From 661329b9774842dcad6a3b5a731f2c3805ed1881 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 9 May 2024 19:54:28 +0300 Subject: [PATCH 001/101] Invalid cursor value --- app/controllers/api/databases.php | 6 +++++ composer.lock | 22 +++++++++---------- .../e2e/Services/Databases/DatabasesBase.php | 15 +++++++++++++ 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index c8903538d0..48f2b40acd 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -36,6 +36,7 @@ use Utopia\Database\Validator\Index as IndexValidator; use Utopia\Database\Validator\Key; use Utopia\Database\Validator\Permissions; use Utopia\Database\Validator\Queries; +use Utopia\Database\Validator\Query\Cursor; use Utopia\Database\Validator\Query\Limit; use Utopia\Database\Validator\Query\Offset; use Utopia\Database\Validator\Structure; @@ -3006,6 +3007,11 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') $cursor = \reset($cursor); if ($cursor) { + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $documentId = $cursor->getValue(); $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId)); diff --git a/composer.lock b/composer.lock index 76f51b75c5..34e6031fdc 100644 --- a/composer.lock +++ b/composer.lock @@ -822,16 +822,16 @@ }, { "name": "paragonie/constant_time_encoding", - "version": "v2.6.3", + "version": "v2.7.0", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "58c3f47f650c94ec05a151692652a868995d2938" + "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938", - "reference": "58c3f47f650c94ec05a151692652a868995d2938", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/52a0d99e69f56b9ec27ace92ba56897fe6993105", + "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105", "shasum": "" }, "require": { @@ -885,7 +885,7 @@ "issues": "https://github.com/paragonie/constant_time_encoding/issues", "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2022-06-14T06:56:20+00:00" + "time": "2024-05-08T12:18:48+00:00" }, { "name": "phpmailer/phpmailer", @@ -3660,16 +3660,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.28.0", + "version": "1.29.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "cd06d6b1a1b3c75b0b83f97577869fd85a3cd4fb" + "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/cd06d6b1a1b3c75b0b83f97577869fd85a3cd4fb", - "reference": "cd06d6b1a1b3c75b0b83f97577869fd85a3cd4fb", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/536889f2b340489d328f5ffb7b02bb6b183ddedc", + "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc", "shasum": "" }, "require": { @@ -3701,9 +3701,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.28.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.0" }, - "time": "2024-04-03T18:51:33+00:00" + "time": "2024-05-06T12:04:23+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index 6f65552f1c..0aac8045a7 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -1743,6 +1743,21 @@ trait DatabasesBase $this->assertEquals(400, $documents['headers']['status-code']); + /** + * Test null value for cursor + */ + + $documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + '{"method":"cursorAfter","values":[null]}', + ], + ]); + + $this->assertEquals(400, $documents['headers']['status-code']); + return []; } From 8bf1d4924b596dedeadcd3fd0d56b08072a1bfb0 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 15 May 2024 10:36:18 +0300 Subject: [PATCH 002/101] Use original delete throw --- composer.json | 2 +- composer.lock | 28 +++++++++++++++++++--------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index 8865a32d91..26032698ec 100644 --- a/composer.json +++ b/composer.json @@ -50,7 +50,7 @@ "utopia-php/cache": "0.9.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.49.*", + "utopia-php/database": "dev-main as 0.49.9", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", diff --git a/composer.lock b/composer.lock index 34e6031fdc..d31d08315e 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "6ce62f5b54254e5023c5ace349a0ced7", + "content-hash": "26d20d884c55e5bcbc24ae42e5040d02", "packages": [ { "name": "adhocore/jwt", @@ -1556,16 +1556,16 @@ }, { "name": "utopia-php/database", - "version": "0.49.8", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "4fb1f6d216f6f628dd5b013e1f539ae2191228b1" + "reference": "ee93c14b99820f791c669648854f094fe399a085" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/4fb1f6d216f6f628dd5b013e1f539ae2191228b1", - "reference": "4fb1f6d216f6f628dd5b013e1f539ae2191228b1", + "url": "https://api.github.com/repos/utopia-php/database/zipball/ee93c14b99820f791c669648854f094fe399a085", + "reference": "ee93c14b99820f791c669648854f094fe399a085", "shasum": "" }, "require": { @@ -1586,6 +1586,7 @@ "swoole/ide-helper": "4.8.0", "utopia-php/cli": "^0.14.0" }, + "default-branch": true, "type": "library", "autoload": { "psr-4": { @@ -1606,9 +1607,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.49.8" + "source": "https://github.com/utopia-php/database/tree/0.49.9" }, - "time": "2024-05-09T04:43:05+00:00" + "time": "2024-05-12T23:58:34+00:00" }, { "name": "utopia-php/domains", @@ -5503,9 +5504,18 @@ "time": "2023-11-21T18:54:41+00:00" } ], - "aliases": [], + "aliases": [ + { + "package": "utopia-php/database", + "version": "dev-main", + "alias": "0.49.9", + "alias_normalized": "0.49.9.0" + } + ], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "utopia-php/database": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { From 6a4581671f79def9096c9dd7cfa767a1321f57f8 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 15 May 2024 10:52:45 +0300 Subject: [PATCH 003/101] Use original delete throw --- composer.json | 2 +- composer.lock | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index 26032698ec..412490ece6 100644 --- a/composer.json +++ b/composer.json @@ -50,7 +50,7 @@ "utopia-php/cache": "0.9.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "dev-main as 0.49.9", + "utopia-php/database": "dev-original-exception as 0.49.9", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", diff --git a/composer.lock b/composer.lock index d31d08315e..d5f431bb26 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "26d20d884c55e5bcbc24ae42e5040d02", + "content-hash": "3ecdbc85939cda3b739dcc76c356f3b4", "packages": [ { "name": "adhocore/jwt", @@ -1556,16 +1556,16 @@ }, { "name": "utopia-php/database", - "version": "dev-main", + "version": "dev-original-exception", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "ee93c14b99820f791c669648854f094fe399a085" + "reference": "1a85827705a420b0dbc6c15845c33ba039cde900" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/ee93c14b99820f791c669648854f094fe399a085", - "reference": "ee93c14b99820f791c669648854f094fe399a085", + "url": "https://api.github.com/repos/utopia-php/database/zipball/1a85827705a420b0dbc6c15845c33ba039cde900", + "reference": "1a85827705a420b0dbc6c15845c33ba039cde900", "shasum": "" }, "require": { @@ -1586,7 +1586,6 @@ "swoole/ide-helper": "4.8.0", "utopia-php/cli": "^0.14.0" }, - "default-branch": true, "type": "library", "autoload": { "psr-4": { @@ -1607,9 +1606,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.49.9" + "source": "https://github.com/utopia-php/database/tree/original-exception" }, - "time": "2024-05-12T23:58:34+00:00" + "time": "2024-05-15T07:29:54+00:00" }, { "name": "utopia-php/domains", @@ -5507,7 +5506,7 @@ "aliases": [ { "package": "utopia-php/database", - "version": "dev-main", + "version": "dev-original-exception", "alias": "0.49.9", "alias_normalized": "0.49.9.0" } From 1a5ae40e15695446bf2c630259693dec437936cb Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 15 May 2024 10:56:02 +0300 Subject: [PATCH 004/101] revert --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 412490ece6..8865a32d91 100644 --- a/composer.json +++ b/composer.json @@ -50,7 +50,7 @@ "utopia-php/cache": "0.9.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "dev-original-exception as 0.49.9", + "utopia-php/database": "0.49.*", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", From d14edf4207f9721c64a1caae78ae8492629acf03 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:30:47 -0400 Subject: [PATCH 005/101] reverting: to set coroutines as head --- .github/workflows/codeql-phpstan.yml | 16 + app/cli.php | 256 +-- app/config/variables.php | 2 +- app/controllers/api/account.php | 581 +++--- app/controllers/api/avatars.php | 102 +- app/controllers/api/console.php | 12 +- app/controllers/api/databases.php | 427 ++-- app/controllers/api/functions.php | 211 +- app/controllers/api/graphql.php | 29 +- app/controllers/api/health.php | 132 +- app/controllers/api/locale.php | 20 +- app/controllers/api/messaging.php | 181 +- app/controllers/api/migrations.php | 54 +- app/controllers/api/project.php | 35 +- app/controllers/api/projects.php | 274 +-- app/controllers/api/proxy.php | 20 +- app/controllers/api/storage.php | 211 +- app/controllers/api/teams.php | 106 +- app/controllers/api/users.php | 111 +- app/controllers/api/vcs.php | 89 +- app/controllers/general.php | 263 ++- app/controllers/mock.php | 34 +- app/controllers/shared/api.php | 118 +- app/controllers/shared/api/auth.php | 22 +- app/controllers/web/console.php | 6 +- app/controllers/web/home.php | 4 +- app/http.php | 515 ++--- app/init.php | 1832 ++--------------- app/init/config.php | 37 + app/init/constants.php | 194 ++ app/init/database/filters.php | 397 ++++ app/init/database/formats.php | 43 + app/init/locale.php | 23 + app/init/resources.php | 1046 ++++++++++ app/realtime.php | 277 +-- app/views/install/compose.phtml | 5 +- app/worker.php | 363 +--- composer.json | 45 +- composer.lock | 622 +++--- docker-compose.yml | 6 +- docs/tutorials/add-route.md | 30 +- phpstan.neon | 11 + src/Appwrite/Auth/Auth.php | 36 +- src/Appwrite/Auth/Authentication.php | 57 + src/Appwrite/Auth/Validator/MockNumber.php | 5 +- src/Appwrite/Auth/Validator/Password.php | 2 +- src/Appwrite/Auth/Validator/Phone.php | 2 +- src/Appwrite/Event/Func.php | 4 +- src/Appwrite/Event/Validator/Event.php | 2 +- src/Appwrite/Functions/Validator/Headers.php | 2 +- src/Appwrite/Functions/Validator/Payload.php | 2 +- .../Validator/RuntimeSpecification.php | 2 +- src/Appwrite/GraphQL/Resolvers.php | 170 +- src/Appwrite/GraphQL/Schema.php | 119 +- src/Appwrite/GraphQL/Types/Mapper.php | 64 +- src/Appwrite/Migration/Migration.php | 36 +- src/Appwrite/Network/Validator/CNAME.php | 2 +- src/Appwrite/Network/Validator/Email.php | 4 +- src/Appwrite/Network/Validator/Origin.php | 4 +- src/Appwrite/Platform/Tasks/Doctor.php | 80 +- src/Appwrite/Platform/Tasks/Install.php | 11 +- src/Appwrite/Platform/Tasks/Migrate.php | 22 +- src/Appwrite/Platform/Tasks/QueueCount.php | 4 +- src/Appwrite/Platform/Tasks/QueueRetry.php | 4 +- src/Appwrite/Platform/Tasks/SSL.php | 4 +- src/Appwrite/Platform/Tasks/ScheduleBase.php | 123 +- .../Platform/Tasks/ScheduleExecutions.php | 30 +- .../Platform/Tasks/ScheduleFunctions.php | 17 +- .../Platform/Tasks/ScheduleMessages.php | 24 +- src/Appwrite/Platform/Tasks/Specs.php | 50 +- src/Appwrite/Platform/Tasks/Upgrade.php | 4 +- src/Appwrite/Platform/Workers/Audits.php | 6 +- src/Appwrite/Platform/Workers/Builds.php | 66 +- .../Platform/Workers/Certificates.php | 30 +- src/Appwrite/Platform/Workers/Deletes.php | 14 +- src/Appwrite/Platform/Workers/Mails.php | 6 +- src/Appwrite/Promises/Promise.php | 2 +- src/Appwrite/Promises/Swoole.php | 17 +- src/Appwrite/Specification/Format.php | 10 +- .../Specification/Format/OpenAPI3.php | 53 +- .../Specification/Format/Swagger2.php | 44 +- src/Appwrite/Task/Validator/Cron.php | 2 +- .../Utopia/Database/Validator/CompoundUID.php | 2 +- .../Utopia/Database/Validator/ProjectId.php | 2 +- src/Appwrite/Utopia/Queue/Connections.php | 36 + src/Appwrite/Utopia/Request.php | 36 +- src/Appwrite/Utopia/Response.php | 318 +-- src/Appwrite/Utopia/Response/Models.php | 304 +++ src/Appwrite/Utopia/View.php | 2 +- .../e2e/Services/Databases/DatabasesBase.php | 4 +- .../Databases/DatabasesCustomClientTest.php | 4 +- .../DatabasesPermissionsGuestTest.php | 20 +- .../e2e/Services/Functions/FunctionsBase.php | 5 +- .../Functions/FunctionsCustomServerTest.php | 6 +- tests/e2e/Services/GraphQL/Base.php | 2 +- .../Services/GraphQL/StorageClientTest.php | 1 - .../Services/GraphQL/StorageServerTest.php | 1 - .../Projects/ProjectsConsoleClientTest.php | 2 +- .../Realtime/RealtimeCustomClientTest.php | 5 +- .../Storage/StorageCustomClientTest.php | 4 +- .../Webhooks/WebhooksCustomServerTest.php | 5 +- tests/resources/docker/docker-compose.yml | 2 +- tests/unit/Auth/AuthTest.php | 35 +- tests/unit/Event/EventTest.php | 10 +- tests/unit/GraphQL/BuilderTest.php | 9 +- .../unit/Messaging/MessagingChannelsTest.php | 9 +- tests/unit/Migration/MigrationTest.php | 2 +- tests/unit/Utopia/RequestTest.php | 7 +- tests/unit/Utopia/ResponseTest.php | 10 +- 109 files changed, 5668 insertions(+), 5073 deletions(-) create mode 100644 .github/workflows/codeql-phpstan.yml create mode 100644 app/init/config.php create mode 100644 app/init/constants.php create mode 100644 app/init/database/filters.php create mode 100644 app/init/database/formats.php create mode 100644 app/init/locale.php create mode 100644 app/init/resources.php create mode 100644 phpstan.neon create mode 100644 src/Appwrite/Auth/Authentication.php create mode 100644 src/Appwrite/Utopia/Queue/Connections.php create mode 100644 src/Appwrite/Utopia/Response/Models.php diff --git a/.github/workflows/codeql-phpstan.yml b/.github/workflows/codeql-phpstan.yml new file mode 100644 index 0000000000..3253e2c38b --- /dev/null +++ b/.github/workflows/codeql-phpstan.yml @@ -0,0 +1,16 @@ +name: "CodeQL" + +on: [pull_request] +jobs: + lint: + name: CodeQL + runs-on: ubuntu-latest + + steps: + - name: Check out the repo + uses: actions/checkout@v2 + + - name: Run CodeQL + run: | + docker run --rm -v $PWD:/app composer sh -c \ + "composer install --profile --ignore-platform-reqs && composer check" \ No newline at end of file diff --git a/app/cli.php b/app/cli.php index 23502ec402..51a8e86137 100644 --- a/app/cli.php +++ b/app/cli.php @@ -7,195 +7,101 @@ use Appwrite\Event\Delete; use Appwrite\Event\Func; use Appwrite\Platform\Appwrite; use Appwrite\Runtimes\Runtimes; -use Utopia\Cache\Adapter\Sharding; -use Utopia\Cache\Cache; -use Utopia\CLI\CLI; +use Swoole\Runtime; +use Utopia\CLI\Adapters\Swoole as SwooleCLI; use Utopia\CLI\Console; use Utopia\Config\Config; -use Utopia\Database\Database; -use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -use Utopia\DSN\DSN; +use Utopia\DI\Dependency; use Utopia\Logger\Log; use Utopia\Platform\Service; -use Utopia\Pools\Group; use Utopia\Queue\Connection; use Utopia\Registry\Registry; use Utopia\System\System; +global $registry, $container; + +Runtime::enableCoroutine(SWOOLE_HOOK_ALL); + // overwriting runtimes to be architectur agnostic for CLI Config::setParam('runtimes', (new Runtimes('v4'))->getAll(supported: false)); // require controllers after overwriting runtimes require_once __DIR__ . '/controllers/general.php'; -Authorization::disable(); +/** + * @var Registry $registry + * @var Container $container + */ +$context = new Dependency(); +$register = new Dependency(); +$logError = new Dependency(); +$queueForDeletes = new Dependency(); +$queueForFunctions = new Dependency(); +$queueForCertificates = new Dependency(); -CLI::setResource('register', fn () => $register); +$context + ->setName('context') + ->setCallback(fn () => $container); -CLI::setResource('cache', function ($pools) { - $list = Config::getParam('pools-cache', []); - $adapters = []; +$register + ->setName('register') + ->setCallback(function () use (&$registry): Registry { + return $registry; + }); - foreach ($list as $value) { - $adapters[] = $pools - ->get($value) - ->pop() - ->getResource() - ; - } +$queueForFunctions + ->setName('queueForFunctions') + ->inject('queue') + ->setCallback(function (Connection $queue) { + return new Func($queue); + }); - return new Cache(new Sharding($adapters)); -}, ['pools']); -CLI::setResource('pools', function (Registry $register) { - return $register->get('pools'); -}, ['register']); +$queueForDeletes + ->setName('queueForDeletes') + ->inject('queue') + ->setCallback(function (Connection $queue) { + return new Delete($queue); + }); -CLI::setResource('dbForConsole', function ($pools, $cache) { - $sleep = 3; - $maxAttempts = 5; - $attempts = 0; - $ready = false; +$queueForCertificates + ->setName('queueForCertificates') + ->inject('queue') + ->setCallback(function (Connection $queue) { + return new Certificate($queue); + }); - do { - $attempts++; - try { - // Prepare database connection - $dbAdapter = $pools - ->get('console') - ->pop() - ->getResource(); +$logError + ->setName('logError') + ->inject('register') + ->setCallback(function (Registry $register) { + return function (Throwable $error, string $namespace, string $action) use ($register) { + $logger = $register->get('logger'); - $dbForConsole = new Database($dbAdapter, $cache); + if ($logger) { + $version = System::getEnv('_APP_VERSION', 'UNKNOWN'); - $dbForConsole - ->setNamespace('_console') - ->setMetadata('host', \gethostname()) - ->setMetadata('project', 'console'); + $log = new Log(); + $log->setNamespace($namespace); + $log->setServer(\gethostname()); + $log->setVersion($version); + $log->setType(Log::TYPE_ERROR); + $log->setMessage($error->getMessage()); - // Ensure tables exist - $collections = Config::getParam('collections', [])['console']; - $last = \array_key_last($collections); + $log->addTag('code', $error->getCode()); + $log->addTag('verboseType', get_class($error)); - if (!($dbForConsole->exists($dbForConsole->getDatabase(), $last))) { /** TODO cache ready variable using registry */ - throw new Exception('Tables not ready yet.'); - } + $log->addExtra('file', $error->getFile()); + $log->addExtra('line', $error->getLine()); + $log->addExtra('trace', $error->getTraceAsString()); + $log->addExtra('trace', $error->getTraceAsString()); - $ready = true; - } catch (\Throwable $err) { - Console::warning($err->getMessage()); - $pools->get('console')->reclaim(); - sleep($sleep); - } - } while ($attempts < $maxAttempts && !$ready); + $log->setAction($action); - if (!$ready) { - throw new Exception("Console is not ready yet. Please try again later."); - } + $isProduction = System::getEnv('_APP_ENV', 'development') === 'production'; - return $dbForConsole; -}, ['pools', 'cache']); - -CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) { - $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools - - return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases) { - if ($project->isEmpty() || $project->getId() === 'console') { - return $dbForConsole; - } - - 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')); - } - - if (isset($databases[$dsn->getHost()])) { - $database = $databases[$dsn->getHost()]; - - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { - $database - ->setSharedTables(true) - ->setTenant($project->getInternalId()) - ->setNamespace($dsn->getParam('namespace')); - } else { - $database - ->setSharedTables(false) - ->setTenant(null) - ->setNamespace('_' . $project->getInternalId()); - } - - return $database; - } - - $dbAdapter = $pools - ->get($dsn->getHost()) - ->pop() - ->getResource(); - - $database = new Database($dbAdapter, $cache); - - $databases[$dsn->getHost()] = $database; - - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { - $database - ->setSharedTables(true) - ->setTenant($project->getInternalId()) - ->setNamespace($dsn->getParam('namespace')); - } else { - $database - ->setSharedTables(false) - ->setTenant(null) - ->setNamespace('_' . $project->getInternalId()); - } - - $database - ->setMetadata('host', \gethostname()) - ->setMetadata('project', $project->getId()); - - return $database; - }; -}, ['pools', 'dbForConsole', 'cache']); - -CLI::setResource('queue', function (Group $pools) { - return $pools->get('queue')->pop()->getResource(); -}, ['pools']); -CLI::setResource('queueForFunctions', function (Connection $queue) { - return new Func($queue); -}, ['queue']); -CLI::setResource('queueForDeletes', function (Connection $queue) { - return new Delete($queue); -}, ['queue']); -CLI::setResource('queueForCertificates', function (Connection $queue) { - return new Certificate($queue); -}, ['queue']); -CLI::setResource('logError', function (Registry $register) { - return function (Throwable $error, string $namespace, string $action) use ($register) { - $logger = $register->get('logger'); - - if ($logger) { - $version = System::getEnv('_APP_VERSION', 'UNKNOWN'); - - $log = new Log(); - $log->setNamespace($namespace); - $log->setServer(\gethostname()); - $log->setVersion($version); - $log->setType(Log::TYPE_ERROR); - $log->setMessage($error->getMessage()); - - $log->addTag('code', $error->getCode()); - $log->addTag('verboseType', get_class($error)); - - $log->addExtra('file', $error->getFile()); - $log->addExtra('line', $error->getLine()); - $log->addExtra('trace', $error->getTraceAsString()); - - $log->setAction($action); - - $isProduction = System::getEnv('_APP_ENV', 'development') === 'production'; - $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING); + $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING); try { $responseCode = $logger->addLog($log); @@ -203,18 +109,32 @@ CLI::setResource('logError', function (Registry $register) { } catch (Throwable $th) { Console::error('Error pushing log: ' . $th->getMessage()); } - } + } - Console::warning("Failed: {$error->getMessage()}"); - Console::warning($error->getTraceAsString()); - }; -}, ['register']); + Console::warning("Failed: {$error->getMessage()}"); + Console::warning($error->getTraceAsString()); + }; + }); + +$container->set($context); +$container->set($logError); +$container->set($register); +$container->set($queueForDeletes); +$container->set($queueForFunctions); +$container->set($queueForCertificates); $platform = new Appwrite(); -$platform->init(Service::TYPE_TASK); +$platform->init(Service::TYPE_TASK, ['adapter' => new SwooleCLI(1)]); $cli = $platform->getCli(); +$cli + ->init() + ->inject('authorization') + ->action(function (Authorization $authorization) { + $authorization->disable(); + }); + $cli ->error() ->inject('error') @@ -222,4 +142,6 @@ $cli Console::error($error->getMessage()); }); -$cli->run(); +$cli + ->setContainer($container) + ->run(); diff --git a/app/config/variables.php b/app/config/variables.php index 113fbae335..161951e3de 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -254,7 +254,7 @@ return [ 'name' => '_APP_WORKER_PER_CORE', 'description' => 'Internal Worker per core for the API, Realtime and Executor containers. Can be configured to optimize performance.', 'introduction' => '0.13.0', - 'default' => 6, + 'default' => 2, 'required' => false, 'question' => '', 'filter' => '' diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 64441fee5c..a76b9dbea5 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2,6 +2,7 @@ use Ahc\Jwt\JWT; use Appwrite\Auth\Auth; +use Appwrite\Auth\Authentication; use Appwrite\Auth\MFA\Challenge; use Appwrite\Auth\MFA\Type; use Appwrite\Auth\MFA\Type\TOTP; @@ -28,7 +29,6 @@ use Appwrite\Utopia\Database\Validator\Queries\Identities; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use MaxMind\Db\Reader; -use Utopia\App; use Utopia\Audit\Audit as EventAudit; use Utopia\Config\Config; use Utopia\Database\Database; @@ -45,15 +45,16 @@ use Utopia\Database\Validator\Queries; use Utopia\Database\Validator\Query\Limit; use Utopia\Database\Validator\Query\Offset; use Utopia\Database\Validator\UID; +use Utopia\Http\Http; +use Utopia\Http\Validator\ArrayList; +use Utopia\Http\Validator\Assoc; +use Utopia\Http\Validator\Boolean; +use Utopia\Http\Validator\Host; +use Utopia\Http\Validator\Text; +use Utopia\Http\Validator\URL; +use Utopia\Http\Validator\WhiteList; use Utopia\Locale\Locale; use Utopia\System\System; -use Utopia\Validator\ArrayList; -use Utopia\Validator\Assoc; -use Utopia\Validator\Boolean; -use Utopia\Validator\Host; -use Utopia\Validator\Text; -use Utopia\Validator\URL; -use Utopia\Validator\WhiteList; $oauthDefaultSuccess = '/console/auth/oauth2/success'; $oauthDefaultFailure = '/console/auth/oauth2/failure'; @@ -144,14 +145,13 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc ->trigger(); }; - -$createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails) { - $roles = Authorization::getRoles(); +$createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Authorization $authorization, Authentication $authentication) { + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); /** @var Utopia\Database\Document $user */ - $userFromRequest = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId)); + $userFromRequest = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId)); if ($userFromRequest->isEmpty()) { throw new Exception(Exception::USER_INVALID_TOKEN); @@ -197,7 +197,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res $detector->getDevice() )); - Authorization::setRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); $session = $dbForProject->createDocument('sessions', $session ->setAttribute('$permissions', [ @@ -206,7 +206,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res Permission::delete(Role::user($user->getId())), ])); - Authorization::skip(fn () => $dbForProject->deleteDocument('tokens', $verifiedToken->getId())); + $authorization->skip(fn () => $dbForProject->deleteDocument('tokens', $verifiedToken->getId())); $dbForProject->purgeCachedDocument('users', $user->getId()); // Magic URL + Email OTP @@ -247,15 +247,15 @@ $createSession = function (string $userId, string $secret, Request $request, Res ->setParam('sessionId', $session->getId()); if (!Config::getParam('domainVerification')) { - $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $sessionSecret)])); + $response->addHeader('X-Fallback-Cookies', \json_encode([$authentication->getCookieName() => Auth::encodeSession($user->getId(), $sessionSecret)])); } $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)); $protocol = $request->getProtocol(); $response - ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $sessionSecret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $sessionSecret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie($authentication->getCookieName() . '_legacy', Auth::encodeSession($user->getId(), $sessionSecret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie($authentication->getCookieName(), Auth::encodeSession($user->getId(), $sessionSecret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->setStatusCode(Response::STATUS_CODE_CREATED); $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); @@ -270,7 +270,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res $response->dynamic($session, Response::MODEL_SESSION); }; -App::post('/v1/account') +Http::post('/v1/account') ->desc('Create account') ->groups(['api', 'account', 'auth']) ->label('event', 'users.[userId].create') @@ -298,7 +298,8 @@ App::post('/v1/account') ->inject('dbForProject') ->inject('queueForEvents') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) { + ->inject('authorization') + ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks, Authorization $authorization) { $email = \strtolower($email); if ('console' === $project->getId()) { @@ -376,9 +377,9 @@ App::post('/v1/account') 'accessedAt' => DateTime::now(), ]); $user->removeAttribute('$internalId'); - $user = Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); + $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user)); try { - $target = Authorization::skip(fn () => $dbForProject->createDocument('targets', new Document([ + $target = $authorization->skip(fn () => $dbForProject->createDocument('targets', new Document([ '$permissions' => [ Permission::read(Role::user($user->getId())), Permission::update(Role::user($user->getId())), @@ -404,9 +405,9 @@ App::post('/v1/account') throw new Exception(Exception::USER_ALREADY_EXISTS); } - Authorization::unsetRole(Role::guests()->toString()); - Authorization::setRole(Role::user($user->getId())->toString()); - Authorization::setRole(Role::users()->toString()); + $authorization->removeRole(Role::guests()->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::users()->toString()); $queueForEvents->setParam('userId', $user->getId()); @@ -415,7 +416,7 @@ App::post('/v1/account') ->dynamic($user, Response::MODEL_ACCOUNT); }); -App::get('/v1/account') +Http::get('/v1/account') ->desc('Get account') ->groups(['api', 'account']) ->label('scope', 'account') @@ -438,7 +439,7 @@ App::get('/v1/account') $response->dynamic($user, Response::MODEL_ACCOUNT); }); -App::delete('/v1/account') +Http::delete('/v1/account') ->desc('Delete account') ->groups(['api', 'account']) ->label('event', 'users.[userId].delete') @@ -486,7 +487,7 @@ App::delete('/v1/account') $response->noContent(); }); -App::get('/v1/account/sessions') +Http::get('/v1/account/sessions') ->desc('List sessions') ->groups(['api', 'account']) ->label('scope', 'account') @@ -501,15 +502,16 @@ App::get('/v1/account/sessions') ->inject('response') ->inject('user') ->inject('locale') - ->inject('project') - ->action(function (Response $response, Document $user, Locale $locale, Document $project) { + ->inject('authorization') + ->inject('authentication') + ->action(function (Response $response, Document $user, Locale $locale, Authorization $authorization, Authentication $authentication) { - $roles = Authorization::getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); $sessions = $user->getAttribute('sessions', []); - $current = Auth::sessionVerify($sessions, Auth::$secret); + $current = Auth::sessionVerify($sessions, $authentication->getSecret()); foreach ($sessions as $key => $session) {/** @var Document $session */ $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); @@ -527,7 +529,7 @@ App::get('/v1/account/sessions') ]), Response::MODEL_SESSION_LIST); }); -App::delete('/v1/account/sessions') +Http::delete('/v1/account/sessions') ->desc('Delete sessions') ->groups(['api', 'account']) ->label('scope', 'account') @@ -548,7 +550,8 @@ App::delete('/v1/account/sessions') ->inject('locale') ->inject('queueForEvents') ->inject('queueForDeletes') - ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes) { + ->inject('authentication') + ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Authentication $authentication) { $protocol = $request->getProtocol(); $sessions = $user->getAttribute('sessions', []); @@ -564,13 +567,13 @@ App::delete('/v1/account/sessions') ->setAttribute('current', false) ->setAttribute('countryName', $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'))); - if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { + if ($session->getAttribute('secret') == Auth::hash($authentication->getSecret())) { $session->setAttribute('current', true); // If current session delete the cookies too $response - ->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); + ->addCookie($authentication->getCookieName() . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie($authentication->getCookieName(), '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); // Use current session for events. $queueForEvents @@ -592,7 +595,7 @@ App::delete('/v1/account/sessions') $response->noContent(); }); -App::get('/v1/account/sessions/:sessionId') +Http::get('/v1/account/sessions/:sessionId') ->desc('Get session') ->groups(['api', 'account']) ->label('scope', 'account') @@ -609,16 +612,17 @@ App::get('/v1/account/sessions/:sessionId') ->inject('response') ->inject('user') ->inject('locale') - ->inject('project') - ->action(function (?string $sessionId, Response $response, Document $user, Locale $locale, Document $project) { + ->inject('authorization') + ->inject('authentication') + ->action(function (?string $sessionId, Response $response, Document $user, Locale $locale, Authorization $authorization, Authentication $authentication) { - $roles = Authorization::getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); $sessions = $user->getAttribute('sessions', []); $sessionId = ($sessionId === 'current') - ? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret) + ? Auth::sessionVerify($user->getAttribute('sessions'), $authentication->getSecret()) : $sessionId; foreach ($sessions as $session) {/** @var Document $session */ @@ -626,7 +630,7 @@ App::get('/v1/account/sessions/:sessionId') $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); $session - ->setAttribute('current', ($session->getAttribute('secret') == Auth::hash(Auth::$secret))) + ->setAttribute('current', ($session->getAttribute('secret') == Auth::hash($authentication->getSecret()))) ->setAttribute('countryName', $countryName) ->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $session->getAttribute('secret', '') : '') ; @@ -638,7 +642,7 @@ App::get('/v1/account/sessions/:sessionId') throw new Exception(Exception::USER_SESSION_NOT_FOUND); }); -App::delete('/v1/account/sessions/:sessionId') +Http::delete('/v1/account/sessions/:sessionId') ->desc('Delete session') ->groups(['api', 'account', 'mfa']) ->label('scope', 'account') @@ -661,12 +665,12 @@ App::delete('/v1/account/sessions/:sessionId') ->inject('locale') ->inject('queueForEvents') ->inject('queueForDeletes') - ->inject('project') - ->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Document $project) { + ->inject('authentication') + ->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Authentication $authentication) { $protocol = $request->getProtocol(); $sessionId = ($sessionId === 'current') - ? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret) + ? Auth::sessionVerify($user->getAttribute('sessions'), $authentication->getSecret()) : $sessionId; $sessions = $user->getAttribute('sessions', []); @@ -685,7 +689,7 @@ App::delete('/v1/account/sessions/:sessionId') $session->setAttribute('current', false); - if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too + if ($session->getAttribute('secret') == Auth::hash($authentication->getSecret())) { // If current session delete the cookies too $session ->setAttribute('current', true) ->setAttribute('countryName', $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'))); @@ -695,8 +699,8 @@ App::delete('/v1/account/sessions/:sessionId') } $response - ->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); + ->addCookie($authentication->getCookieName() . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie($authentication->getCookieName(), '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); } $dbForProject->purgeCachedDocument('users', $user->getId()); @@ -718,7 +722,7 @@ App::delete('/v1/account/sessions/:sessionId') throw new Exception(Exception::USER_SESSION_NOT_FOUND); }); -App::patch('/v1/account/sessions/:sessionId') +Http::patch('/v1/account/sessions/:sessionId') ->desc('Update session') ->groups(['api', 'account']) ->label('scope', 'account') @@ -740,10 +744,11 @@ App::patch('/v1/account/sessions/:sessionId') ->inject('dbForProject') ->inject('project') ->inject('queueForEvents') - ->action(function (?string $sessionId, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents) { + ->inject('authentication') + ->action(function (?string $sessionId, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Authentication $authentication) { $sessionId = ($sessionId === 'current') - ? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret) + ? Auth::sessionVerify($user->getAttribute('sessions'), $authentication->getSecret()) : $sessionId; $sessions = $user->getAttribute('sessions', []); @@ -794,7 +799,7 @@ App::patch('/v1/account/sessions/:sessionId') return $response->dynamic($session, Response::MODEL_SESSION); }); -App::post('/v1/account/sessions/email') +Http::post('/v1/account/sessions/email') ->alias('/v1/account/sessions') ->desc('Create email password session') ->groups(['api', 'account', 'auth', 'session']) @@ -825,7 +830,9 @@ App::post('/v1/account/sessions/email') ->inject('queueForEvents') ->inject('queueForMails') ->inject('hooks') - ->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks) { + ->inject('authorization') + ->inject('authentication') + ->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks, Authorization $authorization, Authentication $authentication) { $email = \strtolower($email); $protocol = $request->getProtocol(); @@ -841,7 +848,7 @@ App::post('/v1/account/sessions/email') throw new Exception(Exception::USER_BLOCKED); // User is in status blocked } - $roles = Authorization::getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -872,7 +879,7 @@ App::post('/v1/account/sessions/email') $detector->getDevice() )); - Authorization::setRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); // Re-hash if not using recommended algo if ($user->getAttribute('hash') !== Auth::DEFAULT_ALGO) { @@ -893,15 +900,15 @@ App::post('/v1/account/sessions/email') if (!Config::getParam('domainVerification')) { $response - ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])) + ->addHeader('X-Fallback-Cookies', \json_encode([$authentication->getCookieName() => Auth::encodeSession($user->getId(), $secret)])) ; } $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)); $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')) + ->addCookie($authentication->getCookieName() . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie($authentication->getCookieName(), Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->setStatusCode(Response::STATUS_CODE_CREATED) ; @@ -929,7 +936,7 @@ App::post('/v1/account/sessions/email') $response->dynamic($session, Response::MODEL_SESSION); }); -App::post('/v1/account/sessions/anonymous') +Http::post('/v1/account/sessions/anonymous') ->desc('Create anonymous session') ->groups(['api', 'account', 'auth', 'session']) ->label('event', 'users.[userId].sessions.[sessionId].create') @@ -955,9 +962,11 @@ App::post('/v1/account/sessions/anonymous') ->inject('dbForProject') ->inject('geodb') ->inject('queueForEvents') - ->action(function (Request $request, Response $response, Locale $locale, Document $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents) { + ->inject('authorization') + ->inject('authentication') + ->action(function (Request $request, Response $response, Locale $locale, Document $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents, Authorization $authorization, Authentication $authentication) { $protocol = $request->getProtocol(); - $roles = Authorization::getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -1003,7 +1012,7 @@ App::post('/v1/account/sessions/anonymous') 'accessedAt' => DateTime::now(), ]); $user->removeAttribute('$internalId'); - Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); + $authorization->skip(fn () => $dbForProject->createDocument('users', $user)); // Create session token $duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; @@ -1029,7 +1038,7 @@ App::post('/v1/account/sessions/anonymous') $detector->getDevice() )); - Authorization::setRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); $session = $dbForProject->createDocument('sessions', $session-> setAttribute('$permissions', [ Permission::read(Role::user($user->getId())), @@ -1045,14 +1054,14 @@ App::post('/v1/account/sessions/anonymous') ; if (!Config::getParam('domainVerification')) { - $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])); + $response->addHeader('X-Fallback-Cookies', \json_encode([$authentication->getCookieName() => Auth::encodeSession($user->getId(), $secret)])); } $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)); $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')) + ->addCookie($authentication->getCookieName() . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie($authentication->getCookieName(), Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->setStatusCode(Response::STATUS_CODE_CREATED) ; @@ -1067,7 +1076,7 @@ App::post('/v1/account/sessions/anonymous') $response->dynamic($session, Response::MODEL_SESSION); }); -App::post('/v1/account/sessions/token') +Http::post('/v1/account/sessions/token') ->desc('Create session') ->label('event', 'users.[userId].sessions.[sessionId].create') ->groups(['api', 'account', 'session']) @@ -1095,9 +1104,11 @@ App::post('/v1/account/sessions/token') ->inject('geodb') ->inject('queueForEvents') ->inject('queueForMails') + ->inject('authorization') + ->inject('authentication') ->action($createSession); -App::get('/v1/account/sessions/oauth2/:provider') +Http::get('/v1/account/sessions/oauth2/:provider') ->desc('Create OAuth2 session') ->groups(['api', 'account']) ->label('error', __DIR__ . '/../../views/general/error.phtml') @@ -1167,7 +1178,7 @@ App::get('/v1/account/sessions/oauth2/:provider') ->redirect($oauth2->getLoginURL()); }); -App::get('/v1/account/sessions/oauth2/callback/:provider/:projectId') +Http::get('/v1/account/sessions/oauth2/callback/:provider/:projectId') ->desc('OAuth2 callback') ->groups(['account']) ->label('error', __DIR__ . '/../../views/general/error.phtml') @@ -1197,7 +1208,7 @@ App::get('/v1/account/sessions/oauth2/callback/:provider/:projectId') . \http_build_query($params)); }); -App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId') +Http::post('/v1/account/sessions/oauth2/callback/:provider/:projectId') ->desc('OAuth2 callback') ->groups(['account']) ->label('error', __DIR__ . '/../../views/general/error.phtml') @@ -1228,7 +1239,7 @@ App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId') . \http_build_query($params)); }); -App::get('/v1/account/sessions/oauth2/:provider/redirect') +Http::get('/v1/account/sessions/oauth2/:provider/redirect') ->desc('OAuth2 redirect') ->groups(['api', 'account', 'session']) ->label('error', __DIR__ . '/../../views/general/error.phtml') @@ -1252,7 +1263,9 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ->inject('dbForProject') ->inject('geodb') ->inject('queueForEvents') - ->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Reader $geodb, Event $queueForEvents) use ($oauthDefaultSuccess) { + ->inject('authorization') + ->inject('authentication') + ->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Reader $geodb, Event $queueForEvents, Authorization $authorization, Authentication $authentication) use ($oauthDefaultSuccess) { $protocol = $request->getProtocol(); $callback = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId(); $defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => '']; @@ -1389,7 +1402,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') } $sessions = $user->getAttribute('sessions', []); - $current = Auth::sessionVerify($sessions, Auth::$secret); + $current = Auth::sessionVerify($sessions, $authentication->getSecret()); if ($current) { // Delete current session of new one. $currentDocument = $dbForProject->getDocument('sessions', $current); @@ -1486,15 +1499,16 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'accessedAt' => DateTime::now(), ]); $user->removeAttribute('$internalId'); - $userDoc = Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); + $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user)); + $dbForProject->createDocument('targets', new Document([ '$permissions' => [ Permission::read(Role::user($user->getId())), Permission::update(Role::user($user->getId())), Permission::delete(Role::user($user->getId())), ], - 'userId' => $userDoc->getId(), - 'userInternalId' => $userDoc->getInternalId(), + 'userId' => $user->getId(), + 'userInternalId' => $user->getInternalId(), 'providerType' => MESSAGE_TYPE_EMAIL, 'identifier' => $email, ])); @@ -1504,8 +1518,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') } } - Authorization::setRole(Role::user($user->getId())->toString()); - Authorization::setRole(Role::users()->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::users()->toString()); if (false === $user->getAttribute('status')) { // Account is blocked $failureRedirect(Exception::USER_BLOCKED); // User is in status blocked @@ -1564,7 +1578,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $dbForProject->updateDocument('users', $user->getId(), $user); - Authorization::setRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); $state['success'] = URLParser::parse($state['success']); $query = URLParser::parseQuery($state['success']['query']); @@ -1586,7 +1600,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'ip' => $request->getIP(), ]); - Authorization::setRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); $token = $dbForProject->createDocument('tokens', $token ->setAttribute('$permissions', [ @@ -1636,7 +1650,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $session->setAttribute('expire', $expire); if (!Config::getParam('domainVerification')) { - $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])); + $response->addHeader('X-Fallback-Cookies', \json_encode([$authentication->getCookieName() => Auth::encodeSession($user->getId(), $secret)])); } $queueForEvents @@ -1649,13 +1663,13 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') if ($state['success']['path'] == $oauthDefaultSuccess) { $query['project'] = $project->getId(); $query['domain'] = Config::getParam('cookieDomain'); - $query['key'] = Auth::$cookieName; + $query['key'] = $authentication->getCookieName(); $query['secret'] = 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')); + ->addCookie($authentication->getCookieName() . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie($authentication->getCookieName(), Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); } if (isset($sessionUpgrade) && $sessionUpgrade) { @@ -1686,7 +1700,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ; }); -App::get('/v1/account/tokens/oauth2/:provider') +Http::get('/v1/account/tokens/oauth2/:provider') ->desc('Create OAuth2 token') ->groups(['api', 'account']) ->label('error', __DIR__ . '/../../views/general/error.phtml') @@ -1755,7 +1769,7 @@ App::get('/v1/account/tokens/oauth2/:provider') ->redirect($oauth2->getLoginURL()); }); -App::post('/v1/account/tokens/magic-url') +Http::post('/v1/account/tokens/magic-url') ->alias('/v1/account/sessions/magic-url') ->desc('Create magic URL token') ->groups(['api', 'account', 'auth']) @@ -1785,7 +1799,8 @@ App::post('/v1/account/tokens/magic-url') ->inject('locale') ->inject('queueForEvents') ->inject('queueForMails') - ->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails) { + ->inject('authorization') + ->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, Authorization $authorization) { if (empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled'); } @@ -1795,7 +1810,7 @@ App::post('/v1/account/tokens/magic-url') $phrase = Phrase::generate(); } - $roles = Authorization::getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -1850,7 +1865,7 @@ App::post('/v1/account/tokens/magic-url') ]); $user->removeAttribute('$internalId'); - Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); + $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user)); } $tokenSecret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_MAGIC_URL); @@ -1867,7 +1882,7 @@ App::post('/v1/account/tokens/magic-url') 'ip' => $request->getIP(), ]); - Authorization::setRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); $token = $dbForProject->createDocument('tokens', $token ->setAttribute('$permissions', [ @@ -1999,7 +2014,7 @@ App::post('/v1/account/tokens/magic-url') ->dynamic($token, Response::MODEL_TOKEN); }); -App::post('/v1/account/tokens/email') +Http::post('/v1/account/tokens/email') ->desc('Create email token (OTP)') ->groups(['api', 'account', 'auth']) ->label('scope', 'sessions.write') @@ -2027,7 +2042,8 @@ App::post('/v1/account/tokens/email') ->inject('locale') ->inject('queueForEvents') ->inject('queueForMails') - ->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails) { + ->inject('authorization') + ->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, Authorization $authorization) { if (empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled'); } @@ -2036,7 +2052,7 @@ App::post('/v1/account/tokens/email') $phrase = Phrase::generate(); } - $roles = Authorization::getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -2089,7 +2105,7 @@ App::post('/v1/account/tokens/email') ]); $user->removeAttribute('$internalId'); - Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); + $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user)); } $tokenSecret = Auth::codeGenerator(6); @@ -2106,7 +2122,7 @@ App::post('/v1/account/tokens/email') 'ip' => $request->getIP(), ]); - Authorization::setRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); $token = $dbForProject->createDocument('tokens', $token ->setAttribute('$permissions', [ @@ -2228,7 +2244,7 @@ App::post('/v1/account/tokens/email') ->dynamic($token, Response::MODEL_TOKEN); }); -App::put('/v1/account/sessions/magic-url') +Http::put('/v1/account/sessions/magic-url') ->desc('Update magic URL session') ->label('event', 'users.[userId].sessions.[sessionId].create') ->groups(['api', 'account', 'session']) @@ -2257,9 +2273,11 @@ App::put('/v1/account/sessions/magic-url') ->inject('geodb') ->inject('queueForEvents') ->inject('queueForMails') + ->inject('authorization') + ->inject('authentication') ->action($createSession); -App::put('/v1/account/sessions/phone') +Http::put('/v1/account/sessions/phone') ->desc('Update phone session') ->label('event', 'users.[userId].sessions.[sessionId].create') ->groups(['api', 'account', 'session']) @@ -2288,9 +2306,11 @@ App::put('/v1/account/sessions/phone') ->inject('geodb') ->inject('queueForEvents') ->inject('queueForMails') + ->inject('authorization') + ->inject('authentication') ->action($createSession); -App::post('/v1/account/tokens/phone') +Http::post('/v1/account/tokens/phone') ->alias('/v1/account/sessions/phone') ->desc('Create phone token') ->groups(['api', 'account']) @@ -2318,12 +2338,13 @@ App::post('/v1/account/tokens/phone') ->inject('queueForEvents') ->inject('queueForMessaging') ->inject('locale') - ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale) { + ->inject('authorization') + ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, Authorization $authorization) { if (empty(System::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); } - $roles = Authorization::getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -2367,9 +2388,9 @@ App::post('/v1/account/tokens/phone') ]); $user->removeAttribute('$internalId'); - Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); + $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user)); try { - $target = Authorization::skip(fn () => $dbForProject->createDocument('targets', new Document([ + $target = $authorization->skip(fn () => $dbForProject->createDocument('targets', new Document([ '$permissions' => [ Permission::read(Role::user($user->getId())), Permission::update(Role::user($user->getId())), @@ -2415,7 +2436,7 @@ App::post('/v1/account/tokens/phone') 'ip' => $request->getIP(), ]); - Authorization::setRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); $token = $dbForProject->createDocument('tokens', $token ->setAttribute('$permissions', [ @@ -2471,7 +2492,7 @@ App::post('/v1/account/tokens/phone') ->dynamic($token, Response::MODEL_TOKEN); }); -App::post('/v1/account/jwts') +Http::post('/v1/account/jwts') ->alias('/v1/account/jwt') ->desc('Create JWT') ->groups(['api', 'account', 'auth']) @@ -2489,14 +2510,15 @@ App::post('/v1/account/jwts') ->inject('response') ->inject('user') ->inject('dbForProject') - ->action(function (Response $response, Document $user, Database $dbForProject) { + ->inject('authentication') + ->action(function (Response $response, Document $user, Database $dbForProject, Authentication $authentication) { $sessions = $user->getAttribute('sessions', []); $current = new Document(); foreach ($sessions as $session) { /** @var Utopia\Database\Document $session */ - if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too + if ($session->getAttribute('secret') == Auth::hash($authentication->getSecret())) { // If current session delete the cookies too $current = $session; } } @@ -2515,7 +2537,7 @@ App::post('/v1/account/jwts') ])]), Response::MODEL_JWT); }); -App::get('/v1/account/prefs') +Http::get('/v1/account/prefs') ->desc('Get account preferences') ->groups(['api', 'account']) ->label('scope', 'account') @@ -2537,7 +2559,7 @@ App::get('/v1/account/prefs') $response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES); }); -App::get('/v1/account/logs') +Http::get('/v1/account/logs') ->desc('List logs') ->groups(['api', 'account']) ->label('scope', 'account') @@ -2554,7 +2576,8 @@ App::get('/v1/account/logs') ->inject('locale') ->inject('geodb') ->inject('dbForProject') - ->action(function (array $queries, Response $response, Document $user, Locale $locale, Reader $geodb, Database $dbForProject) { + ->inject('authorization') + ->action(function (array $queries, Response $response, Document $user, Locale $locale, Reader $geodb, Database $dbForProject, Authorization $authorization) { try { $queries = Query::parseQueries($queries); @@ -2566,7 +2589,7 @@ App::get('/v1/account/logs') $limit = $grouped['limit'] ?? APP_LIMIT_COUNT; $offset = $grouped['offset'] ?? 0; - $audit = new EventAudit($dbForProject); + $audit = new EventAudit($dbForProject, $authorization); $logs = $audit->getLogsByUser($user->getInternalId(), $limit, $offset); @@ -2602,7 +2625,101 @@ App::get('/v1/account/logs') ]), Response::MODEL_LOG_LIST); }); -App::patch('/v1/account/name') +Http::patch('/v1/account/email') + ->desc('Update email') + ->groups(['api', 'account']) + ->label('event', 'users.[userId].update.email') + ->label('scope', 'account') + ->label('audits.event', 'user.update') + ->label('audits.resource', 'user/{response.$id}') + ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) + ->label('sdk.namespace', 'account') + ->label('sdk.method', 'updateEmail') + ->label('sdk.description', '/docs/references/account/update-email.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_USER) + ->label('sdk.offline.model', '/account') + ->label('sdk.offline.key', 'current') + ->param('email', '', new Email(), 'User email.') + ->param('password', '', new Password(), 'User password. Must be at least 8 chars.') + ->inject('requestTimestamp') + ->inject('response') + ->inject('user') + ->inject('dbForProject') + ->inject('queueForEvents') + ->inject('project') + ->inject('hooks') + ->inject('authorization') + ->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, Authorization $authorization) { + // passwordUpdate will be empty if the user has never set a password + $passwordUpdate = $user->getAttribute('passwordUpdate'); + + if ( + !empty($passwordUpdate) && + !Auth::passwordVerify($password, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions')) + ) { // Double check user password + throw new Exception(Exception::USER_INVALID_CREDENTIALS); + } + + $hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, false]); + + $oldEmail = $user->getAttribute('email'); + + $email = \strtolower($email); + + // Makes sure this email is not already used in another identity + $identityWithMatchingEmail = $dbForProject->findOne('identities', [ + Query::equal('providerEmail', [$email]), + Query::notEqual('userInternalId', $user->getInternalId()), + ]); + if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { + throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */ + } + + $user + ->setAttribute('email', $email) + ->setAttribute('emailVerification', false) // After this user needs to confirm mail again + ; + + if (empty($passwordUpdate)) { + $user + ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)) + ->setAttribute('hash', Auth::DEFAULT_ALGO) + ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS) + ->setAttribute('passwordUpdate', DateTime::now()); + } + + $target = $authorization->skip(fn () => $dbForProject->findOne('targets', [ + Query::equal('identifier', [$email]), + ])); + + if ($target instanceof Document && !$target->isEmpty()) { + throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); + } + + try { + $user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user)); + /** + * @var Document $oldTarget + */ + $oldTarget = $user->find('identifier', $oldEmail, 'targets'); + + if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) { + $authorization->skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email))); + } + $dbForProject->purgeCachedDocument('users', $user->getId()); + } catch (Duplicate) { + throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */ + } + + $queueForEvents->setParam('userId', $user->getId()); + + $response->dynamic($user, Response::MODEL_ACCOUNT); + }); + + +Http::patch('/v1/account/name') ->desc('Update name') ->groups(['api', 'account']) ->label('event', 'users.[userId].update.name') @@ -2635,7 +2752,7 @@ App::patch('/v1/account/name') $response->dynamic($user, Response::MODEL_ACCOUNT); }); -App::patch('/v1/account/password') +Http::patch('/v1/account/password') ->desc('Update password') ->groups(['api', 'account']) ->label('event', 'users.[userId].update.password') @@ -2705,99 +2822,7 @@ App::patch('/v1/account/password') $response->dynamic($user, Response::MODEL_ACCOUNT); }); -App::patch('/v1/account/email') - ->desc('Update email') - ->groups(['api', 'account']) - ->label('event', 'users.[userId].update.email') - ->label('scope', 'account') - ->label('audits.event', 'user.update') - ->label('audits.resource', 'user/{response.$id}') - ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) - ->label('sdk.namespace', 'account') - ->label('sdk.method', 'updateEmail') - ->label('sdk.description', '/docs/references/account/update-email.md') - ->label('sdk.response.code', Response::STATUS_CODE_OK) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_USER) - ->label('sdk.offline.model', '/account') - ->label('sdk.offline.key', 'current') - ->param('email', '', new Email(), 'User email.') - ->param('password', '', new Password(), 'User password. Must be at least 8 chars.') - ->inject('requestTimestamp') - ->inject('response') - ->inject('user') - ->inject('dbForProject') - ->inject('queueForEvents') - ->inject('project') - ->inject('hooks') - ->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks) { - // passwordUpdate will be empty if the user has never set a password - $passwordUpdate = $user->getAttribute('passwordUpdate'); - - if ( - !empty($passwordUpdate) && - !Auth::passwordVerify($password, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions')) - ) { // Double check user password - throw new Exception(Exception::USER_INVALID_CREDENTIALS); - } - - $hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, false]); - - $oldEmail = $user->getAttribute('email'); - - $email = \strtolower($email); - - // Makes sure this email is not already used in another identity - $identityWithMatchingEmail = $dbForProject->findOne('identities', [ - Query::equal('providerEmail', [$email]), - Query::notEqual('userInternalId', $user->getInternalId()), - ]); - if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { - throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */ - } - - $user - ->setAttribute('email', $email) - ->setAttribute('emailVerification', false) // After this user needs to confirm mail again - ; - - if (empty($passwordUpdate)) { - $user - ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)) - ->setAttribute('hash', Auth::DEFAULT_ALGO) - ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS) - ->setAttribute('passwordUpdate', DateTime::now()); - } - - $target = Authorization::skip(fn () => $dbForProject->findOne('targets', [ - Query::equal('identifier', [$email]), - ])); - - if ($target instanceof Document && !$target->isEmpty()) { - throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); - } - - try { - $user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user)); - /** - * @var Document $oldTarget - */ - $oldTarget = $user->find('identifier', $oldEmail, 'targets'); - - if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) { - Authorization::skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email))); - } - $dbForProject->purgeCachedDocument('users', $user->getId()); - } catch (Duplicate) { - throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */ - } - - $queueForEvents->setParam('userId', $user->getId()); - - $response->dynamic($user, Response::MODEL_ACCOUNT); - }); - -App::patch('/v1/account/phone') +Http::patch('/v1/account/phone') ->desc('Update phone') ->groups(['api', 'account']) ->label('event', 'users.[userId].update.phone') @@ -2822,7 +2847,8 @@ App::patch('/v1/account/phone') ->inject('queueForEvents') ->inject('project') ->inject('hooks') - ->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks) { + ->inject('authorization') + ->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, Authorization $authorization) { // passwordUpdate will be empty if the user has never set a password $passwordUpdate = $user->getAttribute('passwordUpdate'); @@ -2835,7 +2861,7 @@ App::patch('/v1/account/phone') $hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, false]); - $target = Authorization::skip(fn () => $dbForProject->findOne('targets', [ + $target = $authorization->skip(fn () => $dbForProject->findOne('targets', [ Query::equal('identifier', [$phone]), ])); @@ -2866,7 +2892,7 @@ App::patch('/v1/account/phone') $oldTarget = $user->find('identifier', $oldPhone, 'targets'); if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) { - Authorization::skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone))); + $authorization->skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone))); } $dbForProject->purgeCachedDocument('users', $user->getId()); } catch (Duplicate $th) { @@ -2878,7 +2904,7 @@ App::patch('/v1/account/phone') $response->dynamic($user, Response::MODEL_ACCOUNT); }); -App::patch('/v1/account/prefs') +Http::patch('/v1/account/prefs') ->desc('Update preferences') ->groups(['api', 'account']) ->label('event', 'users.[userId].update.prefs') @@ -2911,7 +2937,7 @@ App::patch('/v1/account/prefs') $response->dynamic($user, Response::MODEL_ACCOUNT); }); -App::patch('/v1/account/status') +Http::patch('/v1/account/status') ->desc('Update status') ->groups(['api', 'account']) ->label('event', 'users.[userId].update.status') @@ -2931,7 +2957,8 @@ App::patch('/v1/account/status') ->inject('user') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) { + ->inject('authentication') + ->action(function (?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authentication $authentication) { $user->setAttribute('status', false); @@ -2947,14 +2974,14 @@ App::patch('/v1/account/status') $protocol = $request->getProtocol(); $response - ->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie($authentication->getCookieName() . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie($authentication->getCookieName(), '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ; $response->dynamic($user, Response::MODEL_ACCOUNT); }); -App::post('/v1/account/recovery') +Http::post('/v1/account/recovery') ->desc('Create password recovery') ->groups(['api', 'account']) ->label('scope', 'sessions.write') @@ -2981,14 +3008,15 @@ App::post('/v1/account/recovery') ->inject('locale') ->inject('queueForMails') ->inject('queueForEvents') - ->action(function (string $email, string $url, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Mail $queueForMails, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $email, string $url, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Mail $queueForMails, Event $queueForEvents, Authorization $authorization) { if (empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled'); } $url = htmlentities($url); - $roles = Authorization::getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -3022,7 +3050,7 @@ App::post('/v1/account/recovery') 'ip' => $request->getIP(), ]); - Authorization::setRole(Role::user($profile->getId())->toString()); + $authorization->addRole(Role::user($profile->getId())->toString()); $recovery = $dbForProject->createDocument('tokens', $recovery ->setAttribute('$permissions', [ @@ -3044,7 +3072,7 @@ App::post('/v1/account/recovery') $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl'); $message - ->setParam('{{body}}', $body, escapeHtml: false) + ->setParam('{{body}}', $body, escape: false) ->setParam('{{hello}}', $locale->getText("emails.recovery.hello")) ->setParam('{{footer}}', $locale->getText("emails.recovery.footer")) ->setParam('{{thanks}}', $locale->getText("emails.recovery.thanks")) @@ -3134,7 +3162,7 @@ App::post('/v1/account/recovery') ->dynamic($recovery, Response::MODEL_TOKEN); }); -App::put('/v1/account/recovery') +Http::put('/v1/account/recovery') ->desc('Create password recovery (confirmation)') ->groups(['api', 'account']) ->label('scope', 'sessions.write') @@ -3160,7 +3188,8 @@ App::put('/v1/account/recovery') ->inject('project') ->inject('queueForEvents') ->inject('hooks') - ->action(function (string $userId, string $secret, string $password, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks) { + ->inject('authorization') + ->action(function (string $userId, string $secret, string $password, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks, Authorization $authorization) { $profile = $dbForProject->getDocument('users', $userId); if ($profile->isEmpty()) { @@ -3174,7 +3203,7 @@ App::put('/v1/account/recovery') throw new Exception(Exception::USER_INVALID_TOKEN); } - Authorization::setRole(Role::user($profile->getId())->toString()); + $authorization->addRole(Role::user($profile->getId())->toString()); $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); @@ -3219,7 +3248,7 @@ App::put('/v1/account/recovery') $response->dynamic($recoveryDocument, Response::MODEL_TOKEN); }); -App::post('/v1/account/verification') +Http::post('/v1/account/verification') ->desc('Create email verification') ->groups(['api', 'account']) ->label('scope', 'account') @@ -3244,7 +3273,8 @@ App::post('/v1/account/verification') ->inject('locale') ->inject('queueForEvents') ->inject('queueForMails') - ->action(function (string $url, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails) { + ->inject('authorization') + ->action(function (string $url, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, Authorization $authorization) { if (empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled'); @@ -3255,7 +3285,7 @@ App::post('/v1/account/verification') throw new Exception(Exception::USER_EMAIL_ALREADY_VERIFIED); } - $roles = Authorization::getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); $verificationSecret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_VERIFICATION); @@ -3272,7 +3302,7 @@ App::post('/v1/account/verification') 'ip' => $request->getIP(), ]); - Authorization::setRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); $verification = $dbForProject->createDocument('tokens', $verification ->setAttribute('$permissions', [ @@ -3294,7 +3324,7 @@ App::post('/v1/account/verification') $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl'); $message - ->setParam('{{body}}', $body, escapeHtml: false) + ->setParam('{{body}}', $body, escape: false) ->setParam('{{hello}}', $locale->getText("emails.verification.hello")) ->setParam('{{footer}}', $locale->getText("emails.verification.footer")) ->setParam('{{thanks}}', $locale->getText("emails.verification.thanks")) @@ -3384,7 +3414,7 @@ App::post('/v1/account/verification') ->dynamic($verification, Response::MODEL_TOKEN); }); -App::put('/v1/account/verification') +Http::put('/v1/account/verification') ->desc('Create email verification (confirmation)') ->groups(['api', 'account']) ->label('scope', 'public') @@ -3406,9 +3436,10 @@ App::put('/v1/account/verification') ->inject('user') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { - $profile = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId)); + $profile = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId)); if ($profile->isEmpty()) { throw new Exception(Exception::USER_NOT_FOUND); @@ -3421,7 +3452,7 @@ App::put('/v1/account/verification') throw new Exception(Exception::USER_INVALID_TOKEN); } - Authorization::setRole(Role::user($profile->getId())->toString()); + $authorization->addRole(Role::user($profile->getId())->toString()); $profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('emailVerification', true)); @@ -3443,7 +3474,7 @@ App::put('/v1/account/verification') $response->dynamic($verification, Response::MODEL_TOKEN); }); -App::post('/v1/account/verification/phone') +Http::post('/v1/account/verification/phone') ->desc('Create phone verification') ->groups(['api', 'account', 'auth']) ->label('scope', 'account') @@ -3468,7 +3499,8 @@ App::post('/v1/account/verification/phone') ->inject('queueForMessaging') ->inject('project') ->inject('locale') - ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale) { + ->inject('authorization') + ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale, Authorization $authorization) { if (empty(System::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); } @@ -3482,7 +3514,7 @@ App::post('/v1/account/verification/phone') throw new Exception(Exception::USER_PHONE_ALREADY_VERIFIED); } - $roles = Authorization::getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -3511,7 +3543,7 @@ App::post('/v1/account/verification/phone') 'ip' => $request->getIP(), ]); - Authorization::setRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); $verification = $dbForProject->createDocument('tokens', $verification ->setAttribute('$permissions', [ @@ -3571,7 +3603,7 @@ App::post('/v1/account/verification/phone') ->dynamic($verification, Response::MODEL_TOKEN); }); -App::put('/v1/account/verification/phone') +Http::put('/v1/account/verification/phone') ->desc('Update phone verification (confirmation)') ->groups(['api', 'account']) ->label('scope', 'public') @@ -3593,9 +3625,10 @@ App::put('/v1/account/verification/phone') ->inject('user') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { - $profile = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId)); + $profile = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId)); if ($profile->isEmpty()) { throw new Exception(Exception::USER_NOT_FOUND); @@ -3607,7 +3640,7 @@ App::put('/v1/account/verification/phone') throw new Exception(Exception::USER_INVALID_TOKEN); } - Authorization::setRole(Role::user($profile->getId())->toString()); + $authorization->addRole(Role::user($profile->getId())->toString()); $profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('phoneVerification', true)); @@ -3629,7 +3662,7 @@ App::put('/v1/account/verification/phone') $response->dynamic($verificationDocument, Response::MODEL_TOKEN); }); -App::patch('/v1/account/mfa') +Http::patch('/v1/account/mfa') ->desc('Update MFA') ->groups(['api', 'account']) ->label('event', 'users.[userId].update.mfa') @@ -3682,8 +3715,8 @@ App::patch('/v1/account/mfa') $response->dynamic($user, Response::MODEL_ACCOUNT); }); -App::get('/v1/account/mfa/factors') - ->desc('List factors') +Http::get('/v1/account/mfa/factors') + ->desc('List Factors') ->groups(['api', 'account', 'mfa']) ->label('scope', 'account') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) @@ -3714,8 +3747,8 @@ App::get('/v1/account/mfa/factors') $response->dynamic($factors, Response::MODEL_MFA_FACTORS); }); -App::post('/v1/account/mfa/authenticators/:type') - ->desc('Create authenticator') +Http::post('/v1/account/mfa/authenticators/:type') + ->desc('Create Authenticator') ->groups(['api', 'account']) ->label('event', 'users.[userId].update.mfa') ->label('scope', 'account') @@ -3786,8 +3819,8 @@ App::post('/v1/account/mfa/authenticators/:type') $response->dynamic($model, Response::MODEL_MFA_TYPE); }); -App::put('/v1/account/mfa/authenticators/:type') - ->desc('Verify authenticator') +Http::put('/v1/account/mfa/authenticators/:type') + ->desc('Verify Authenticator') ->groups(['api', 'account']) ->label('event', 'users.[userId].update.mfa') ->label('scope', 'account') @@ -3851,8 +3884,8 @@ App::put('/v1/account/mfa/authenticators/:type') $response->dynamic($user, Response::MODEL_ACCOUNT); }); -App::post('/v1/account/mfa/recovery-codes') - ->desc('Create MFA recovery codes') +Http::post('/v1/account/mfa/recovery-codes') + ->desc('Create MFA Recovery Codes') ->groups(['api', 'account']) ->label('event', 'users.[userId].update.mfa') ->label('scope', 'account') @@ -3893,8 +3926,8 @@ App::post('/v1/account/mfa/recovery-codes') $response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES); }); -App::patch('/v1/account/mfa/recovery-codes') - ->desc('Regenerate MFA recovery codes') +Http::patch('/v1/account/mfa/recovery-codes') + ->desc('Regenerate MFA Recovery Codes') ->groups(['api', 'account', 'mfaProtected']) ->label('event', 'users.[userId].update.mfa') ->label('scope', 'account') @@ -3934,8 +3967,8 @@ App::patch('/v1/account/mfa/recovery-codes') $response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES); }); -App::get('/v1/account/mfa/recovery-codes') - ->desc('Get MFA recovery codes') +Http::get('/v1/account/mfa/recovery-codes') + ->desc('Get MFA Recovery Codes') ->groups(['api', 'account', 'mfaProtected']) ->label('scope', 'account') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) @@ -3964,8 +3997,8 @@ App::get('/v1/account/mfa/recovery-codes') $response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES); }); -App::delete('/v1/account/mfa/authenticators/:type') - ->desc('Delete authenticator') +Http::delete('/v1/account/mfa/authenticators/:type') + ->desc('Delete Authenticator') ->groups(['api', 'account', 'mfaProtected']) ->label('event', 'users.[userId].delete.mfa') ->label('scope', 'account') @@ -4002,8 +4035,8 @@ App::delete('/v1/account/mfa/authenticators/:type') $response->noContent(); }); -App::post('/v1/account/mfa/challenge') - ->desc('Create MFA challenge') +Http::post('/v1/account/mfa/challenge') + ->desc('Create MFA Challenge') ->groups(['api', 'account', 'mfa']) ->label('scope', 'account') ->label('event', 'users.[userId].challenges.[challengeId].create') @@ -4190,8 +4223,8 @@ App::post('/v1/account/mfa/challenge') $response->dynamic($challenge, Response::MODEL_MFA_CHALLENGE); }); -App::put('/v1/account/mfa/challenge') - ->desc('Create MFA challenge (confirmation)') +Http::put('/v1/account/mfa/challenge') + ->desc('Create MFA Challenge (confirmation)') ->groups(['api', 'account', 'mfa']) ->label('scope', 'account') ->label('event', 'users.[userId].sessions.[sessionId].create') @@ -4277,7 +4310,7 @@ App::put('/v1/account/mfa/challenge') $response->dynamic($session, Response::MODEL_SESSION); }); -App::post('/v1/account/targets/push') +Http::post('/v1/account/targets/push') ->desc('Create push target') ->groups(['api', 'account']) ->label('scope', 'targets.write') @@ -4298,12 +4331,14 @@ App::post('/v1/account/targets/push') ->inject('request') ->inject('response') ->inject('dbForProject') - ->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject) { + ->inject('authorization') + ->inject('authentication') + ->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization, Authentication $authentication) { $targetId = $targetId == 'unique()' ? ID::unique() : $targetId; - $provider = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId)); + $provider = $authorization->skip(fn () => $dbForProject->getDocument('providers', $providerId)); - $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId)); + $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId)); if (!$target->isEmpty()) { throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); @@ -4314,7 +4349,7 @@ App::post('/v1/account/targets/push') $device = $detector->getDevice(); - $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret); + $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), $authentication->getSecret()); $session = $dbForProject->getDocument('sessions', $sessionId); try { @@ -4350,7 +4385,7 @@ App::post('/v1/account/targets/push') ->dynamic($target, Response::MODEL_TARGET); }); -App::put('/v1/account/targets/:targetId/push') +Http::put('/v1/account/targets/:targetId/push') ->desc('Update push target') ->groups(['api', 'account']) ->label('scope', 'targets.write') @@ -4370,9 +4405,10 @@ App::put('/v1/account/targets/:targetId/push') ->inject('request') ->inject('response') ->inject('dbForProject') - ->action(function (string $targetId, string $identifier, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject) { + ->inject('authorization') + ->action(function (string $targetId, string $identifier, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) { - $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId)); + $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId)); if ($target->isEmpty()) { throw new Exception(Exception::USER_TARGET_NOT_FOUND); @@ -4405,7 +4441,7 @@ App::put('/v1/account/targets/:targetId/push') ->dynamic($target, Response::MODEL_TARGET); }); -App::delete('/v1/account/targets/:targetId/push') +Http::delete('/v1/account/targets/:targetId/push') ->desc('Delete push target') ->groups(['api', 'account']) ->label('scope', 'targets.write') @@ -4425,8 +4461,9 @@ App::delete('/v1/account/targets/:targetId/push') ->inject('request') ->inject('response') ->inject('dbForProject') - ->action(function (string $targetId, Event $queueForEvents, Delete $queueForDeletes, Document $user, Request $request, Response $response, Database $dbForProject) { - $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId)); + ->inject('authorization') + ->action(function (string $targetId, Event $queueForEvents, Delete $queueForDeletes, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) { + $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId)); if ($target->isEmpty()) { throw new Exception(Exception::USER_TARGET_NOT_FOUND); @@ -4451,8 +4488,8 @@ App::delete('/v1/account/targets/:targetId/push') $response->noContent(); }); -App::get('/v1/account/identities') - ->desc('List identities') +Http::get('/v1/account/identities') + ->desc('List Identities') ->groups(['api', 'account']) ->label('scope', 'account') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) @@ -4507,7 +4544,7 @@ App::get('/v1/account/identities') ]), Response::MODEL_IDENTITY_LIST); }); -App::delete('/v1/account/identities/:identityId') +Http::delete('/v1/account/identities/:identityId') ->desc('Delete identity') ->groups(['api', 'account']) ->label('scope', 'account') diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index fcff3e4179..54ba096c5d 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -5,7 +5,7 @@ use Appwrite\URL\URL as URLParse; use Appwrite\Utopia\Response; use chillerlan\QRCode\QRCode; use chillerlan\QRCode\QROptions; -use Utopia\App; +use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\DateTime; @@ -14,15 +14,17 @@ use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\UID; use Utopia\Domains\Domain; use Utopia\Fetch\Client; +use Utopia\Http\Http; +use Utopia\Http\Validator\Boolean; +use Utopia\Http\Validator\HexColor; +use Utopia\Http\Validator\Range; +use Utopia\Http\Validator\Text; +use Utopia\Http\Validator\URL; +use Utopia\Http\Validator\WhiteList; use Utopia\Image\Image; +use Utopia\Logger\Log; use Utopia\Logger\Logger; use Utopia\System\System; -use Utopia\Validator\Boolean; -use Utopia\Validator\HexColor; -use Utopia\Validator\Range; -use Utopia\Validator\Text; -use Utopia\Validator\URL; -use Utopia\Validator\WhiteList; $avatarCallback = function (string $type, string $code, int $width, int $height, int $quality, Response $response) { @@ -61,9 +63,9 @@ $avatarCallback = function (string $type, string $code, int $width, int $height, unset($image); }; -$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForConsole, ?Logger $logger) { +$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForConsole, ?Logger $logger, Authorization $auth) { try { - $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); + $user = $auth->skip(fn () => $dbForConsole->getDocument('users', $userId)); $sessions = $user->getAttribute('sessions', []); @@ -114,7 +116,7 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro ->setAttribute('providerRefreshToken', $refreshToken) ->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry(''))); - Authorization::skip(fn () => $dbForProject->updateDocument('sessions', $gitHubSession->getId(), $gitHubSession)); + $auth->skip(fn () => $dbForProject->updateDocument('sessions', $gitHubSession->getId(), $gitHubSession)); $dbForProject->purgeCachedDocument('users', $user->getId()); } catch (Throwable $err) { @@ -122,7 +124,7 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro do { $previousAccessToken = $gitHubSession->getAttribute('providerAccessToken'); - $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); + $user = $auth->skip(fn () => $dbForConsole->getDocument('users', $userId)); $sessions = $user->getAttribute('sessions', []); $gitHubSession = new Document(); @@ -154,11 +156,42 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro 'id' => $githubId ]; } catch (Exception $error) { + if ($logger) { + $version = System::getEnv('_APP_VERSION', 'UNKNOWN'); + + $log = new Log(); + $log->setNamespace('console'); + $log->setServer(\gethostname()); + $log->setVersion($version); + $log->setType(Log::TYPE_ERROR); + $log->setMessage($error->getMessage()); + + $log->addTag('code', $error->getCode()); + $log->addTag('verboseType', get_class($error)); + + $log->addExtra('file', $error->getFile()); + $log->addExtra('line', $error->getLine()); + $log->addExtra('trace', $error->getTraceAsString()); + $log->addExtra('detailedTrace', $error->getTrace()); + + $log->setAction('avatarsGetGitHub'); + + $isProduction = System::getEnv('_APP_ENV', 'development') === 'production'; + + $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING); + + $responseCode = $logger->addLog($log); + Console::info('GitHub error log pushed with status code: ' . $responseCode); + } + + Console::warning("Failed: {$error->getMessage()}"); + Console::warning($error->getTraceAsString()); + return []; } }; -App::get('/v1/avatars/credit-cards/:code') +Http::get('/v1/avatars/credit-cards/:code') ->desc('Get credit card icon') ->groups(['api', 'avatars']) ->label('scope', 'avatars.read') @@ -178,7 +211,7 @@ App::get('/v1/avatars/credit-cards/:code') ->inject('response') ->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('credit-cards', $code, $width, $height, $quality, $response)); -App::get('/v1/avatars/browsers/:code') +Http::get('/v1/avatars/browsers/:code') ->desc('Get browser icon') ->groups(['api', 'avatars']) ->label('scope', 'avatars.read') @@ -198,7 +231,7 @@ App::get('/v1/avatars/browsers/:code') ->inject('response') ->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('browsers', $code, $width, $height, $quality, $response)); -App::get('/v1/avatars/flags/:code') +Http::get('/v1/avatars/flags/:code') ->desc('Get country flag') ->groups(['api', 'avatars']) ->label('scope', 'avatars.read') @@ -218,7 +251,7 @@ App::get('/v1/avatars/flags/:code') ->inject('response') ->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('flags', $code, $width, $height, $quality, $response)); -App::get('/v1/avatars/image') +Http::get('/v1/avatars/image') ->desc('Get image from URL') ->groups(['api', 'avatars']) ->label('scope', 'avatars.read') @@ -281,7 +314,7 @@ App::get('/v1/avatars/image') unset($image); }); -App::get('/v1/avatars/favicon') +Http::get('/v1/avatars/favicon') ->desc('Get favicon') ->groups(['api', 'avatars']) ->label('scope', 'avatars.read') @@ -426,7 +459,7 @@ App::get('/v1/avatars/favicon') unset($image); }); -App::get('/v1/avatars/qr') +Http::get('/v1/avatars/qr') ->desc('Get QR code') ->groups(['api', 'avatars']) ->label('scope', 'avatars.read') @@ -466,7 +499,7 @@ App::get('/v1/avatars/qr') ->send($image->output('png', 9)); }); -App::get('/v1/avatars/initials') +Http::get('/v1/avatars/initials') ->desc('Get user initials') ->groups(['api', 'avatars']) ->label('scope', 'avatars.read') @@ -549,8 +582,8 @@ App::get('/v1/avatars/initials') ->file($image->getImageBlob()); }); -App::get('/v1/cards/cloud') - ->desc('Get front Of Cloud Card') +Http::get('/v1/cards/cloud') + ->desc('Get Front Of Cloud Card') ->groups(['api', 'avatars']) ->label('scope', 'avatars.read') ->label('cache', true) @@ -571,8 +604,9 @@ App::get('/v1/cards/cloud') ->inject('contributors') ->inject('employees') ->inject('logger') - ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) { - $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); + ->inject('auth') + ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $auth) use ($getUserGitHub) { + $user = $auth->skip(fn () => $dbForConsole->getDocument('users', $userId)); if ($user->isEmpty() && empty($mock)) { throw new Exception(Exception::USER_NOT_FOUND); @@ -583,7 +617,7 @@ App::get('/v1/cards/cloud') $email = $user->getAttribute('email', ''); $createdAt = new \DateTime($user->getCreatedAt()); - $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger); + $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger, $auth); $githubName = $gitHub['name'] ?? ''; $githubId = $gitHub['id'] ?? ''; @@ -756,8 +790,8 @@ App::get('/v1/cards/cloud') ->file($baseImage->getImageBlob()); }); -App::get('/v1/cards/cloud-back') - ->desc('Get back Of Cloud Card') +Http::get('/v1/cards/cloud-back') + ->desc('Get Back Of Cloud Card') ->groups(['api', 'avatars']) ->label('scope', 'avatars.read') ->label('cache', true) @@ -778,8 +812,9 @@ App::get('/v1/cards/cloud-back') ->inject('contributors') ->inject('employees') ->inject('logger') - ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) { - $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); + ->inject('auth') + ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $auth) use ($getUserGitHub) { + $user = $auth->skip(fn () => $dbForConsole->getDocument('users', $userId)); if ($user->isEmpty() && empty($mock)) { throw new Exception(Exception::USER_NOT_FOUND); @@ -789,7 +824,7 @@ App::get('/v1/cards/cloud-back') $userId = $user->getId(); $email = $user->getAttribute('email', ''); - $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger); + $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger, $auth); $githubId = $gitHub['id'] ?? ''; $isHero = \array_key_exists($email, $heroes); @@ -834,8 +869,8 @@ App::get('/v1/cards/cloud-back') ->file($baseImage->getImageBlob()); }); -App::get('/v1/cards/cloud-og') - ->desc('Get OG image From Cloud Card') +Http::get('/v1/cards/cloud-og') + ->desc('Get OG Image From Cloud Card') ->groups(['api', 'avatars']) ->label('scope', 'avatars.read') ->label('cache', true) @@ -856,8 +891,9 @@ App::get('/v1/cards/cloud-og') ->inject('contributors') ->inject('employees') ->inject('logger') - ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) { - $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); + ->inject('auth') + ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $auth) use ($getUserGitHub) { + $user = $auth->skip(fn () => $dbForConsole->getDocument('users', $userId)); if ($user->isEmpty() && empty($mock)) { throw new Exception(Exception::USER_NOT_FOUND); @@ -872,7 +908,7 @@ App::get('/v1/cards/cloud-og') $email = $user->getAttribute('email', ''); $createdAt = new \DateTime($user->getCreatedAt()); - $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger); + $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger, $auth); $githubName = $gitHub['name'] ?? ''; $githubId = $gitHub['id'] ?? ''; diff --git a/app/controllers/api/console.php b/app/controllers/api/console.php index eeb823a3d3..2a393497ae 100644 --- a/app/controllers/api/console.php +++ b/app/controllers/api/console.php @@ -2,12 +2,12 @@ use Appwrite\Extend\Exception; use Appwrite\Utopia\Response; -use Utopia\App; use Utopia\Database\Document; +use Utopia\Http\Http; +use Utopia\Http\Validator\Text; use Utopia\System\System; -use Utopia\Validator\Text; -App::init() +Http::init() ->groups(['console']) ->inject('project') ->action(function (Document $project) { @@ -17,7 +17,7 @@ App::init() }); -App::get('/v1/console/variables') +Http::get('/v1/console/variables') ->desc('Get variables') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -56,8 +56,8 @@ App::get('/v1/console/variables') $response->dynamic($variables, Response::MODEL_CONSOLE_VARIABLES); }); -App::post('/v1/console/assistant') - ->desc('Ask query') +Http::post('/v1/console/assistant') + ->desc('Ask Query') ->groups(['api', 'assistant']) ->label('scope', 'assistant.read') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index b76548b725..4bc074fb8f 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -16,7 +16,6 @@ use Appwrite\Utopia\Database\Validator\Queries\Indexes; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use MaxMind\Db\Reader; -use Utopia\App; use Utopia\Audit\Audit; use Utopia\Config\Config; use Utopia\Database\Database; @@ -35,6 +34,7 @@ use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; +use Utopia\Database\Validator\Authorization\Input; use Utopia\Database\Validator\Datetime as DatetimeValidator; use Utopia\Database\Validator\Index as IndexValidator; use Utopia\Database\Validator\Key; @@ -44,18 +44,19 @@ use Utopia\Database\Validator\Query\Limit; use Utopia\Database\Validator\Query\Offset; use Utopia\Database\Validator\Structure; use Utopia\Database\Validator\UID; +use Utopia\Http\Http; +use Utopia\Http\Validator\ArrayList; +use Utopia\Http\Validator\Boolean; +use Utopia\Http\Validator\FloatValidator; +use Utopia\Http\Validator\Integer; +use Utopia\Http\Validator\IP; +use Utopia\Http\Validator\JSON; +use Utopia\Http\Validator\Nullable; +use Utopia\Http\Validator\Range; +use Utopia\Http\Validator\Text; +use Utopia\Http\Validator\URL; +use Utopia\Http\Validator\WhiteList; use Utopia\Locale\Locale; -use Utopia\Validator\ArrayList; -use Utopia\Validator\Boolean; -use Utopia\Validator\FloatValidator; -use Utopia\Validator\Integer; -use Utopia\Validator\IP; -use Utopia\Validator\JSON; -use Utopia\Validator\Nullable; -use Utopia\Validator\Range; -use Utopia\Validator\Text; -use Utopia\Validator\URL; -use Utopia\Validator\WhiteList; /** * * Create attribute of varying type @@ -77,7 +78,7 @@ use Utopia\Validator\WhiteList; * @throws ConflictException * @throws Exception */ -function createAttribute(string $databaseId, string $collectionId, Document $attribute, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): Document +function createAttribute(string $databaseId, string $collectionId, Document $attribute, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): Document { $key = $attribute->getAttribute('key'); $type = $attribute->getAttribute('type', ''); @@ -91,7 +92,7 @@ function createAttribute(string $databaseId, string $collectionId, Document $att $default = $attribute->getAttribute('default'); $options = $attribute->getAttribute('options', []); - $db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($db->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -226,6 +227,7 @@ function createAttribute(string $databaseId, string $collectionId, Document $att } function updateAttribute( + Authorization $authorization, string $databaseId, string $collectionId, string $key, @@ -239,10 +241,10 @@ function updateAttribute( int|float $min = null, int|float $max = null, array $elements = null, - array $options = [], string $newKey = null, + array $options = [], ): Document { - $db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($db->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -421,19 +423,19 @@ function updateAttribute( return $attribute; } -App::init() +Http::init() ->groups(['api', 'database']) ->inject('request') ->inject('dbForProject') ->action(function (Request $request, Database $dbForProject) { $timeout = \intval($request->getHeader('x-appwrite-timeout')); - if (!empty($timeout) && App::isDevelopment()) { + if (!empty($timeout) && Http::isDevelopment()) { $dbForProject->setTimeout($timeout); } }); -App::post('/v1/databases') +Http::post('/v1/databases') ->desc('Create database') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].create') @@ -511,7 +513,7 @@ App::post('/v1/databases') ->dynamic($database, Response::MODEL_DATABASE); }); -App::get('/v1/databases') +Http::get('/v1/databases') ->desc('List databases') ->groups(['api', 'database']) ->label('scope', 'databases.read') @@ -564,7 +566,7 @@ App::get('/v1/databases') ]), Response::MODEL_DATABASE_LIST); }); -App::get('/v1/databases/:databaseId') +Http::get('/v1/databases/:databaseId') ->desc('Get database') ->groups(['api', 'database']) ->label('scope', 'databases.read') @@ -589,7 +591,7 @@ App::get('/v1/databases/:databaseId') $response->dynamic($database, Response::MODEL_DATABASE); }); -App::get('/v1/databases/:databaseId/logs') +Http::get('/v1/databases/:databaseId/logs') ->desc('List database logs') ->groups(['api', 'database']) ->label('scope', 'databases.read') @@ -606,7 +608,8 @@ App::get('/v1/databases/:databaseId/logs') ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->action(function (string $databaseId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { + ->inject('authorization') + ->action(function (string $databaseId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) { $database = $dbForProject->getDocument('databases', $databaseId); @@ -680,7 +683,7 @@ App::get('/v1/databases/:databaseId/logs') }); -App::put('/v1/databases/:databaseId') +Http::put('/v1/databases/:databaseId') ->desc('Update database') ->groups(['api', 'database', 'schema']) ->label('scope', 'databases.write') @@ -718,7 +721,7 @@ App::put('/v1/databases/:databaseId') $response->dynamic($database, Response::MODEL_DATABASE); }); -App::delete('/v1/databases/:databaseId') +Http::delete('/v1/databases/:databaseId') ->desc('Delete database') ->groups(['api', 'database', 'schema']) ->label('scope', 'databases.write') @@ -766,7 +769,7 @@ App::delete('/v1/databases/:databaseId') $response->noContent(); }); -App::post('/v1/databases/:databaseId/collections') +Http::post('/v1/databases/:databaseId/collections') ->desc('Create collection') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].create') @@ -790,9 +793,10 @@ App::post('/v1/databases/:databaseId/collections') ->inject('dbForProject') ->inject('mode') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, Response $response, Database $dbForProject, string $mode, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, Response $response, Database $dbForProject, string $mode, Event $queueForEvents, Authorization $authorization) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -833,7 +837,7 @@ App::post('/v1/databases/:databaseId/collections') ->dynamic($collection, Response::MODEL_COLLECTION); }); -App::get('/v1/databases/:databaseId/collections') +Http::get('/v1/databases/:databaseId/collections') ->alias('/v1/database/collections', ['databaseId' => 'default']) ->desc('List collections') ->groups(['api', 'database']) @@ -851,9 +855,10 @@ App::get('/v1/databases/:databaseId/collections') ->inject('response') ->inject('dbForProject') ->inject('mode') - ->action(function (string $databaseId, array $queries, string $search, Response $response, Database $dbForProject, string $mode) { + ->inject('authorization') + ->action(function (string $databaseId, array $queries, string $search, Response $response, Database $dbForProject, string $mode, Authorization $authorization) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -896,7 +901,7 @@ App::get('/v1/databases/:databaseId/collections') ]), Response::MODEL_COLLECTION_LIST); }); -App::get('/v1/databases/:databaseId/collections/:collectionId') +Http::get('/v1/databases/:databaseId/collections/:collectionId') ->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default']) ->desc('Get collection') ->groups(['api', 'database']) @@ -913,9 +918,10 @@ App::get('/v1/databases/:databaseId/collections/:collectionId') ->inject('response') ->inject('dbForProject') ->inject('mode') - ->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, string $mode) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, string $mode, Authorization $authorization) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -930,7 +936,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId') $response->dynamic($collection, Response::MODEL_COLLECTION); }); -App::get('/v1/databases/:databaseId/collections/:collectionId/logs') +Http::get('/v1/databases/:databaseId/collections/:collectionId/logs') ->alias('/v1/database/collections/:collectionId/logs', ['databaseId' => 'default']) ->desc('List collection logs') ->groups(['api', 'database']) @@ -949,9 +955,10 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs') ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -1030,7 +1037,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs') }); -App::put('/v1/databases/:databaseId/collections/:collectionId') +Http::put('/v1/databases/:databaseId/collections/:collectionId') ->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default']) ->desc('Update collection') ->groups(['api', 'database', 'schema']) @@ -1055,9 +1062,10 @@ App::put('/v1/databases/:databaseId/collections/:collectionId') ->inject('dbForProject') ->inject('mode') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, Response $response, Database $dbForProject, string $mode, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, Response $response, Database $dbForProject, string $mode, Event $queueForEvents, Authorization $authorization) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -1093,7 +1101,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId') $response->dynamic($collection, Response::MODEL_COLLECTION); }); -App::delete('/v1/databases/:databaseId/collections/:collectionId') +Http::delete('/v1/databases/:databaseId/collections/:collectionId') ->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default']) ->desc('Delete collection') ->groups(['api', 'database', 'schema']) @@ -1114,9 +1122,10 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId') ->inject('queueForDatabase') ->inject('queueForEvents') ->inject('mode') - ->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, string $mode) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, string $mode, Authorization $authorization) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -1148,7 +1157,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId') $response->noContent(); }); -App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string') +Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string') ->alias('/v1/database/collections/:collectionId/attributes/string', ['databaseId' => 'default']) ->desc('Create string attribute') ->groups(['api', 'database', 'schema']) @@ -1175,7 +1184,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $key, ?int $size, ?bool $required, ?string $default, bool $array, bool $encrypt, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, ?int $size, ?bool $required, ?string $default, bool $array, bool $encrypt, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { // Ensure attribute default is within required size $validator = new Text($size, 0); @@ -1197,7 +1207,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string 'default' => $default, 'array' => $array, 'filters' => $filters, - ]), $response, $dbForProject, $queueForDatabase, $queueForEvents); + ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization); $response @@ -1205,7 +1215,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string ->dynamic($attribute, Response::MODEL_ATTRIBUTE_STRING); }); -App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email') +Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email') ->alias('/v1/database/collections/:collectionId/attributes/email', ['databaseId' => 'default']) ->desc('Create email attribute') ->groups(['api', 'database', 'schema']) @@ -1230,7 +1240,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email' ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, @@ -1240,14 +1251,14 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email' 'default' => $default, 'array' => $array, 'format' => APP_DATABASE_ATTRIBUTE_EMAIL, - ]), $response, $dbForProject, $queueForDatabase, $queueForEvents); + ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization); $response ->setStatusCode(Response::STATUS_CODE_ACCEPTED) ->dynamic($attribute, Response::MODEL_ATTRIBUTE_EMAIL); }); -App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum') +Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum') ->alias('/v1/database/collections/:collectionId/attributes/enum', ['databaseId' => 'default']) ->desc('Create enum attribute') ->groups(['api', 'database', 'schema']) @@ -1273,7 +1284,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum') ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $key, array $elements, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, array $elements, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { if (!is_null($default) && !in_array($default, $elements)) { throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Default value not found in elements'); } @@ -1287,14 +1299,14 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum') 'array' => $array, 'format' => APP_DATABASE_ATTRIBUTE_ENUM, 'formatOptions' => ['elements' => $elements], - ]), $response, $dbForProject, $queueForDatabase, $queueForEvents); + ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization); $response ->setStatusCode(Response::STATUS_CODE_ACCEPTED) ->dynamic($attribute, Response::MODEL_ATTRIBUTE_ENUM); }); -App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') +Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') ->alias('/v1/database/collections/:collectionId/attributes/ip', ['databaseId' => 'default']) ->desc('Create IP address attribute') ->groups(['api', 'database', 'schema']) @@ -1319,7 +1331,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, @@ -1329,14 +1342,14 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') 'default' => $default, 'array' => $array, 'format' => APP_DATABASE_ATTRIBUTE_IP, - ]), $response, $dbForProject, $queueForDatabase, $queueForEvents); + ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization); $response ->setStatusCode(Response::STATUS_CODE_ACCEPTED) ->dynamic($attribute, Response::MODEL_ATTRIBUTE_IP); }); -App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') +Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') ->alias('/v1/database/collections/:collectionId/attributes/url', ['databaseId' => 'default']) ->desc('Create URL attribute') ->groups(['api', 'database', 'schema']) @@ -1361,7 +1374,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, @@ -1371,14 +1385,14 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') 'default' => $default, 'array' => $array, 'format' => APP_DATABASE_ATTRIBUTE_URL, - ]), $response, $dbForProject, $queueForDatabase, $queueForEvents); + ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization); $response ->setStatusCode(Response::STATUS_CODE_ACCEPTED) ->dynamic($attribute, Response::MODEL_ATTRIBUTE_URL); }); -App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/integer') +Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/integer') ->alias('/v1/database/collections/:collectionId/attributes/integer', ['databaseId' => 'default']) ->desc('Create integer attribute') ->groups(['api', 'database', 'schema']) @@ -1405,7 +1419,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { // Ensure attribute default is within range $min = (is_null($min)) ? PHP_INT_MIN : \intval($min); @@ -1435,7 +1450,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege 'min' => $min, 'max' => $max, ], - ]), $response, $dbForProject, $queueForDatabase, $queueForEvents); + ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization); $formatOptions = $attribute->getAttribute('formatOptions', []); @@ -1449,7 +1464,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege ->dynamic($attribute, Response::MODEL_ATTRIBUTE_INTEGER); }); -App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float') +Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float') ->alias('/v1/database/collections/:collectionId/attributes/float', ['databaseId' => 'default']) ->desc('Create float attribute') ->groups(['api', 'database', 'schema']) @@ -1476,7 +1491,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float' ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { // Ensure attribute default is within range $min = (is_null($min)) ? -PHP_FLOAT_MAX : \floatval($min); @@ -1509,7 +1525,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float' 'min' => $min, 'max' => $max, ], - ]), $response, $dbForProject, $queueForDatabase, $queueForEvents); + ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization); $formatOptions = $attribute->getAttribute('formatOptions', []); @@ -1523,7 +1539,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float' ->dynamic($attribute, Response::MODEL_ATTRIBUTE_FLOAT); }); -App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean') +Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean') ->alias('/v1/database/collections/:collectionId/attributes/boolean', ['databaseId' => 'default']) ->desc('Create boolean attribute') ->groups(['api', 'database', 'schema']) @@ -1548,7 +1564,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolea ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, @@ -1557,14 +1574,14 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolea 'required' => $required, 'default' => $default, 'array' => $array, - ]), $response, $dbForProject, $queueForDatabase, $queueForEvents); + ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization); $response ->setStatusCode(Response::STATUS_CODE_ACCEPTED) ->dynamic($attribute, Response::MODEL_ATTRIBUTE_BOOLEAN); }); -App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/datetime') +Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/datetime') ->alias('/v1/database/collections/:collectionId/attributes/datetime', ['databaseId' => 'default']) ->desc('Create datetime attribute') ->groups(['api', 'database']) @@ -1589,7 +1606,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { $filters[] = 'datetime'; @@ -1601,14 +1619,14 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti 'default' => $default, 'array' => $array, 'filters' => $filters, - ]), $response, $dbForProject, $queueForDatabase, $queueForEvents); + ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization); $response ->setStatusCode(Response::STATUS_CODE_ACCEPTED) ->dynamic($attribute, Response::MODEL_ATTRIBUTE_DATETIME); }); -App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relationship') +Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relationship') ->alias('/v1/database/collections/:collectionId/attributes/relationship', ['databaseId' => 'default']) ->desc('Create relationship attribute') ->groups(['api', 'database']) @@ -1635,6 +1653,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') + ->inject('authorization') ->action(function ( string $databaseId, string $collectionId, @@ -1647,12 +1666,13 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati Response $response, Database $dbForProject, EventDatabase $queueForDatabase, - Event $queueForEvents + Event $queueForEvents, + Authorization $authorization ) { $key ??= $relatedCollectionId; $twoWayKey ??= $collectionId; - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -1722,7 +1742,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati $response, $dbForProject, $queueForDatabase, - $queueForEvents + $queueForEvents, + $authorization ); $options = $attribute->getAttribute('options', []); @@ -1736,7 +1757,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati ->dynamic($attribute, Response::MODEL_ATTRIBUTE_RELATIONSHIP); }); -App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') +Http::get('/v1/databases/:databaseId/collections/:collectionId/attributes') ->alias('/v1/database/collections/:collectionId/attributes', ['databaseId' => 'default']) ->desc('List attributes') ->groups(['api', 'database']) @@ -1753,9 +1774,10 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') ->param('queries', [], new Attributes(), '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(', ', Attributes::ALLOWED_ATTRIBUTES), true) ->inject('response') ->inject('dbForProject') - ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, Authorization $authorization) { /** @var Document $database */ - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -1789,7 +1811,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') if ($cursor) { $attributeId = $cursor->getValue(); - $cursorDocument = Authorization::skip(fn () => $dbForProject->find('attributes', [ + $cursorDocument = $authorization->skip(fn () => $dbForProject->find('attributes', [ Query::equal('collectionInternalId', [$collection->getInternalId()]), Query::equal('databaseInternalId', [$database->getInternalId()]), Query::equal('key', [$attributeId]), @@ -1814,7 +1836,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') ]), Response::MODEL_ATTRIBUTE_LIST); }); -App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key') +Http::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key') ->alias('/v1/database/collections/:collectionId/attributes/:key', ['databaseId' => 'default']) ->desc('Get attribute') ->groups(['api', 'database']) @@ -1841,9 +1863,10 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key') ->param('key', '', new Key(), 'Attribute Key.') ->inject('response') ->inject('dbForProject') - ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, Authorization $authorization) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -1889,7 +1912,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key') $response->dynamic($attribute, $model); }); -App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/string/:key') +Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/string/:key') ->desc('Update string attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -1912,9 +1935,11 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/strin ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?int $size, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?int $size, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { $attribute = updateAttribute( + authorization: $authorization, databaseId: $databaseId, collectionId: $collectionId, key: $key, @@ -1932,7 +1957,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/strin ->dynamic($attribute, Response::MODEL_ATTRIBUTE_STRING); }); -App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/email/:key') +Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/email/:key') ->desc('Update email attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -1954,8 +1979,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/email ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { $attribute = updateAttribute( + authorization: $authorization, databaseId: $databaseId, collectionId: $collectionId, key: $key, @@ -1973,7 +2000,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/email ->dynamic($attribute, Response::MODEL_ATTRIBUTE_EMAIL); }); -App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/enum/:key') +Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/enum/:key') ->desc('Update enum attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -1996,8 +2023,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/enum/ ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $key, ?array $elements, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, ?array $elements, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { $attribute = updateAttribute( + authorization: $authorization, databaseId: $databaseId, collectionId: $collectionId, key: $key, @@ -2016,7 +2045,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/enum/ ->dynamic($attribute, Response::MODEL_ATTRIBUTE_ENUM); }); -App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/ip/:key') +Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/ip/:key') ->desc('Update IP address attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -2038,8 +2067,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/ip/:k ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { $attribute = updateAttribute( + authorization: $authorization, databaseId: $databaseId, collectionId: $collectionId, key: $key, @@ -2057,7 +2088,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/ip/:k ->dynamic($attribute, Response::MODEL_ATTRIBUTE_IP); }); -App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/url/:key') +Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/url/:key') ->desc('Update URL attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -2079,8 +2110,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/url/: ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { $attribute = updateAttribute( + authorization: $authorization, databaseId: $databaseId, collectionId: $collectionId, key: $key, @@ -2098,7 +2131,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/url/: ->dynamic($attribute, Response::MODEL_ATTRIBUTE_URL); }); -App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/integer/:key') +Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/integer/:key') ->desc('Update integer attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -2122,8 +2155,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/integ ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { $attribute = updateAttribute( + authorization: $authorization, databaseId: $databaseId, collectionId: $collectionId, key: $key, @@ -2149,7 +2184,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/integ ->dynamic($attribute, Response::MODEL_ATTRIBUTE_INTEGER); }); -App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/float/:key') +Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/float/:key') ->desc('Update float attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -2173,8 +2208,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/float ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { $attribute = updateAttribute( + authorization: $authorization, databaseId: $databaseId, collectionId: $collectionId, key: $key, @@ -2200,7 +2237,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/float ->dynamic($attribute, Response::MODEL_ATTRIBUTE_FLOAT); }); -App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean/:key') +Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean/:key') ->desc('Update boolean attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -2222,8 +2259,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/boole ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { $attribute = updateAttribute( + authorization: $authorization, databaseId: $databaseId, collectionId: $collectionId, key: $key, @@ -2240,7 +2279,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/boole ->dynamic($attribute, Response::MODEL_ATTRIBUTE_BOOLEAN); }); -App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datetime/:key') +Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datetime/:key') ->desc('Update dateTime attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -2262,8 +2301,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datet ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { $attribute = updateAttribute( + authorization: $authorization, databaseId: $databaseId, collectionId: $collectionId, key: $key, @@ -2280,7 +2321,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datet ->dynamic($attribute, Response::MODEL_ATTRIBUTE_DATETIME); }); -App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/relationship') +Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/relationship') ->desc('Update relationship attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -2301,6 +2342,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/ ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') + ->inject('authorization') ->action(function ( string $databaseId, string $collectionId, @@ -2309,9 +2351,11 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/ ?string $newKey, Response $response, Database $dbForProject, - Event $queueForEvents + Event $queueForEvents, + Authorization $authorization ) { $attribute = updateAttribute( + $authorization, $databaseId, $collectionId, $key, @@ -2336,7 +2380,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/ ->dynamic($attribute, Response::MODEL_ATTRIBUTE_RELATIONSHIP); }); -App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key') +Http::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key') ->alias('/v1/database/collections/:collectionId/attributes/:key', ['databaseId' => 'default']) ->desc('Delete attribute') ->groups(['api', 'database', 'schema']) @@ -2358,9 +2402,10 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key ->inject('queueForDatabase') ->inject('queueForEvents') ->inject('queueForUsage') - ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Usage $queueForUsage) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Usage $queueForUsage, Authorization $authorization) { - $db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($db->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -2449,7 +2494,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key $response->noContent(); }); -App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') +Http::post('/v1/databases/:databaseId/collections/:collectionId/indexes') ->alias('/v1/database/collections/:collectionId/indexes', ['databaseId' => 'default']) ->desc('Create index') ->groups(['api', 'database']) @@ -2474,9 +2519,10 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $key, string $type, array $attributes, array $orders, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, string $type, array $attributes, array $orders, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { - $db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($db->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -2619,7 +2665,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') ->dynamic($index, Response::MODEL_INDEX); }); -App::get('/v1/databases/:databaseId/collections/:collectionId/indexes') +Http::get('/v1/databases/:databaseId/collections/:collectionId/indexes') ->alias('/v1/database/collections/:collectionId/indexes', ['databaseId' => 'default']) ->desc('List indexes') ->groups(['api', 'database']) @@ -2636,9 +2682,10 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes') ->param('queries', [], new Indexes(), '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(', ', Indexes::ALLOWED_ATTRIBUTES), true) ->inject('response') ->inject('dbForProject') - ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, Authorization $authorization) { /** @var Document $database */ - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -2668,7 +2715,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes') if ($cursor) { $indexId = $cursor->getValue(); - $cursorDocument = Authorization::skip(fn () => $dbForProject->find('indexes', [ + $cursorDocument = $authorization->skip(fn () => $dbForProject->find('indexes', [ Query::equal('collectionInternalId', [$collection->getInternalId()]), Query::equal('databaseInternalId', [$database->getInternalId()]), Query::equal('key', [$indexId]), @@ -2689,7 +2736,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes') ]), Response::MODEL_INDEX_LIST); }); -App::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') +Http::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') ->alias('/v1/database/collections/:collectionId/indexes/:key', ['databaseId' => 'default']) ->desc('Get index') ->groups(['api', 'database']) @@ -2706,9 +2753,10 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') ->param('key', null, new Key(), 'Index Key.') ->inject('response') ->inject('dbForProject') - ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, Authorization $authorization) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -2728,7 +2776,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') }); -App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') +Http::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') ->alias('/v1/database/collections/:collectionId/indexes/:key', ['databaseId' => 'default']) ->desc('Delete index') ->groups(['api', 'database']) @@ -2749,9 +2797,10 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { - $db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($db->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -2792,7 +2841,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') $response->noContent(); }); -App::post('/v1/databases/:databaseId/collections/:collectionId/documents') +Http::post('/v1/databases/:databaseId/collections/:collectionId/documents') ->alias('/v1/database/collections/:collectionId/documents', ['databaseId' => 'default']) ->desc('Create document') ->groups(['api', 'database']) @@ -2823,7 +2872,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') ->inject('queueForEvents') ->inject('queueForUsage') ->inject('mode') - ->action(function (string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, Usage $queueForUsage, string $mode) { + ->inject('authorization') + ->action(function (string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, Usage $queueForUsage, string $mode, Authorization $authorization) { $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array @@ -2835,16 +2885,16 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, '$id is not allowed for creating new documents, try update instead'); } - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $isAPIKey = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } - $collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); + $collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::COLLECTION_NOT_FOUND); @@ -2882,8 +2932,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') $permission->getIdentifier(), $permission->getDimension() ))->toString(); - if (!Authorization::isRole($role)) { - throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', Authorization::getRoles()) . ')'); + if (!$authorization->isRole($role)) { + throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $authorization->getRoles()) . ')'); } } } @@ -2894,17 +2944,16 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') $data['$permissions'] = $permissions; $document = new Document($data); - $checkPermissions = function (Document $collection, Document $document, string $permission) use (&$checkPermissions, $dbForProject, $database) { + $checkPermissions = function (Document $collection, Document $document, string $permission) use (&$checkPermissions, $dbForProject, $database, $authorization) { $documentSecurity = $collection->getAttribute('documentSecurity', false); - $validator = new Authorization($permission); - $valid = $validator->isValid($collection->getPermissionsByType($permission)); + $valid = $authorization->isValid(new Input($permission, $collection->getPermissionsByType($permission))); if (($permission === Database::PERMISSION_UPDATE && !$documentSecurity) || !$valid) { throw new Exception(Exception::USER_UNAUTHORIZED); } if ($permission === Database::PERMISSION_UPDATE) { - $valid = $valid || $validator->isValid($document->getUpdate()); + $valid = $valid || $authorization->isValid($document->getUpdate()); if ($documentSecurity && !$valid) { throw new Exception(Exception::USER_UNAUTHORIZED); } @@ -2931,7 +2980,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') } $relatedCollectionId = $relationship->getAttribute('relatedCollection'); - $relatedCollection = Authorization::skip( + $relatedCollection = $authorization->skip( fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId) ); @@ -2945,7 +2994,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') $relation = new Document($relation); } if ($relation instanceof Document) { - $current = Authorization::skip( + $current = $authorization->skip( fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId(), $relation->getId()) ); @@ -2985,7 +3034,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') } // Add $collectionId and $databaseId for all documents - $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) { + $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database, $authorization) { $document->setAttribute('$databaseId', $database->getId()); $document->setAttribute('$collectionId', $collection->getId()); @@ -3005,7 +3054,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') } $relatedCollectionId = $relationship->getAttribute('relatedCollection'); - $relatedCollection = Authorization::skip( + $relatedCollection = $authorization->skip( fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId) ); @@ -3044,7 +3093,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') ->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection }); -App::get('/v1/databases/:databaseId/collections/:collectionId/documents') +Http::get('/v1/databases/:databaseId/collections/:collectionId/documents') ->alias('/v1/database/collections/:collectionId/documents', ['databaseId' => 'default']) ->desc('List documents') ->groups(['api', 'database']) @@ -3063,16 +3112,17 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') ->inject('response') ->inject('dbForProject') ->inject('mode') - ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, string $mode) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, string $mode, Authorization $authorization) { + $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $isAPIKey = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } - $collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); + $collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::COLLECTION_NOT_FOUND); @@ -3096,7 +3146,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') if ($cursor) { $documentId = $cursor->getValue(); - $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId)); + $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId)); if ($cursorDocument->isEmpty()) { throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Document '{$documentId}' for the 'cursor' value not found."); @@ -3115,7 +3165,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') } // Add $collectionId and $databaseId for all documents - $processDocument = (function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database): bool { + $processDocument = (function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database, $authorization): bool { if ($document->isEmpty()) { return false; } @@ -3142,7 +3192,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') } $relatedCollectionId = $relationship->getAttribute('relatedCollection'); - $relatedCollection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId)); + $relatedCollection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId)); foreach ($relations as $index => $doc) { if ($doc instanceof Document) { @@ -3199,7 +3249,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') ]), Response::MODEL_DOCUMENT_LIST); }); -App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId') +Http::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId') ->alias('/v1/database/collections/:collectionId/documents/:documentId', ['databaseId' => 'default']) ->desc('Get document') ->groups(['api', 'database']) @@ -3220,17 +3270,18 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->inject('response') ->inject('dbForProject') ->inject('mode') - ->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, string $mode) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, string $mode, Authorization $authorization) { + $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $isAPIKey = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } - $collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); + $collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::COLLECTION_NOT_FOUND); @@ -3250,7 +3301,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen } // Add $collectionId and $databaseId for all documents - $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) { + $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database, $authorization) { if ($document->isEmpty()) { return; } @@ -3274,7 +3325,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen } $relatedCollectionId = $relationship->getAttribute('relatedCollection'); - $relatedCollection = Authorization::skip( + $relatedCollection = $authorization->skip( fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId) ); @@ -3291,7 +3342,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen $response->dynamic($document, Response::MODEL_DOCUMENT); }); -App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId/logs') +Http::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId/logs') ->alias('/v1/database/collections/:collectionId/documents/:documentId/logs', ['databaseId' => 'default']) ->desc('List document logs') ->groups(['api', 'database']) @@ -3311,9 +3362,10 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -3395,7 +3447,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ]), Response::MODEL_LOG_LIST); }); -App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId') +Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId') ->alias('/v1/database/collections/:collectionId/documents/:documentId', ['databaseId' => 'default']) ->desc('Update document') ->groups(['api', 'database']) @@ -3425,7 +3477,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum ->inject('dbForProject') ->inject('queueForEvents') ->inject('mode') - ->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, string $mode) { + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Authorization $authorization) { $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array @@ -3433,16 +3486,16 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum throw new Exception(Exception::DOCUMENT_MISSING_PAYLOAD); } - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $isAPIKey = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } - $collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); + $collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::COLLECTION_NOT_FOUND); @@ -3450,7 +3503,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum // Read permission should not be required for update /** @var Document $document */ - $document = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId)); + $document = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId)); if ($document->isEmpty()) { throw new Exception(Exception::DOCUMENT_NOT_FOUND); @@ -3464,7 +3517,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum ]); // Users can only manage their own roles, API keys and Admin users can manage any - $roles = Authorization::getRoles(); + $roles = $authorization->getRoles(); if (!$isAPIKey && !$isPrivilegedUser && !\is_null($permissions)) { foreach (Database::PERMISSIONS as $type) { foreach ($permissions as $permission) { @@ -3477,7 +3530,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum $permission->getIdentifier(), $permission->getDimension() ))->toString(); - if (!Authorization::isRole($role)) { + if (!$authorization->isRole($role)) { throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')'); } } @@ -3492,7 +3545,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum $data['$permissions'] = $permissions; $newDocument = new Document($data); - $setCollection = (function (Document $collection, Document $document) use (&$setCollection, $dbForProject, $database) { + $setCollection = (function (Document $collection, Document $document) use (&$setCollection, $dbForProject, $database, $authorization) { $relationships = \array_filter( $collection->getAttribute('attributes', []), fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP @@ -3514,7 +3567,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum } $relatedCollectionId = $relationship->getAttribute('relatedCollection'); - $relatedCollection = Authorization::skip( + $relatedCollection = $authorization->skip( fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId) ); @@ -3529,7 +3582,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum $relation = new Document($relation); } if ($relation instanceof Document) { - $oldDocument = Authorization::skip(fn () => $dbForProject->getDocument( + $oldDocument = $authorization->skip(fn () => $dbForProject->getDocument( 'database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId(), $relation->getId() )); @@ -3578,7 +3631,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum } // Add $collectionId and $databaseId for all documents - $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) { + $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database, $authorization) { $document->setAttribute('$databaseId', $database->getId()); $document->setAttribute('$collectionId', $collection->getId()); @@ -3598,7 +3651,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum } $relatedCollectionId = $relationship->getAttribute('relatedCollection'); - $relatedCollection = Authorization::skip( + $relatedCollection = $authorization->skip( fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId) ); @@ -3631,7 +3684,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum ->setPayload($response->getPayload(), sensitive: $relationships); }); -App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId') +Http::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId') ->alias('/v1/database/collections/:collectionId/documents/:documentId', ['databaseId' => 'default']) ->desc('Delete document') ->groups(['api', 'database']) @@ -3660,24 +3713,25 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu ->inject('queueForEvents') ->inject('queueForUsage') ->inject('mode') - ->action(function (string $databaseId, string $collectionId, string $documentId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Usage $queueForUsage, string $mode) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + ->inject('authorization') + ->action(function (string $databaseId, string $collectionId, string $documentId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Usage $queueForUsage, string $mode, Authorization $authorization) { + $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $isAPIKey = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } - $collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); + $collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::COLLECTION_NOT_FOUND); } // Read permission should not be required for delete - $document = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId)); + $document = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId)); if ($document->isEmpty()) { throw new Exception(Exception::DOCUMENT_NOT_FOUND); @@ -3691,7 +3745,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu }); // Add $collectionId and $databaseId for all documents - $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) { + $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database, $authorization) { $document->setAttribute('$databaseId', $database->getId()); $document->setAttribute('$collectionId', $collection->getId()); @@ -3711,7 +3765,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu } $relatedCollectionId = $relationship->getAttribute('relatedCollection'); - $relatedCollection = Authorization::skip( + $relatedCollection = $authorization->skip( fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId) ); @@ -3751,7 +3805,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu $response->noContent(); }); -App::get('/v1/databases/usage') +Http::get('/v1/databases/usage') ->desc('Get databases usage stats') ->groups(['api', 'database', 'usage']) ->label('scope', 'collections.read') @@ -3764,7 +3818,8 @@ App::get('/v1/databases/usage') ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), '`Date range.', true) ->inject('response') ->inject('dbForProject') - ->action(function (string $range, Response $response, Database $dbForProject) { + ->inject('authorization') + ->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) { $periods = Config::getParam('usage', []); $stats = $usage = []; @@ -3776,7 +3831,7 @@ App::get('/v1/databases/usage') METRIC_DATABASES_STORAGE ]; - Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) { + $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) { foreach ($metrics as $metric) { $result = $dbForProject->findOne('stats', [ Query::equal('metric', [$metric]), @@ -3832,7 +3887,7 @@ App::get('/v1/databases/usage') ]), Response::MODEL_USAGE_DATABASES); }); -App::get('/v1/databases/:databaseId/usage') +Http::get('/v1/databases/:databaseId/usage') ->desc('Get database usage stats') ->groups(['api', 'database', 'usage']) ->label('scope', 'collections.read') @@ -3846,7 +3901,8 @@ App::get('/v1/databases/:databaseId/usage') ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), '`Date range.', true) ->inject('response') ->inject('dbForProject') - ->action(function (string $databaseId, string $range, Response $response, Database $dbForProject) { + ->inject('authorization') + ->action(function (string $databaseId, string $range, Response $response, Database $dbForProject, Authorization $authorization) { $database = $dbForProject->getDocument('databases', $databaseId); @@ -3863,7 +3919,7 @@ App::get('/v1/databases/:databaseId/usage') str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_STORAGE) ]; - Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) { + $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) { foreach ($metrics as $metric) { $result = $dbForProject->findOne('stats', [ Query::equal('metric', [$metric]), @@ -3918,7 +3974,7 @@ App::get('/v1/databases/:databaseId/usage') ]), Response::MODEL_USAGE_DATABASE); }); -App::get('/v1/databases/:databaseId/collections/:collectionId/usage') +Http::get('/v1/databases/:databaseId/collections/:collectionId/usage') ->alias('/v1/database/:collectionId/usage', ['databaseId' => 'default']) ->desc('Get collection usage stats') ->groups(['api', 'database', 'usage']) @@ -3934,7 +3990,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/usage') ->param('collectionId', '', new UID(), 'Collection ID.') ->inject('response') ->inject('dbForProject') - ->action(function (string $databaseId, string $range, string $collectionId, Response $response, Database $dbForProject) { + ->inject('authorization') + ->action(function (string $databaseId, string $range, string $collectionId, Response $response, Database $dbForProject, Authorization $authorization) { $database = $dbForProject->getDocument('databases', $databaseId); $collectionDocument = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId); @@ -3951,7 +4008,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/usage') str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collectionDocument->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS), ]; - Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) { + $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) { foreach ($metrics as $metric) { $result = $dbForProject->findOne('stats', [ Query::equal('metric', [$metric]), diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 9c3e6782b4..d0aa60ad06 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -2,6 +2,7 @@ use Ahc\Jwt\JWT; use Appwrite\Auth\Auth; +use Appwrite\Auth\Authentication; use Appwrite\Event\Build; use Appwrite\Event\Delete; use Appwrite\Event\Event; @@ -20,11 +21,11 @@ use Appwrite\Utopia\Database\Validator\CustomId; use Appwrite\Utopia\Database\Validator\Queries\Deployments; use Appwrite\Utopia\Database\Validator\Queries\Executions; use Appwrite\Utopia\Database\Validator\Queries\Functions; +use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use Appwrite\Utopia\Response\Model\Rule; use Executor\Executor; use MaxMind\Db\Reader; -use Utopia\App; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Database\Database; @@ -37,24 +38,25 @@ use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; +use Utopia\Database\Validator\Authorization\Input; use Utopia\Database\Validator\Datetime as DatetimeValidator; use Utopia\Database\Validator\Roles; use Utopia\Database\Validator\UID; +use Utopia\Http\Http; +use Utopia\Http\Validator\AnyOf; +use Utopia\Http\Validator\ArrayList; +use Utopia\Http\Validator\Assoc; +use Utopia\Http\Validator\Boolean; +use Utopia\Http\Validator\Nullable; +use Utopia\Http\Validator\Range; +use Utopia\Http\Validator\Text; +use Utopia\Http\Validator\WhiteList; 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\AnyOf; -use Utopia\Validator\ArrayList; -use Utopia\Validator\Assoc; -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; use Utopia\VCS\Exception\RepositoryNotFound; @@ -133,7 +135,7 @@ $redeployVcs = function (Request $request, Document $function, Document $project ->setTemplate($template); }; -App::post('/v1/functions') +Http::post('/v1/functions') ->groups(['api', 'functions']) ->desc('Create function') ->label('scope', 'functions.write') @@ -171,8 +173,8 @@ App::post('/v1/functions') ->param('specification', APP_FUNCTION_SPECIFICATION_DEFAULT, fn (array $plan) => new RuntimeSpecification( $plan, Config::getParam('runtime-specifications', []), - App::getEnv('_APP_FUNCTIONS_CPUS', APP_FUNCTION_CPUS_DEFAULT), - App::getEnv('_APP_FUNCTIONS_MEMORY', APP_FUNCTION_MEMORY_DEFAULT) + System::getEnv('_APP_FUNCTIONS_CPUS', APP_FUNCTION_CPUS_DEFAULT), + System::getEnv('_APP_FUNCTIONS_MEMORY', APP_FUNCTION_MEMORY_DEFAULT) ), 'Runtime specification for the function and builds.', true, ['plan']) ->inject('request') ->inject('response') @@ -183,7 +185,8 @@ App::post('/v1/functions') ->inject('queueForBuilds') ->inject('dbForConsole') ->inject('gitHub') - ->action(function (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) use ($redeployVcs) { + ->inject('authorization') + ->action(function (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, Authorization $authorization) use ($redeployVcs) { $functionId = ($functionId == 'unique()') ? ID::unique() : $functionId; $allowList = \array_filter(\explode(',', System::getEnv('_APP_FUNCTIONS_RUNTIMES', ''))); @@ -247,7 +250,7 @@ App::post('/v1/functions') 'specification' => $specification ])); - $schedule = Authorization::skip( + $schedule = $authorization->skip( fn () => $dbForConsole->createDocument('schedules', new Document([ 'region' => System::getEnv('_APP_REGION', 'default'), // Todo replace with projects region 'resourceType' => 'function', @@ -329,7 +332,7 @@ App::post('/v1/functions') $routeSubdomain = ID::unique(); $domain = "{$routeSubdomain}.{$functionsDomain}"; - $rule = Authorization::skip( + $rule = $authorization->skip( fn () => $dbForConsole->createDocument('rules', new Document([ '$id' => $ruleId, 'projectId' => $project->getId(), @@ -396,7 +399,7 @@ App::post('/v1/functions') ->dynamic($function, Response::MODEL_FUNCTION); }); -App::get('/v1/functions') +Http::get('/v1/functions') ->groups(['api', 'functions']) ->desc('List functions') ->label('scope', 'functions.read') @@ -450,7 +453,7 @@ App::get('/v1/functions') ]), Response::MODEL_FUNCTION_LIST); }); -App::get('/v1/functions/runtimes') +Http::get('/v1/functions/runtimes') ->groups(['api', 'functions']) ->desc('List runtimes') ->label('scope', 'functions.read') @@ -483,7 +486,7 @@ App::get('/v1/functions/runtimes') ]), Response::MODEL_RUNTIME_LIST); }); -App::get('/v1/functions/specifications') +Http::get('/v1/functions/specifications') ->groups(['api', 'functions']) ->desc('List available function runtime specifications') ->label('scope', 'functions.read') @@ -519,7 +522,7 @@ App::get('/v1/functions/specifications') ]), Response::MODEL_SPECIFICATION_LIST); }); -App::get('/v1/functions/:functionId') +Http::get('/v1/functions/:functionId') ->groups(['api', 'functions']) ->desc('Get function') ->label('scope', 'functions.read') @@ -543,7 +546,7 @@ App::get('/v1/functions/:functionId') $response->dynamic($function, Response::MODEL_FUNCTION); }); -App::get('/v1/functions/:functionId/usage') +Http::get('/v1/functions/:functionId/usage') ->desc('Get function usage') ->groups(['api', 'functions', 'usage']) ->label('scope', 'functions.read') @@ -557,7 +560,8 @@ App::get('/v1/functions/:functionId/usage') ->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true) ->inject('response') ->inject('dbForProject') - ->action(function (string $functionId, string $range, Response $response, Database $dbForProject) { + ->inject('authorization') + ->action(function (string $functionId, string $range, Response $response, Database $dbForProject, Authorization $authorization) { $function = $dbForProject->getDocument('functions', $functionId); @@ -580,7 +584,7 @@ App::get('/v1/functions/:functionId/usage') str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS) ]; - Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) { + $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) { foreach ($metrics as $metric) { $result = $dbForProject->findOne('stats', [ Query::equal('metric', [$metric]), @@ -647,7 +651,7 @@ App::get('/v1/functions/:functionId/usage') ]), Response::MODEL_USAGE_FUNCTION); }); -App::get('/v1/functions/usage') +Http::get('/v1/functions/usage') ->desc('Get functions usage') ->groups(['api', 'functions']) ->label('scope', 'functions.read') @@ -660,7 +664,8 @@ App::get('/v1/functions/usage') ->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true) ->inject('response') ->inject('dbForProject') - ->action(function (string $range, Response $response, Database $dbForProject) { + ->inject('authorization') + ->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) { $periods = Config::getParam('usage', []); $stats = $usage = []; @@ -678,7 +683,7 @@ App::get('/v1/functions/usage') METRIC_EXECUTIONS_MB_SECONDS, ]; - Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) { + $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) { foreach ($metrics as $metric) { $result = $dbForProject->findOne('stats', [ Query::equal('metric', [$metric]), @@ -746,7 +751,7 @@ App::get('/v1/functions/usage') ]), Response::MODEL_USAGE_FUNCTIONS); }); -App::put('/v1/functions/:functionId') +Http::put('/v1/functions/:functionId') ->groups(['api', 'functions']) ->desc('Update function') ->label('scope', 'functions.write') @@ -780,8 +785,8 @@ App::put('/v1/functions/:functionId') ->param('specification', APP_FUNCTION_SPECIFICATION_DEFAULT, fn (array $plan) => new RuntimeSpecification( $plan, Config::getParam('runtime-specifications', []), - App::getEnv('_APP_FUNCTIONS_CPUS', APP_FUNCTION_CPUS_DEFAULT), - App::getEnv('_APP_FUNCTIONS_MEMORY', APP_FUNCTION_MEMORY_DEFAULT) + System::getEnv('_APP_FUNCTIONS_CPUS', APP_FUNCTION_CPUS_DEFAULT), + System::getEnv('_APP_FUNCTIONS_MEMORY', APP_FUNCTION_MEMORY_DEFAULT) ), 'Runtime specification for the function and builds.', true, ['plan']) ->inject('request') ->inject('response') @@ -791,7 +796,8 @@ App::put('/v1/functions/:functionId') ->inject('queueForBuilds') ->inject('dbForConsole') ->inject('gitHub') - ->action(function (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) use ($redeployVcs) { + ->inject('authorization') + ->action(function (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, Authorization $authorization) use ($redeployVcs) { // TODO: If only branch changes, re-deploy $function = $dbForProject->getDocument('functions', $functionId); @@ -894,7 +900,7 @@ App::put('/v1/functions/:functionId') // Enforce Cold Start if spec limits change. if ($function->getAttribute('specification') !== $specification && !empty($function->getAttribute('deployment'))) { - $executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST')); + $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST')); try { $executor->deleteRuntime($project->getId(), $function->getAttribute('deployment')); } catch (\Throwable $th) { @@ -941,14 +947,14 @@ App::put('/v1/functions/:functionId') ->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)); + $authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); $queueForEvents->setParam('functionId', $function->getId()); $response->dynamic($function, Response::MODEL_FUNCTION); }); -App::get('/v1/functions/:functionId/deployments/:deploymentId/download') +Http::get('/v1/functions/:functionId/deployments/:deploymentId/download') ->groups(['api', 'functions']) ->desc('Download deployment') ->label('scope', 'functions.read') @@ -1033,7 +1039,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download') } }); -App::patch('/v1/functions/:functionId/deployments/:deploymentId') +Http::patch('/v1/functions/:functionId/deployments/:deploymentId') ->groups(['api', 'functions']) ->desc('Update deployment') ->label('scope', 'functions.write') @@ -1053,7 +1059,8 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId') ->inject('dbForProject') ->inject('queueForEvents') ->inject('dbForConsole') - ->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Event $queueForEvents, Database $dbForConsole) { + ->inject('authorization') + ->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Event $queueForEvents, Database $dbForConsole, Authorization $authorization) { $function = $dbForProject->getDocument('functions', $functionId); $deployment = $dbForProject->getDocument('deployments', $deploymentId); @@ -1086,7 +1093,7 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId') ->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)); + $authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); $queueForEvents ->setParam('functionId', $function->getId()) @@ -1095,7 +1102,7 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId') $response->dynamic($function, Response::MODEL_FUNCTION); }); -App::delete('/v1/functions/:functionId') +Http::delete('/v1/functions/:functionId') ->groups(['api', 'functions']) ->desc('Delete function') ->label('scope', 'functions.write') @@ -1114,7 +1121,8 @@ App::delete('/v1/functions/:functionId') ->inject('queueForDeletes') ->inject('queueForEvents') ->inject('dbForConsole') - ->action(function (string $functionId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Database $dbForConsole) { + ->inject('authorization') + ->action(function (string $functionId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Database $dbForConsole, Authorization $authorization) { $function = $dbForProject->getDocument('functions', $functionId); @@ -1131,7 +1139,7 @@ App::delete('/v1/functions/:functionId') $schedule ->setAttribute('resourceUpdatedAt', DateTime::now()) ->setAttribute('active', false); - Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); + $authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); $queueForDeletes ->setType(DELETE_TYPE_DOCUMENT) @@ -1142,7 +1150,7 @@ App::delete('/v1/functions/:functionId') $response->noContent(); }); -App::post('/v1/functions/:functionId/deployments') +Http::post('/v1/functions/:functionId/deployments') ->groups(['api', 'functions']) ->desc('Create deployment') ->label('scope', 'functions.write') @@ -1361,7 +1369,7 @@ App::post('/v1/functions/:functionId/deployments') ->dynamic($deployment, Response::MODEL_DEPLOYMENT); }); -App::get('/v1/functions/:functionId/deployments') +Http::get('/v1/functions/:functionId/deployments') ->groups(['api', 'functions']) ->desc('List deployments') ->label('scope', 'functions.read') @@ -1438,7 +1446,7 @@ App::get('/v1/functions/:functionId/deployments') ]), Response::MODEL_DEPLOYMENT_LIST); }); -App::get('/v1/functions/:functionId/deployments/:deploymentId') +Http::get('/v1/functions/:functionId/deployments/:deploymentId') ->groups(['api', 'functions']) ->desc('Get deployment') ->label('scope', 'functions.read') @@ -1481,7 +1489,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId') $response->dynamic($deployment, Response::MODEL_DEPLOYMENT); }); -App::delete('/v1/functions/:functionId/deployments/:deploymentId') +Http::delete('/v1/functions/:functionId/deployments/:deploymentId') ->groups(['api', 'functions']) ->desc('Delete deployment') ->label('scope', 'functions.write') @@ -1545,7 +1553,7 @@ App::delete('/v1/functions/:functionId/deployments/:deploymentId') $response->noContent(); }); -App::post('/v1/functions/:functionId/deployments/:deploymentId/build') +Http::post('/v1/functions/:functionId/deployments/:deploymentId/build') ->alias('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId') ->groups(['api', 'functions']) ->desc('Rebuild deployment') @@ -1568,7 +1576,8 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/build') ->inject('queueForEvents') ->inject('queueForBuilds') ->inject('deviceForFunctions') - ->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Device $deviceForFunctions) { + ->inject('authorization') + ->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Device $deviceForFunctions, Authorization $authorization) { $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { @@ -1614,7 +1623,7 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/build') $response->noContent(); }); -App::patch('/v1/functions/:functionId/deployments/:deploymentId/build') +Http::patch('/v1/functions/:functionId/deployments/:deploymentId/build') ->groups(['api', 'functions']) ->desc('Cancel deployment') ->label('scope', 'functions.write') @@ -1632,7 +1641,8 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId/build') ->inject('dbForProject') ->inject('project') ->inject('queueForEvents') - ->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Document $project, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Authorization $authorization) { $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { @@ -1645,7 +1655,7 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId/build') throw new Exception(Exception::DEPLOYMENT_NOT_FOUND); } - $build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''))); + $build = $authorization->skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''))); if ($build->isEmpty()) { $buildId = ID::unique(); @@ -1687,7 +1697,7 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId/build') $dbForProject->purgeCachedDocument('deployments', $deployment->getId()); try { - $executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST')); + $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST')); $executor->deleteRuntime($project->getId(), $deploymentId . "-build"); } catch (\Throwable $th) { // Don't throw if the deployment doesn't exist @@ -1703,7 +1713,7 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId/build') $response->dynamic($build, Response::MODEL_BUILD); }); -App::post('/v1/functions/:functionId/executions') +Http::post('/v1/functions/:functionId/executions') ->groups(['api', 'functions']) ->desc('Create execution') ->label('scope', 'execution.write') @@ -1733,7 +1743,9 @@ App::post('/v1/functions/:functionId/executions') ->inject('queueForUsage') ->inject('queueForFunctions') ->inject('geodb') - ->action(function (string $functionId, string $body, mixed $async, string $path, string $method, mixed $headers, ?string $scheduledAt, Response $response, Request $request, Document $project, Database $dbForProject, Database $dbForConsole, Document $user, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb) { + ->inject('authorization') + ->inject('authentication') + ->action(function (string $functionId, string $body, mixed $async, string $path, string $method, mixed $headers, ?string $scheduledAt, Response $response, Request $request, Document $project, Database $dbForProject, Database $dbForConsole, Document $user, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb, Authorization $authorization, Authentication $authentication) { $async = \strval($async) === 'true' || \strval($async) === '1'; if (!$async && !is_null($scheduledAt)) { @@ -1763,10 +1775,10 @@ App::post('/v1/functions/:functionId/executions') throw new Exception($validator->getDescription(), 400); } - $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); + $function = $authorization->skip(fn () => $dbForProject->getDocument('functions', $functionId)); - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $isAPIKey = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); if ($function->isEmpty() || (!$function->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::FUNCTION_NOT_FOUND); @@ -1782,7 +1794,7 @@ App::post('/v1/functions/:functionId/executions') throw new Exception(Exception::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $function->getAttribute('runtime', '') . '" is not supported'); } - $deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', ''))); + $deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', ''))); if ($deployment->getAttribute('resourceId') !== $function->getId()) { throw new Exception(Exception::DEPLOYMENT_NOT_FOUND, 'Deployment not found. Create a deployment before trying to execute a function'); @@ -1793,7 +1805,7 @@ App::post('/v1/functions/:functionId/executions') } /** Check if build has completed */ - $build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''))); + $build = $authorization->skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''))); if ($build->isEmpty()) { throw new Exception(Exception::BUILD_NOT_FOUND); } @@ -1802,10 +1814,8 @@ App::post('/v1/functions/:functionId/executions') throw new Exception(Exception::BUILD_NOT_READY); } - $validator = new Authorization('execute'); - - if (!$validator->isValid($function->getAttribute('execute'))) { // Check if user has write access to execute function - throw new Exception(Exception::USER_UNAUTHORIZED, $validator->getDescription()); + if (!$authorization->isValid(new Input('execute', $function->getAttribute('execute')))) { // Check if user has write access to execute function + throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription()); } $jwt = ''; // initialize @@ -1815,7 +1825,7 @@ App::post('/v1/functions/:functionId/executions') foreach ($sessions as $session) { /** @var Utopia\Database\Document $session */ - if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too + if ($session->getAttribute('secret') == Auth::hash($authentication->getSecret())) { // If current session delete the cookies too $current = $session; } } @@ -1899,8 +1909,9 @@ App::post('/v1/functions/:functionId/executions') ->setContext('function', $function); if ($async) { + if (is_null($scheduledAt)) { - $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution)); + $execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution)); $queueForFunctions ->setType('http') ->setExecution($execution) @@ -1941,7 +1952,7 @@ App::post('/v1/functions/:functionId/executions') ->setAttribute('scheduleInternalId', $schedule->getInternalId()) ->setAttribute('scheduledAt', $scheduledAt); - $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution)); + $execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution)); } return $response @@ -2027,8 +2038,7 @@ App::post('/v1/functions/:functionId/executions') runtimeEntrypoint: $command, cpus: $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT, memory: $spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT, - logging: $function->getAttribute('logging', true), - requestTimeout: 30 + logging: $function->getAttribute('logging', true) ); $headersFiltered = []; @@ -2069,10 +2079,10 @@ App::post('/v1/functions/:functionId/executions') ->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))) ; - $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution)); + $execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution)); } - $roles = Authorization::getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -2105,7 +2115,7 @@ App::post('/v1/functions/:functionId/executions') ->dynamic($execution, Response::MODEL_EXECUTION); }); -App::get('/v1/functions/:functionId/executions') +Http::get('/v1/functions/:functionId/executions') ->groups(['api', 'functions']) ->desc('List executions') ->label('scope', 'execution.read') @@ -2122,11 +2132,12 @@ App::get('/v1/functions/:functionId/executions') ->inject('response') ->inject('dbForProject') ->inject('mode') - ->action(function (string $functionId, array $queries, string $search, Response $response, Database $dbForProject, string $mode) { - $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); + ->inject('authorization') + ->action(function (string $functionId, array $queries, string $search, Response $response, Database $dbForProject, string $mode, Authorization $authorization) { + $function = $authorization->skip(fn () => $dbForProject->getDocument('functions', $functionId)); - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $isAPIKey = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); if ($function->isEmpty() || (!$function->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::FUNCTION_NOT_FOUND); @@ -2169,7 +2180,7 @@ App::get('/v1/functions/:functionId/executions') $results = $dbForProject->find('executions', $queries); $total = $dbForProject->count('executions', $filterQueries, APP_LIMIT_COUNT); - $roles = Authorization::getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); if (!$isPrivilegedUser && !$isAppUser) { @@ -2186,7 +2197,7 @@ App::get('/v1/functions/:functionId/executions') ]), Response::MODEL_EXECUTION_LIST); }); -App::get('/v1/functions/:functionId/executions/:executionId') +Http::get('/v1/functions/:functionId/executions/:executionId') ->groups(['api', 'functions']) ->desc('Get execution') ->label('scope', 'execution.read') @@ -2202,11 +2213,12 @@ App::get('/v1/functions/:functionId/executions/:executionId') ->inject('response') ->inject('dbForProject') ->inject('mode') - ->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, string $mode) { - $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); + ->inject('authorization') + ->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, string $mode, Authorization $authorization) { + $function = $authorization->skip(fn () => $dbForProject->getDocument('functions', $functionId)); - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $isAPIKey = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); if ($function->isEmpty() || (!$function->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::FUNCTION_NOT_FOUND); @@ -2222,7 +2234,7 @@ App::get('/v1/functions/:functionId/executions/:executionId') throw new Exception(Exception::EXECUTION_NOT_FOUND); } - $roles = Authorization::getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); if (!$isPrivilegedUser && !$isAppUser) { @@ -2233,7 +2245,7 @@ App::get('/v1/functions/:functionId/executions/:executionId') $response->dynamic($execution, Response::MODEL_EXECUTION); }); -App::delete('/v1/functions/:functionId/executions/:executionId') +Http::delete('/v1/functions/:functionId/executions/:executionId') ->groups(['api', 'functions']) ->desc('Delete execution') ->label('scope', 'execution.write') @@ -2252,7 +2264,8 @@ App::delete('/v1/functions/:functionId/executions/:executionId') ->inject('dbForProject') ->inject('dbForConsole') ->inject('queueForEvents') - ->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, Database $dbForConsole, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, Database $dbForConsole, Event $queueForEvents, Authorization $authorization) { $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { @@ -2289,7 +2302,7 @@ App::delete('/v1/functions/:functionId/executions/:executionId') ->setAttribute('resourceUpdatedAt', DateTime::now()) ->setAttribute('active', false); - Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); + $authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); } } @@ -2303,7 +2316,7 @@ App::delete('/v1/functions/:functionId/executions/:executionId') // Variables -App::post('/v1/functions/:functionId/variables') +Http::post('/v1/functions/:functionId/variables') ->desc('Create variable') ->groups(['api', 'functions']) ->label('scope', 'functions.write') @@ -2322,7 +2335,8 @@ App::post('/v1/functions/:functionId/variables') ->inject('response') ->inject('dbForProject') ->inject('dbForConsole') - ->action(function (string $functionId, string $key, string $value, Response $response, Database $dbForProject, Database $dbForConsole) { + ->inject('authorization') + ->action(function (string $functionId, string $key, string $value, Response $response, Database $dbForProject, Database $dbForConsole, Authorization $authorization) { $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { @@ -2360,14 +2374,14 @@ App::post('/v1/functions/:functionId/variables') ->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)); + $authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($variable, Response::MODEL_VARIABLE); }); -App::get('/v1/functions/:functionId/variables') +Http::get('/v1/functions/:functionId/variables') ->desc('List variables') ->groups(['api', 'functions']) ->label('scope', 'functions.read') @@ -2394,7 +2408,7 @@ App::get('/v1/functions/:functionId/variables') ]), Response::MODEL_VARIABLE_LIST); }); -App::get('/v1/functions/:functionId/variables/:variableId') +Http::get('/v1/functions/:functionId/variables/:variableId') ->desc('Get variable') ->groups(['api', 'functions']) ->label('scope', 'functions.read') @@ -2433,7 +2447,7 @@ App::get('/v1/functions/:functionId/variables/:variableId') $response->dynamic($variable, Response::MODEL_VARIABLE); }); -App::put('/v1/functions/:functionId/variables/:variableId') +Http::put('/v1/functions/:functionId/variables/:variableId') ->desc('Update variable') ->groups(['api', 'functions']) ->label('scope', 'functions.write') @@ -2453,7 +2467,8 @@ App::put('/v1/functions/:functionId/variables/:variableId') ->inject('response') ->inject('dbForProject') ->inject('dbForConsole') - ->action(function (string $functionId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject, Database $dbForConsole) { + ->inject('authorization') + ->action(function (string $functionId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject, Database $dbForConsole, Authorization $authorization) { $function = $dbForProject->getDocument('functions', $functionId); @@ -2489,12 +2504,12 @@ App::put('/v1/functions/:functionId/variables/:variableId') ->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)); + $authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); $response->dynamic($variable, Response::MODEL_VARIABLE); }); -App::delete('/v1/functions/:functionId/variables/:variableId') +Http::delete('/v1/functions/:functionId/variables/:variableId') ->desc('Delete variable') ->groups(['api', 'functions']) ->label('scope', 'functions.write') @@ -2511,7 +2526,8 @@ App::delete('/v1/functions/:functionId/variables/:variableId') ->inject('response') ->inject('dbForProject') ->inject('dbForConsole') - ->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject, Database $dbForConsole) { + ->inject('authorization') + ->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject, Database $dbForConsole, Authorization $authorization) { $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { @@ -2537,12 +2553,12 @@ App::delete('/v1/functions/:functionId/variables/:variableId') ->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)); + $authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); $response->noContent(); }); -App::get('/v1/functions/templates') +Http::get('/v1/functions/templates') ->groups(['api']) ->desc('List function templates') ->label('scope', 'public') @@ -2580,7 +2596,7 @@ App::get('/v1/functions/templates') ]), Response::MODEL_TEMPLATE_FUNCTION_LIST); }); -App::get('/v1/functions/templates/:templateId') +Http::get('/v1/functions/templates/:templateId') ->desc('Get function template') ->label('scope', 'public') ->label('sdk.namespace', 'functions') @@ -2595,9 +2611,10 @@ App::get('/v1/functions/templates/:templateId') ->action(function (string $templateId, Response $response) { $templates = Config::getParam('function-templates', []); - $template = array_shift(\array_filter($templates, function ($template) use ($templateId) { + $array = \array_filter($templates, function ($template) use ($templateId) { return $template['id'] === $templateId; - })); + }); + $template = array_shift($array); if (empty($template)) { throw new Exception(Exception::FUNCTION_TEMPLATE_NOT_FOUND); diff --git a/app/controllers/api/graphql.php b/app/controllers/api/graphql.php index f79f433b5c..0e7ddc783d 100644 --- a/app/controllers/api/graphql.php +++ b/app/controllers/api/graphql.php @@ -14,27 +14,28 @@ use GraphQL\Validator\Rules\DisableIntrospection; use GraphQL\Validator\Rules\QueryComplexity; use GraphQL\Validator\Rules\QueryDepth; use Swoole\Coroutine\WaitGroup; -use Utopia\App; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; +use Utopia\Http\Http; +use Utopia\Http\Validator\JSON; +use Utopia\Http\Validator\Text; use Utopia\System\System; -use Utopia\Validator\JSON; -use Utopia\Validator\Text; -App::init() +Http::init() ->groups(['graphql']) ->inject('project') - ->action(function (Document $project) { + ->inject('authorization') + ->action(function (Document $project, Authorization $authorization) { if ( array_key_exists('graphql', $project->getAttribute('apis', [])) && !$project->getAttribute('apis', [])['graphql'] - && !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles())) + && !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles())) ) { throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED); } }); -App::get('/v1/graphql') +Http::get('/v1/graphql') ->desc('GraphQL endpoint') ->groups(['graphql']) ->label('scope', 'graphql') @@ -74,7 +75,7 @@ App::get('/v1/graphql') ->json($output); }); -App::post('/v1/graphql/mutation') +Http::post('/v1/graphql/mutation') ->desc('GraphQL endpoint') ->groups(['graphql']) ->label('scope', 'graphql') @@ -119,7 +120,7 @@ App::post('/v1/graphql/mutation') ->json($output); }); -App::post('/v1/graphql') +Http::post('/v1/graphql') ->desc('GraphQL endpoint') ->groups(['graphql']) ->label('scope', 'graphql') @@ -156,7 +157,6 @@ App::post('/v1/graphql') if (\str_starts_with($type, 'multipart/form-data')) { $query = parseMultipart($query, $request); } - $output = execute($schema, $promiseAdapter, $query); $response @@ -205,7 +205,7 @@ function execute( $validations[] = new QueryComplexity($maxComplexity); $validations[] = new QueryDepth($maxDepth); } - if (App::getMode() === App::MODE_TYPE_PRODUCTION) { + if (Http::getMode() === Http::MODE_TYPE_PRODUCTION) { $flags = DebugFlag::NONE; } @@ -306,9 +306,10 @@ function processResult($result, $debugFlags): array ); } -App::shutdown() +Http::shutdown() ->groups(['schema']) ->inject('project') - ->action(function (Document $project) { - Schema::setDirty($project->getId()); + ->inject('schemaVariable') + ->action(function (Document $project, Schema $schemaVariable) { + $schemaVariable->setDirty($project->getId()); }); diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index f4581df8e4..47d80dbf71 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -3,12 +3,19 @@ use Appwrite\ClamAV\Network; use Appwrite\Event\Event; use Appwrite\Extend\Exception; +use Appwrite\Utopia\Queue\Connections; use Appwrite\Utopia\Response; -use Utopia\App; use Utopia\Config\Config; +use Utopia\Database\Adapter\MariaDB; +use Utopia\Database\Adapter\MySQL; use Utopia\Database\Document; use Utopia\Domains\Validator\PublicDomain; -use Utopia\Pools\Group; +use Utopia\Http\Http; +use Utopia\Http\Validator\Domain; +use Utopia\Http\Validator\Integer; +use Utopia\Http\Validator\Multiple; +use Utopia\Http\Validator\Text; +use Utopia\Http\Validator\WhiteList; use Utopia\Queue\Client; use Utopia\Queue\Connection; use Utopia\Registry\Registry; @@ -16,13 +23,8 @@ use Utopia\Storage\Device; use Utopia\Storage\Device\Local; use Utopia\Storage\Storage; use Utopia\System\System; -use Utopia\Validator\Domain; -use Utopia\Validator\Integer; -use Utopia\Validator\Multiple; -use Utopia\Validator\Text; -use Utopia\Validator\WhiteList; -App::get('/v1/health') +Http::get('/v1/health') ->desc('Get HTTP') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -45,7 +47,7 @@ App::get('/v1/health') $response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS); }); -App::get('/v1/health/version') +Http::get('/v1/health/version') ->desc('Get version') ->groups(['api', 'health']) ->label('scope', 'public') @@ -57,7 +59,7 @@ App::get('/v1/health/version') $response->dynamic(new Document([ 'version' => APP_VERSION_STABLE ]), Response::MODEL_HEALTH_VERSION); }); -App::get('/v1/health/db') +Http::get('/v1/health/db') ->desc('Get DB') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -70,21 +72,34 @@ App::get('/v1/health/db') ->label('sdk.response.model', Response::MODEL_HEALTH_STATUS) ->inject('response') ->inject('pools') - ->action(function (Response $response, Group $pools) { + ->inject('connections') + ->action(function (Response $response, array $pools, Connections $connections) { $output = []; $configs = [ - 'Console.DB' => Config::getParam('pools-console'), - 'Projects.DB' => Config::getParam('pools-database'), + 'console' => Config::getParam('pools-console'), + 'database' => Config::getParam('pools-database'), ]; foreach ($configs as $key => $config) { foreach ($config as $database) { - try { - $adapter = $pools->get($database)->pop()->getResource(); + $checkStart = \microtime(true); + + try { + + $pool = $pools['pools-'.$key.'-'.$database]['pool']; + $dsn = $pools['pools-'.$key.'-'.$database]['dsn']; + + $connection = $pool->get(); + $connections->add($connection, $pool); + $adapter = match ($dsn->getScheme()) { + 'mariadb' => new MariaDB($connection), + 'mysql' => new MySQL($connection), + default => null + }; + $adapter->setDatabase($dsn->getPath()); - $checkStart = \microtime(true); if ($adapter->ping()) { $output[] = new Document([ @@ -111,7 +126,7 @@ App::get('/v1/health/db') ]), Response::MODEL_HEALTH_STATUS_LIST); }); -App::get('/v1/health/cache') +Http::get('/v1/health/cache') ->desc('Get cache') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -124,7 +139,8 @@ App::get('/v1/health/cache') ->label('sdk.response.model', Response::MODEL_HEALTH_STATUS) ->inject('response') ->inject('pools') - ->action(function (Response $response, Group $pools) { + ->inject('connections') + ->action(function (Response $response, array $pools, Connections $connections) { $output = []; @@ -134,10 +150,15 @@ App::get('/v1/health/cache') foreach ($configs as $key => $config) { foreach ($config as $database) { + $checkStart = \microtime(true); try { - $adapter = $pools->get($database)->pop()->getResource(); + $pool = $pools['pools-cache-' . $database]['pool']; + $dsn = $pools['pools-cache-' . $database]['dsn']; + $connection = $pool->get(); + $connections->add($connection, $pool); + + $adapter = new Connection\Redis($dsn->getHost(), $dsn->getPort()); - $checkStart = \microtime(true); if ($adapter->ping()) { $output[] = new Document([ @@ -158,6 +179,8 @@ App::get('/v1/health/cache') 'status' => 'fail', 'ping' => \round((\microtime(true) - $checkStart) / 1000) ]); + } finally { + $connections->reclaim(); } } } @@ -168,7 +191,7 @@ App::get('/v1/health/cache') ]), Response::MODEL_HEALTH_STATUS_LIST); }); -App::get('/v1/health/queue') +Http::get('/v1/health/queue') ->desc('Get queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -181,7 +204,8 @@ App::get('/v1/health/queue') ->label('sdk.response.model', Response::MODEL_HEALTH_STATUS) ->inject('response') ->inject('pools') - ->action(function (Response $response, Group $pools) { + ->inject('connections') + ->action(function (Response $response, array $pools, Connections $connections) { $output = []; @@ -190,12 +214,16 @@ App::get('/v1/health/queue') ]; foreach ($configs as $key => $config) { + $checkStart = \microtime(true); + foreach ($config as $database) { try { - $adapter = $pools->get($database)->pop()->getResource(); - - $checkStart = \microtime(true); + $pool = $pools['pools-queue-' . $database]['pool']; + $dsn = $pools['pools-queue-' . $database]['dsn']; + $connection = $pool->get(); + $connections->add($connection, $pool); + $adapter = new Connection\Redis($dsn->getHost(), $dsn->getPort()); if ($adapter->ping()) { $output[] = new Document([ 'name' => $key . " ($database)", @@ -215,6 +243,8 @@ App::get('/v1/health/queue') 'status' => 'fail', 'ping' => \round((\microtime(true) - $checkStart) / 1000) ]); + } finally { + $connections->reclaim(); } } } @@ -225,7 +255,7 @@ App::get('/v1/health/queue') ]), Response::MODEL_HEALTH_STATUS_LIST); }); -App::get('/v1/health/pubsub') +Http::get('/v1/health/pubsub') ->desc('Get pubsub') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -238,7 +268,8 @@ App::get('/v1/health/pubsub') ->label('sdk.response.model', Response::MODEL_HEALTH_STATUS) ->inject('response') ->inject('pools') - ->action(function (Response $response, Group $pools) { + ->inject('connections') + ->action(function (Response $response, array $pools, Connections $connections) { $output = []; @@ -249,7 +280,12 @@ App::get('/v1/health/pubsub') foreach ($configs as $key => $config) { foreach ($config as $database) { try { - $adapter = $pools->get($database)->pop()->getResource(); + $pool = $pools['pools-pubsub-' . $database]['pool']; + + $connection = $pool->get(); + $connections->add($connection, $pool); + + $adapter = new Connection\Redis($connection); $checkStart = \microtime(true); @@ -272,6 +308,8 @@ App::get('/v1/health/pubsub') 'status' => 'fail', 'ping' => \round((\microtime(true) - $checkStart) / 1000) ]); + } finally { + $connections->reclaim(); } } } @@ -282,7 +320,7 @@ App::get('/v1/health/pubsub') ]), Response::MODEL_HEALTH_STATUS_LIST); }); -App::get('/v1/health/time') +Http::get('/v1/health/time') ->desc('Get time') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -339,7 +377,7 @@ App::get('/v1/health/time') $response->dynamic(new Document($output), Response::MODEL_HEALTH_TIME); }); -App::get('/v1/health/queue/webhooks') +Http::get('/v1/health/queue/webhooks') ->desc('Get webhooks queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -366,7 +404,7 @@ App::get('/v1/health/queue/webhooks') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); -App::get('/v1/health/queue/logs') +Http::get('/v1/health/queue/logs') ->desc('Get logs queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -393,7 +431,7 @@ App::get('/v1/health/queue/logs') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); -App::get('/v1/health/certificate') +Http::get('/v1/health/certificate') ->desc('Get the SSL certificate for a domain') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -443,7 +481,7 @@ App::get('/v1/health/certificate') ]), Response::MODEL_HEALTH_CERTIFICATE); }, ['response']); -App::get('/v1/health/queue/certificates') +Http::get('/v1/health/queue/certificates') ->desc('Get certificates queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -470,7 +508,7 @@ App::get('/v1/health/queue/certificates') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); -App::get('/v1/health/queue/builds') +Http::get('/v1/health/queue/builds') ->desc('Get builds queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -497,7 +535,7 @@ App::get('/v1/health/queue/builds') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); -App::get('/v1/health/queue/databases') +Http::get('/v1/health/queue/databases') ->desc('Get databases queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -525,7 +563,7 @@ App::get('/v1/health/queue/databases') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); -App::get('/v1/health/queue/deletes') +Http::get('/v1/health/queue/deletes') ->desc('Get deletes queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -552,7 +590,7 @@ App::get('/v1/health/queue/deletes') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); -App::get('/v1/health/queue/mails') +Http::get('/v1/health/queue/mails') ->desc('Get mails queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -579,7 +617,7 @@ App::get('/v1/health/queue/mails') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); -App::get('/v1/health/queue/messaging') +Http::get('/v1/health/queue/messaging') ->desc('Get messaging queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -606,7 +644,7 @@ App::get('/v1/health/queue/messaging') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); -App::get('/v1/health/queue/migrations') +Http::get('/v1/health/queue/migrations') ->desc('Get migrations queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -633,7 +671,7 @@ App::get('/v1/health/queue/migrations') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); -App::get('/v1/health/queue/functions') +Http::get('/v1/health/queue/functions') ->desc('Get functions queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -660,7 +698,7 @@ App::get('/v1/health/queue/functions') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); -App::get('/v1/health/queue/usage') +Http::get('/v1/health/queue/usage') ->desc('Get usage queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -687,7 +725,7 @@ App::get('/v1/health/queue/usage') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }); -App::get('/v1/health/queue/usage-dump') +Http::get('/v1/health/queue/usage-dump') ->desc('Get usage dump queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -714,7 +752,7 @@ App::get('/v1/health/queue/usage-dump') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }); -App::get('/v1/health/storage/local') +Http::get('/v1/health/storage/local') ->desc('Get local storage') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -757,7 +795,7 @@ App::get('/v1/health/storage/local') $response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS); }); -App::get('/v1/health/storage') +Http::get('/v1/health/storage') ->desc('Get storage') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -798,7 +836,7 @@ App::get('/v1/health/storage') $response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS); }); -App::get('/v1/health/anti-virus') +Http::get('/v1/health/anti-virus') ->desc('Get antivirus') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -837,7 +875,7 @@ App::get('/v1/health/anti-virus') $response->dynamic(new Document($output), Response::MODEL_HEALTH_ANTIVIRUS); }); -App::get('/v1/health/queue/failed/:name') +Http::get('/v1/health/queue/failed/:name') ->desc('Get number of failed queue jobs') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -878,7 +916,7 @@ App::get('/v1/health/queue/failed/:name') $response->dynamic(new Document([ 'size' => $failed ]), Response::MODEL_HEALTH_QUEUE); }); -App::get('/v1/health/stats') // Currently only used internally +Http::get('/v1/health/stats') // Currently only used internally ->desc('Get system stats') ->groups(['api', 'health']) ->label('scope', 'root') diff --git a/app/controllers/api/locale.php b/app/controllers/api/locale.php index 2917bc8416..fcaf0c03cb 100644 --- a/app/controllers/api/locale.php +++ b/app/controllers/api/locale.php @@ -3,12 +3,12 @@ use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use MaxMind\Db\Reader; -use Utopia\App; use Utopia\Config\Config; use Utopia\Database\Document; +use Utopia\Http\Http; use Utopia\Locale\Locale; -App::get('/v1/locale') +Http::get('/v1/locale') ->desc('Get user locale') ->groups(['api', 'locale']) ->label('scope', 'locale.read') @@ -68,8 +68,8 @@ App::get('/v1/locale') $response->dynamic(new Document($output), Response::MODEL_LOCALE); }); -App::get('/v1/locale/codes') - ->desc('List locale codes') +Http::get('/v1/locale/codes') + ->desc('List Locale Codes') ->groups(['api', 'locale']) ->label('scope', 'locale.read') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) @@ -90,7 +90,7 @@ App::get('/v1/locale/codes') ]), Response::MODEL_LOCALE_CODE_LIST); }); -App::get('/v1/locale/countries') +Http::get('/v1/locale/countries') ->desc('List countries') ->groups(['api', 'locale']) ->label('scope', 'locale.read') @@ -123,7 +123,7 @@ App::get('/v1/locale/countries') $response->dynamic(new Document(['countries' => $output, 'total' => \count($output)]), Response::MODEL_COUNTRY_LIST); }); -App::get('/v1/locale/countries/eu') +Http::get('/v1/locale/countries/eu') ->desc('List EU countries') ->groups(['api', 'locale']) ->label('scope', 'locale.read') @@ -158,7 +158,7 @@ App::get('/v1/locale/countries/eu') $response->dynamic(new Document(['countries' => $output, 'total' => \count($output)]), Response::MODEL_COUNTRY_LIST); }); -App::get('/v1/locale/countries/phones') +Http::get('/v1/locale/countries/phones') ->desc('List countries phone codes') ->groups(['api', 'locale']) ->label('scope', 'locale.read') @@ -192,7 +192,7 @@ App::get('/v1/locale/countries/phones') $response->dynamic(new Document(['phones' => $output, 'total' => \count($output)]), Response::MODEL_PHONE_LIST); }); -App::get('/v1/locale/continents') +Http::get('/v1/locale/continents') ->desc('List continents') ->groups(['api', 'locale']) ->label('scope', 'locale.read') @@ -224,7 +224,7 @@ App::get('/v1/locale/continents') $response->dynamic(new Document(['continents' => $output, 'total' => \count($output)]), Response::MODEL_CONTINENT_LIST); }); -App::get('/v1/locale/currencies') +Http::get('/v1/locale/currencies') ->desc('List currencies') ->groups(['api', 'locale']) ->label('scope', 'locale.read') @@ -247,7 +247,7 @@ App::get('/v1/locale/currencies') }); -App::get('/v1/locale/languages') +Http::get('/v1/locale/languages') ->desc('List languages') ->groups(['api', 'locale']) ->label('scope', 'locale.read') diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 7da0348a8f..8beb38c7ca 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -20,7 +20,6 @@ use Appwrite\Utopia\Database\Validator\Queries\Targets; use Appwrite\Utopia\Database\Validator\Queries\Topics; use Appwrite\Utopia\Response; use MaxMind\Db\Reader; -use Utopia\App; use Utopia\Audit\Audit; use Utopia\Database\Database; use Utopia\Database\DateTime; @@ -30,25 +29,27 @@ use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; +use Utopia\Database\Validator\Authorization\Input; use Utopia\Database\Validator\Datetime as DatetimeValidator; use Utopia\Database\Validator\Queries; use Utopia\Database\Validator\Query\Limit; use Utopia\Database\Validator\Query\Offset; use Utopia\Database\Validator\Roles; use Utopia\Database\Validator\UID; +use Utopia\Http\Http; +use Utopia\Http\Validator\ArrayList; +use Utopia\Http\Validator\Boolean; +use Utopia\Http\Validator\Integer; +use Utopia\Http\Validator\JSON; +use Utopia\Http\Validator\Range; +use Utopia\Http\Validator\Text; +use Utopia\Http\Validator\WhiteList; use Utopia\Locale\Locale; use Utopia\System\System; -use Utopia\Validator\ArrayList; -use Utopia\Validator\Boolean; -use Utopia\Validator\Integer; -use Utopia\Validator\JSON; -use Utopia\Validator\Range; -use Utopia\Validator\Text; -use Utopia\Validator\WhiteList; use function Swoole\Coroutine\batch; -App::post('/v1/messaging/providers/mailgun') +Http::post('/v1/messaging/providers/mailgun') ->desc('Create Mailgun provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.create') @@ -135,7 +136,7 @@ App::post('/v1/messaging/providers/mailgun') ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::post('/v1/messaging/providers/sendgrid') +Http::post('/v1/messaging/providers/sendgrid') ->desc('Create Sendgrid provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.create') @@ -210,7 +211,7 @@ App::post('/v1/messaging/providers/sendgrid') ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::post('/v1/messaging/providers/smtp') +Http::post('/v1/messaging/providers/smtp') ->desc('Create SMTP provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.create') @@ -298,7 +299,7 @@ App::post('/v1/messaging/providers/smtp') ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::post('/v1/messaging/providers/msg91') +Http::post('/v1/messaging/providers/msg91') ->desc('Create Msg91 provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.create') @@ -374,7 +375,7 @@ App::post('/v1/messaging/providers/msg91') ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::post('/v1/messaging/providers/telesign') +Http::post('/v1/messaging/providers/telesign') ->desc('Create Telesign provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.create') @@ -451,7 +452,7 @@ App::post('/v1/messaging/providers/telesign') ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::post('/v1/messaging/providers/textmagic') +Http::post('/v1/messaging/providers/textmagic') ->desc('Create Textmagic provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.create') @@ -528,7 +529,7 @@ App::post('/v1/messaging/providers/textmagic') ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::post('/v1/messaging/providers/twilio') +Http::post('/v1/messaging/providers/twilio') ->desc('Create Twilio provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.create') @@ -605,7 +606,7 @@ App::post('/v1/messaging/providers/twilio') ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::post('/v1/messaging/providers/vonage') +Http::post('/v1/messaging/providers/vonage') ->desc('Create Vonage provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.create') @@ -682,7 +683,7 @@ App::post('/v1/messaging/providers/vonage') ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::post('/v1/messaging/providers/fcm') +Http::post('/v1/messaging/providers/fcm') ->desc('Create FCM provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.create') @@ -745,7 +746,7 @@ App::post('/v1/messaging/providers/fcm') ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::post('/v1/messaging/providers/apns') +Http::post('/v1/messaging/providers/apns') ->desc('Create APNS provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.create') @@ -831,7 +832,7 @@ App::post('/v1/messaging/providers/apns') ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::get('/v1/messaging/providers') +Http::get('/v1/messaging/providers') ->desc('List providers') ->groups(['api', 'messaging']) ->label('scope', 'providers.read') @@ -846,7 +847,8 @@ App::get('/v1/messaging/providers') ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->inject('dbForProject') ->inject('response') - ->action(function (array $queries, string $search, Database $dbForProject, Response $response) { + ->inject('authorization') + ->action(function (array $queries, string $search, Database $dbForProject, Response $response, Authorization $authorization) { try { $queries = Query::parseQueries($queries); } catch (QueryException $e) { @@ -867,7 +869,7 @@ App::get('/v1/messaging/providers') if ($cursor) { $providerId = $cursor->getValue(); - $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId)); + $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('providers', $providerId)); if ($cursorDocument->isEmpty()) { throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Provider '{$providerId}' for the 'cursor' value not found."); @@ -882,7 +884,7 @@ App::get('/v1/messaging/providers') ]), Response::MODEL_PROVIDER_LIST); }); -App::get('/v1/messaging/providers/:providerId/logs') +Http::get('/v1/messaging/providers/:providerId/logs') ->desc('List provider logs') ->groups(['api', 'messaging']) ->label('scope', 'providers.read') @@ -970,7 +972,7 @@ App::get('/v1/messaging/providers/:providerId/logs') ]), Response::MODEL_LOG_LIST); }); -App::get('/v1/messaging/providers/:providerId') +Http::get('/v1/messaging/providers/:providerId') ->desc('Get provider') ->groups(['api', 'messaging']) ->label('scope', 'providers.read') @@ -994,7 +996,7 @@ App::get('/v1/messaging/providers/:providerId') $response->dynamic($provider, Response::MODEL_PROVIDER); }); -App::patch('/v1/messaging/providers/mailgun/:providerId') +Http::patch('/v1/messaging/providers/mailgun/:providerId') ->desc('Update Mailgun provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.update') @@ -1100,7 +1102,7 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::patch('/v1/messaging/providers/sendgrid/:providerId') +Http::patch('/v1/messaging/providers/sendgrid/:providerId') ->desc('Update Sendgrid provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.update') @@ -1191,7 +1193,7 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId') ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::patch('/v1/messaging/providers/smtp/:providerId') +Http::patch('/v1/messaging/providers/smtp/:providerId') ->desc('Update SMTP provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.update') @@ -1313,7 +1315,7 @@ App::patch('/v1/messaging/providers/smtp/:providerId') ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::patch('/v1/messaging/providers/msg91/:providerId') +Http::patch('/v1/messaging/providers/msg91/:providerId') ->desc('Update Msg91 provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.update') @@ -1393,7 +1395,7 @@ App::patch('/v1/messaging/providers/msg91/:providerId') ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::patch('/v1/messaging/providers/telesign/:providerId') +Http::patch('/v1/messaging/providers/telesign/:providerId') ->desc('Update Telesign provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.update') @@ -1475,7 +1477,7 @@ App::patch('/v1/messaging/providers/telesign/:providerId') ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::patch('/v1/messaging/providers/textmagic/:providerId') +Http::patch('/v1/messaging/providers/textmagic/:providerId') ->desc('Update Textmagic provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.update') @@ -1557,7 +1559,7 @@ App::patch('/v1/messaging/providers/textmagic/:providerId') ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::patch('/v1/messaging/providers/twilio/:providerId') +Http::patch('/v1/messaging/providers/twilio/:providerId') ->desc('Update Twilio provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.update') @@ -1639,7 +1641,7 @@ App::patch('/v1/messaging/providers/twilio/:providerId') ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::patch('/v1/messaging/providers/vonage/:providerId') +Http::patch('/v1/messaging/providers/vonage/:providerId') ->desc('Update Vonage provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.update') @@ -1721,7 +1723,7 @@ App::patch('/v1/messaging/providers/vonage/:providerId') ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::patch('/v1/messaging/providers/fcm/:providerId') +Http::patch('/v1/messaging/providers/fcm/:providerId') ->desc('Update FCM provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.update') @@ -1790,7 +1792,7 @@ App::patch('/v1/messaging/providers/fcm/:providerId') }); -App::patch('/v1/messaging/providers/apns/:providerId') +Http::patch('/v1/messaging/providers/apns/:providerId') ->desc('Update APNS provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.update') @@ -1885,7 +1887,7 @@ App::patch('/v1/messaging/providers/apns/:providerId') ->dynamic($provider, Response::MODEL_PROVIDER); }); -App::delete('/v1/messaging/providers/:providerId') +Http::delete('/v1/messaging/providers/:providerId') ->desc('Delete provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.delete') @@ -1920,7 +1922,7 @@ App::delete('/v1/messaging/providers/:providerId') ->noContent(); }); -App::post('/v1/messaging/topics') +Http::post('/v1/messaging/topics') ->desc('Create topic') ->groups(['api', 'messaging']) ->label('audits.event', 'topic.create') @@ -1963,7 +1965,7 @@ App::post('/v1/messaging/topics') ->dynamic($topic, Response::MODEL_TOPIC); }); -App::get('/v1/messaging/topics') +Http::get('/v1/messaging/topics') ->desc('List topics') ->groups(['api', 'messaging']) ->label('scope', 'topics.read') @@ -1978,7 +1980,8 @@ App::get('/v1/messaging/topics') ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->inject('dbForProject') ->inject('response') - ->action(function (array $queries, string $search, Database $dbForProject, Response $response) { + ->inject('authorization') + ->action(function (array $queries, string $search, Database $dbForProject, Response $response, Authorization $authorization) { try { $queries = Query::parseQueries($queries); } catch (QueryException $e) { @@ -1999,7 +2002,7 @@ App::get('/v1/messaging/topics') if ($cursor) { $topicId = $cursor->getValue(); - $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); + $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId)); if ($cursorDocument->isEmpty()) { throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Topic '{$topicId}' for the 'cursor' value not found."); @@ -2014,7 +2017,7 @@ App::get('/v1/messaging/topics') ]), Response::MODEL_TOPIC_LIST); }); -App::get('/v1/messaging/topics/:topicId/logs') +Http::get('/v1/messaging/topics/:topicId/logs') ->desc('List topic logs') ->groups(['api', 'messaging']) ->label('scope', 'topics.read') @@ -2031,7 +2034,8 @@ App::get('/v1/messaging/topics/:topicId/logs') ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->action(function (string $topicId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { + ->inject('authorization') + ->action(function (string $topicId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) { $topic = $dbForProject->getDocument('topics', $topicId); if ($topic->isEmpty()) { @@ -2103,7 +2107,7 @@ App::get('/v1/messaging/topics/:topicId/logs') ]), Response::MODEL_LOG_LIST); }); -App::get('/v1/messaging/topics/:topicId') +Http::get('/v1/messaging/topics/:topicId') ->desc('Get topic') ->groups(['api', 'messaging']) ->label('scope', 'topics.read') @@ -2128,7 +2132,7 @@ App::get('/v1/messaging/topics/:topicId') ->dynamic($topic, Response::MODEL_TOPIC); }); -App::patch('/v1/messaging/topics/:topicId') +Http::patch('/v1/messaging/topics/:topicId') ->desc('Update topic') ->groups(['api', 'messaging']) ->label('audits.event', 'topic.update') @@ -2172,7 +2176,7 @@ App::patch('/v1/messaging/topics/:topicId') ->dynamic($topic, Response::MODEL_TOPIC); }); -App::delete('/v1/messaging/topics/:topicId') +Http::delete('/v1/messaging/topics/:topicId') ->desc('Delete topic') ->groups(['api', 'messaging']) ->label('audits.event', 'topic.delete') @@ -2212,7 +2216,7 @@ App::delete('/v1/messaging/topics/:topicId') ->noContent(); }); -App::post('/v1/messaging/topics/:topicId/subscribers') +Http::post('/v1/messaging/topics/:topicId/subscribers') ->desc('Create subscriber') ->groups(['api', 'messaging']) ->label('audits.event', 'subscriber.create') @@ -2232,28 +2236,27 @@ App::post('/v1/messaging/topics/:topicId/subscribers') ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $subscriberId, string $topicId, string $targetId, Event $queueForEvents, Database $dbForProject, Response $response) { + ->inject('authorization') + ->action(function (string $subscriberId, string $topicId, string $targetId, Event $queueForEvents, Database $dbForProject, Response $response, Authorization $authorization) { $subscriberId = $subscriberId == 'unique()' ? ID::unique() : $subscriberId; - $topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); + $topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId)); if ($topic->isEmpty()) { throw new Exception(Exception::TOPIC_NOT_FOUND); } - $validator = new Authorization('subscribe'); - - if (!$validator->isValid($topic->getAttribute('subscribe'))) { - throw new Exception(Exception::USER_UNAUTHORIZED, $validator->getDescription()); + if (!$authorization->isValid(new Input('subscribe', $topic->getAttribute('subscribe')))) { + throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription()); } - $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId)); + $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId)); if ($target->isEmpty()) { throw new Exception(Exception::USER_TARGET_NOT_FOUND); } - $user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId'))); + $user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId'))); $subscriber = new Document([ '$id' => $subscriberId, @@ -2286,7 +2289,7 @@ App::post('/v1/messaging/topics/:topicId/subscribers') default => throw new Exception(Exception::TARGET_PROVIDER_INVALID_TYPE), }; - Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute( + $authorization->skip(fn () => $dbForProject->increaseDocumentAttribute( 'topics', $topicId, $totalAttribute, @@ -2308,7 +2311,7 @@ App::post('/v1/messaging/topics/:topicId/subscribers') ->dynamic($subscriber, Response::MODEL_SUBSCRIBER); }); -App::get('/v1/messaging/topics/:topicId/subscribers') +Http::get('/v1/messaging/topics/:topicId/subscribers') ->desc('List subscribers') ->groups(['api', 'messaging']) ->label('scope', 'subscribers.read') @@ -2324,7 +2327,8 @@ App::get('/v1/messaging/topics/:topicId/subscribers') ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->inject('dbForProject') ->inject('response') - ->action(function (string $topicId, array $queries, string $search, Database $dbForProject, Response $response) { + ->inject('authorization') + ->action(function (string $topicId, array $queries, string $search, Database $dbForProject, Response $response, Authorization $authorization) { try { $queries = Query::parseQueries($queries); } catch (QueryException $e) { @@ -2335,7 +2339,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers') $queries[] = Query::search('search', $search); } - $topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); + $topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId)); if ($topic->isEmpty()) { throw new Exception(Exception::TOPIC_NOT_FOUND); @@ -2353,7 +2357,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers') if ($cursor) { $subscriberId = $cursor->getValue(); - $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('subscribers', $subscriberId)); + $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('subscribers', $subscriberId)); if ($cursorDocument->isEmpty()) { throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Subscriber '{$subscriberId}' for the 'cursor' value not found."); @@ -2364,10 +2368,10 @@ App::get('/v1/messaging/topics/:topicId/subscribers') $subscribers = $dbForProject->find('subscribers', $queries); - $subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject) { - return function () use ($subscriber, $dbForProject) { - $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId'))); - $user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId'))); + $subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject, $authorization) { + return function () use ($subscriber, $dbForProject, $authorization) { + $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId'))); + $user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId'))); return $subscriber ->setAttribute('target', $target) @@ -2382,7 +2386,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers') ]), Response::MODEL_SUBSCRIBER_LIST); }); -App::get('/v1/messaging/subscribers/:subscriberId/logs') +Http::get('/v1/messaging/subscribers/:subscriberId/logs') ->desc('List subscriber logs') ->groups(['api', 'messaging']) ->label('scope', 'subscribers.read') @@ -2399,7 +2403,8 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs') ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->action(function (string $subscriberId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { + ->inject('authorization') + ->action(function (string $subscriberId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) { $subscriber = $dbForProject->getDocument('subscribers', $subscriberId); if ($subscriber->isEmpty()) { @@ -2471,7 +2476,7 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs') ]), Response::MODEL_LOG_LIST); }); -App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId') +Http::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId') ->desc('Get subscriber') ->groups(['api', 'messaging']) ->label('scope', 'subscribers.read') @@ -2486,8 +2491,9 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId') ->param('subscriberId', '', new UID(), 'Subscriber ID.') ->inject('dbForProject') ->inject('response') - ->action(function (string $topicId, string $subscriberId, Database $dbForProject, Response $response) { - $topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); + ->inject('authorization') + ->action(function (string $topicId, string $subscriberId, Database $dbForProject, Response $response, Authorization $authorization) { + $topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId)); if ($topic->isEmpty()) { throw new Exception(Exception::TOPIC_NOT_FOUND); @@ -2499,8 +2505,8 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId') throw new Exception(Exception::SUBSCRIBER_NOT_FOUND); } - $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId'))); - $user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId'))); + $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId'))); + $user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId'))); $subscriber ->setAttribute('target', $target) @@ -2510,7 +2516,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId') ->dynamic($subscriber, Response::MODEL_SUBSCRIBER); }); -App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId') +Http::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId') ->desc('Delete subscriber') ->groups(['api', 'messaging']) ->label('audits.event', 'subscriber.delete') @@ -2529,8 +2535,9 @@ App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId') ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $topicId, string $subscriberId, Event $queueForEvents, Database $dbForProject, Response $response) { - $topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); + ->inject('authorization') + ->action(function (string $topicId, string $subscriberId, Event $queueForEvents, Database $dbForProject, Response $response, Authorization $authorization) { + $topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId)); if ($topic->isEmpty()) { throw new Exception(Exception::TOPIC_NOT_FOUND); @@ -2553,7 +2560,7 @@ App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId') default => throw new Exception(Exception::TARGET_PROVIDER_INVALID_TYPE), }; - Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute( + $authorization->skip(fn () => $dbForProject->decreaseDocumentAttribute( 'topics', $topicId, $totalAttribute, @@ -2569,7 +2576,7 @@ App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId') ->noContent(); }); -App::post('/v1/messaging/messages/email') +Http::post('/v1/messaging/messages/email') ->desc('Create email') ->groups(['api', 'messaging']) ->label('audits.event', 'message.create') @@ -2721,7 +2728,7 @@ App::post('/v1/messaging/messages/email') ->dynamic($message, Response::MODEL_MESSAGE); }); -App::post('/v1/messaging/messages/sms') +Http::post('/v1/messaging/messages/sms') ->desc('Create SMS') ->groups(['api', 'messaging']) ->label('audits.event', 'message.create') @@ -2837,7 +2844,7 @@ App::post('/v1/messaging/messages/sms') ->dynamic($message, Response::MODEL_MESSAGE); }); -App::post('/v1/messaging/messages/push') +Http::post('/v1/messaging/messages/push') ->desc('Create push notification') ->groups(['api', 'messaging']) ->label('audits.event', 'message.create') @@ -3013,7 +3020,7 @@ App::post('/v1/messaging/messages/push') ->dynamic($message, Response::MODEL_MESSAGE); }); -App::get('/v1/messaging/messages') +Http::get('/v1/messaging/messages') ->desc('List messages') ->groups(['api', 'messaging']) ->label('scope', 'messages.read') @@ -3028,7 +3035,8 @@ App::get('/v1/messaging/messages') ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->inject('dbForProject') ->inject('response') - ->action(function (array $queries, string $search, Database $dbForProject, Response $response) { + ->inject('authorization') + ->action(function (array $queries, string $search, Database $dbForProject, Response $response, Authorization $authorization) { try { $queries = Query::parseQueries($queries); } catch (QueryException $e) { @@ -3049,7 +3057,7 @@ App::get('/v1/messaging/messages') if ($cursor) { $messageId = $cursor->getValue(); - $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('messages', $messageId)); + $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('messages', $messageId)); if ($cursorDocument->isEmpty()) { throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Message '{$messageId}' for the 'cursor' value not found."); @@ -3064,7 +3072,7 @@ App::get('/v1/messaging/messages') ]), Response::MODEL_MESSAGE_LIST); }); -App::get('/v1/messaging/messages/:messageId/logs') +Http::get('/v1/messaging/messages/:messageId/logs') ->desc('List message logs') ->groups(['api', 'messaging']) ->label('scope', 'messages.read') @@ -3081,7 +3089,8 @@ App::get('/v1/messaging/messages/:messageId/logs') ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->action(function (string $messageId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { + ->inject('authorization') + ->action(function (string $messageId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) { $message = $dbForProject->getDocument('messages', $messageId); if ($message->isEmpty()) { @@ -3153,7 +3162,7 @@ App::get('/v1/messaging/messages/:messageId/logs') ]), Response::MODEL_LOG_LIST); }); -App::get('/v1/messaging/messages/:messageId/targets') +Http::get('/v1/messaging/messages/:messageId/targets') ->desc('List message targets') ->groups(['api', 'messaging']) ->label('scope', 'messages.read') @@ -3218,7 +3227,7 @@ App::get('/v1/messaging/messages/:messageId/targets') ]), Response::MODEL_TARGET_LIST); }); -App::get('/v1/messaging/messages/:messageId') +Http::get('/v1/messaging/messages/:messageId') ->desc('Get message') ->groups(['api', 'messaging']) ->label('scope', 'messages.read') @@ -3242,7 +3251,7 @@ App::get('/v1/messaging/messages/:messageId') $response->dynamic($message, Response::MODEL_MESSAGE); }); -App::patch('/v1/messaging/messages/email/:messageId') +Http::patch('/v1/messaging/messages/email/:messageId') ->desc('Update email') ->groups(['api', 'messaging']) ->label('audits.event', 'message.update') @@ -3442,7 +3451,7 @@ App::patch('/v1/messaging/messages/email/:messageId') ->dynamic($message, Response::MODEL_MESSAGE); }); -App::patch('/v1/messaging/messages/sms/:messageId') +Http::patch('/v1/messaging/messages/sms/:messageId') ->desc('Update SMS') ->groups(['api', 'messaging']) ->label('audits.event', 'message.update') @@ -3597,7 +3606,7 @@ App::patch('/v1/messaging/messages/sms/:messageId') ->dynamic($message, Response::MODEL_MESSAGE); }); -App::patch('/v1/messaging/messages/push/:messageId') +Http::patch('/v1/messaging/messages/push/:messageId') ->desc('Update push notification') ->groups(['api', 'messaging']) ->label('audits.event', 'message.update') @@ -3835,7 +3844,7 @@ App::patch('/v1/messaging/messages/push/:messageId') ->dynamic($message, Response::MODEL_MESSAGE); }); -App::delete('/v1/messaging/messages/:messageId') +Http::delete('/v1/messaging/messages/:messageId') ->desc('Delete message') ->groups(['api', 'messaging']) ->label('audits.event', 'message.delete') diff --git a/app/controllers/api/migrations.php b/app/controllers/api/migrations.php index 374a5575a7..552a4293f1 100644 --- a/app/controllers/api/migrations.php +++ b/app/controllers/api/migrations.php @@ -9,7 +9,6 @@ use Appwrite\Role; use Appwrite\Utopia\Database\Validator\Queries\Migrations; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; -use Utopia\App; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; @@ -17,21 +16,22 @@ use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Database\Validator\UID; +use Utopia\Http\Http; +use Utopia\Http\Validator\ArrayList; +use Utopia\Http\Validator\Host; +use Utopia\Http\Validator\Integer; +use Utopia\Http\Validator\Text; +use Utopia\Http\Validator\URL; +use Utopia\Http\Validator\WhiteList; use Utopia\Migration\Sources\Appwrite; use Utopia\Migration\Sources\Firebase; use Utopia\Migration\Sources\NHost; use Utopia\Migration\Sources\Supabase; use Utopia\System\System; -use Utopia\Validator\ArrayList; -use Utopia\Validator\Host; -use Utopia\Validator\Integer; -use Utopia\Validator\Text; -use Utopia\Validator\URL; -use Utopia\Validator\WhiteList; include_once __DIR__ . '/../shared/api.php'; -App::post('/v1/migrations/appwrite') +Http::post('/v1/migrations/appwrite') ->groups(['api', 'migrations']) ->desc('Migrate Appwrite data') ->label('scope', 'migrations.write') @@ -85,7 +85,7 @@ App::post('/v1/migrations/appwrite') ->dynamic($migration, Response::MODEL_MIGRATION); }); -App::post('/v1/migrations/firebase/oauth') +Http::post('/v1/migrations/firebase/oauth') ->groups(['api', 'migrations']) ->desc('Migrate Firebase data (OAuth)') ->label('scope', 'migrations.write') @@ -187,7 +187,7 @@ App::post('/v1/migrations/firebase/oauth') ->dynamic($migration, Response::MODEL_MIGRATION); }); -App::post('/v1/migrations/firebase') +Http::post('/v1/migrations/firebase') ->groups(['api', 'migrations']) ->desc('Migrate Firebase data (Service Account)') ->label('scope', 'migrations.write') @@ -247,7 +247,7 @@ App::post('/v1/migrations/firebase') ->dynamic($migration, Response::MODEL_MIGRATION); }); -App::post('/v1/migrations/supabase') +Http::post('/v1/migrations/supabase') ->groups(['api', 'migrations']) ->desc('Migrate Supabase data') ->label('scope', 'migrations.write') @@ -307,7 +307,7 @@ App::post('/v1/migrations/supabase') ->dynamic($migration, Response::MODEL_MIGRATION); }); -App::post('/v1/migrations/nhost') +Http::post('/v1/migrations/nhost') ->groups(['api', 'migrations']) ->desc('Migrate NHost data') ->label('scope', 'migrations.write') @@ -369,7 +369,7 @@ App::post('/v1/migrations/nhost') ->dynamic($migration, Response::MODEL_MIGRATION); }); -App::get('/v1/migrations') +Http::get('/v1/migrations') ->groups(['api', 'migrations']) ->desc('List migrations') ->label('scope', 'migrations.read') @@ -422,7 +422,7 @@ App::get('/v1/migrations') ]), Response::MODEL_MIGRATION_LIST); }); -App::get('/v1/migrations/:migrationId') +Http::get('/v1/migrations/:migrationId') ->groups(['api', 'migrations']) ->desc('Get migration') ->label('scope', 'migrations.read') @@ -446,7 +446,7 @@ App::get('/v1/migrations/:migrationId') $response->dynamic($migration, Response::MODEL_MIGRATION); }); -App::get('/v1/migrations/appwrite/report') +Http::get('/v1/migrations/appwrite/report') ->groups(['api', 'migrations']) ->desc('Generate a report on Appwrite data') ->label('scope', 'migrations.write') @@ -488,7 +488,7 @@ App::get('/v1/migrations/appwrite/report') ->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT); }); -App::get('/v1/migrations/firebase/report') +Http::get('/v1/migrations/firebase/report') ->groups(['api', 'migrations']) ->desc('Generate a report on Firebase data') ->label('scope', 'migrations.write') @@ -535,7 +535,7 @@ App::get('/v1/migrations/firebase/report') ->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT); }); -App::get('/v1/migrations/firebase/report/oauth') +Http::get('/v1/migrations/firebase/report/oauth') ->groups(['api', 'migrations']) ->desc('Generate a report on Firebase data using OAuth') ->label('scope', 'migrations.write') @@ -626,7 +626,7 @@ App::get('/v1/migrations/firebase/report/oauth') ->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT); }); -App::get('/v1/migrations/firebase/connect') +Http::get('/v1/migrations/firebase/connect') ->desc('Authorize with Firebase') ->groups(['api', 'migrations']) ->label('scope', 'migrations.write') @@ -668,7 +668,7 @@ App::get('/v1/migrations/firebase/connect') ->redirect($url); }); -App::get('/v1/migrations/firebase/redirect') +Http::get('/v1/migrations/firebase/redirect') ->desc('Capture and receive data on Firebase authorization') ->groups(['api', 'migrations']) ->label('scope', 'public') @@ -780,8 +780,8 @@ App::get('/v1/migrations/firebase/redirect') ->redirect($redirect); }); -App::get('/v1/migrations/firebase/projects') - ->desc('List Firebase projects') +Http::get('/v1/migrations/firebase/projects') + ->desc('List Firebase Projects') ->groups(['api', 'migrations']) ->label('scope', 'migrations.read') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) @@ -869,8 +869,8 @@ App::get('/v1/migrations/firebase/projects') ]), Response::MODEL_MIGRATION_FIREBASE_PROJECT_LIST); }); -App::get('/v1/migrations/firebase/deauthorize') - ->desc('Revoke Appwrite\'s authorization to access Firebase projects') +Http::get('/v1/migrations/firebase/deauthorize') + ->desc('Revoke Appwrite\'s authorization to access Firebase Projects') ->groups(['api', 'migrations']) ->label('scope', 'migrations.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) @@ -897,7 +897,7 @@ App::get('/v1/migrations/firebase/deauthorize') $response->noContent(); }); -App::get('/v1/migrations/supabase/report') +Http::get('/v1/migrations/supabase/report') ->groups(['api', 'migrations']) ->desc('Generate a report on Supabase Data') ->label('scope', 'migrations.write') @@ -940,7 +940,7 @@ App::get('/v1/migrations/supabase/report') ->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT); }); -App::get('/v1/migrations/nhost/report') +Http::get('/v1/migrations/nhost/report') ->groups(['api', 'migrations']) ->desc('Generate a report on NHost Data') ->label('scope', 'migrations.write') @@ -983,7 +983,7 @@ App::get('/v1/migrations/nhost/report') ->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT); }); -App::patch('/v1/migrations/:migrationId') +Http::patch('/v1/migrations/:migrationId') ->groups(['api', 'migrations']) ->desc('Retry migration') ->label('scope', 'migrations.write') @@ -1028,7 +1028,7 @@ App::patch('/v1/migrations/:migrationId') $response->noContent(); }); -App::delete('/v1/migrations/:migrationId') +Http::delete('/v1/migrations/:migrationId') ->groups(['api', 'migrations']) ->desc('Delete migration') ->label('scope', 'migrations.write') diff --git a/app/controllers/api/project.php b/app/controllers/api/project.php index 6053326308..8a6f4d942e 100644 --- a/app/controllers/api/project.php +++ b/app/controllers/api/project.php @@ -2,7 +2,6 @@ use Appwrite\Extend\Exception; use Appwrite\Utopia\Response; -use Utopia\App; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate as DuplicateException; @@ -13,10 +12,11 @@ use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Datetime as DateTimeValidator; use Utopia\Database\Validator\UID; -use Utopia\Validator\Text; -use Utopia\Validator\WhiteList; +use Utopia\Http\Http; +use Utopia\Http\Validator\Text; +use Utopia\Http\Validator\WhiteList; -App::get('/v1/project/usage') +Http::get('/v1/project/usage') ->desc('Get project usage stats') ->groups(['api', 'usage']) ->label('scope', 'projects.read') @@ -31,7 +31,8 @@ App::get('/v1/project/usage') ->param('period', '1d', new WhiteList(['1h', '1d']), 'Period used', true) ->inject('response') ->inject('dbForProject') - ->action(function (string $startDate, string $endDate, string $period, Response $response, Database $dbForProject) { + ->inject('authorization') + ->action(function (string $startDate, string $endDate, string $period, Response $response, Database $dbForProject, Authorization $authorization) { $stats = $total = $usage = []; $format = 'Y-m-d 00:00:00'; $firstDay = (new DateTime($startDate))->format($format); @@ -78,7 +79,7 @@ App::get('/v1/project/usage') '1d' => 'Y-m-d\T00:00:00.000P', }; - Authorization::skip(function () use ($dbForProject, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) { + $authorization->skip(function () use ($dbForProject, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) { foreach ($metrics['total'] as $metric) { $result = $dbForProject->findOne('stats', [ Query::equal('metric', [$metric]), @@ -296,8 +297,6 @@ App::get('/v1/project/usage') 'buildsStorageTotal' => $total[METRIC_BUILDS_STORAGE], 'deploymentsStorageTotal' => $total[METRIC_DEPLOYMENTS_STORAGE], 'executionsBreakdown' => $executionsBreakdown, - 'executionsMbSecondsBreakdown' => $executionsMbSecondsBreakdown, - 'buildsMbSecondsBreakdown' => $buildsMbSecondsBreakdown, 'bucketsBreakdown' => $bucketsBreakdown, 'databasesStorageBreakdown' => $databasesStorageBreakdown, 'executionsMbSecondsBreakdown' => $executionsMbSecondsBreakdown, @@ -308,8 +307,8 @@ App::get('/v1/project/usage') // Variables -App::post('/v1/project/variables') - ->desc('Create variable') +Http::post('/v1/project/variables') + ->desc('Create Variable') ->groups(['api']) ->label('scope', 'projects.write') ->label('audits.event', 'variable.create') @@ -363,8 +362,8 @@ App::post('/v1/project/variables') ->dynamic($variable, Response::MODEL_VARIABLE); }); -App::get('/v1/project/variables') - ->desc('List variables') +Http::get('/v1/project/variables') + ->desc('List Variables') ->groups(['api']) ->label('scope', 'projects.read') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) @@ -388,8 +387,8 @@ App::get('/v1/project/variables') ]), Response::MODEL_VARIABLE_LIST); }); -App::get('/v1/project/variables/:variableId') - ->desc('Get variable') +Http::get('/v1/project/variables/:variableId') + ->desc('Get Variable') ->groups(['api']) ->label('scope', 'projects.read') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) @@ -412,8 +411,8 @@ App::get('/v1/project/variables/:variableId') $response->dynamic($variable, Response::MODEL_VARIABLE); }); -App::put('/v1/project/variables/:variableId') - ->desc('Update variable') +Http::put('/v1/project/variables/:variableId') + ->desc('Update Variable') ->groups(['api']) ->label('scope', 'projects.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) @@ -458,8 +457,8 @@ App::put('/v1/project/variables/:variableId') $response->dynamic($variable, Response::MODEL_VARIABLE); }); -App::delete('/v1/project/variables/:variableId') - ->desc('Delete variable') +Http::delete('/v1/project/variables/:variableId') + ->desc('Delete Variable') ->groups(['api']) ->label('scope', 'projects.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 3a8c232195..da5b4b882c 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -13,14 +13,16 @@ use Appwrite\Network\Validator\Origin; use Appwrite\Template\Template; use Appwrite\Utopia\Database\Validator\ProjectId; use Appwrite\Utopia\Database\Validator\Queries\Projects; +use Appwrite\Utopia\Queue\Connections; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use PHPMailer\PHPMailer\PHPMailer; use Utopia\Abuse\Adapters\Database\TimeLimit; -use Utopia\App; use Utopia\Audit\Audit; use Utopia\Cache\Cache; use Utopia\Config\Config; +use Utopia\Database\Adapter\MariaDB; +use Utopia\Database\Adapter\MySQL; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; @@ -30,24 +32,25 @@ 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\Datetime as DatetimeValidator; use Utopia\Database\Validator\UID; use Utopia\Domains\Validator\PublicDomain; use Utopia\DSN\DSN; +use Utopia\Http\Http; +use Utopia\Http\Validator\ArrayList; +use Utopia\Http\Validator\Boolean; +use Utopia\Http\Validator\Hostname; +use Utopia\Http\Validator\Integer; +use Utopia\Http\Validator\Multiple; +use Utopia\Http\Validator\Range; +use Utopia\Http\Validator\Text; +use Utopia\Http\Validator\URL; +use Utopia\Http\Validator\WhiteList; use Utopia\Locale\Locale; -use Utopia\Pools\Group; use Utopia\System\System; -use Utopia\Validator\ArrayList; -use Utopia\Validator\Boolean; -use Utopia\Validator\Hostname; -use Utopia\Validator\Integer; -use Utopia\Validator\Multiple; -use Utopia\Validator\Range; -use Utopia\Validator\Text; -use Utopia\Validator\URL; -use Utopia\Validator\WhiteList; -App::init() +Http::init() ->groups(['projects']) ->inject('project') ->action(function (Document $project) { @@ -56,7 +59,7 @@ App::init() } }); -App::post('/v1/projects') +Http::post('/v1/projects') ->desc('Create project') ->groups(['api', 'projects']) ->label('audits.event', 'projects.create') @@ -86,8 +89,9 @@ App::post('/v1/projects') ->inject('cache') ->inject('pools') ->inject('hooks') - ->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Request $request, Response $response, Database $dbForConsole, Cache $cache, Group $pools, Hooks $hooks) { - + ->inject('authorization') + ->inject('connections') + ->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Request $request, Response $response, Database $dbForConsole, Cache $cache, array $pools, Hooks $hooks, Authorization $authorization, Connections $connections) { $team = $dbForConsole->getDocument('teams', $teamId); if ($team->isEmpty()) { @@ -189,8 +193,21 @@ App::post('/v1/projects') $dsn = new DSN('mysql://' . $dsn); } - $adapter = $pools->get($dsn->getHost())->pop()->getResource(); + $pool = $pools['pools-database-' . $dsn->getHost()]['pool']; + $connectionDsn = $pools['pools-database-' . $dsn->getHost()]['dsn']; + $connection = $pool->get(); + $connections->add($connection, $pool); + + $adapter = match ($connectionDsn->getScheme()) { + 'mariadb' => new MariaDB($connection), + 'mysql' => new MySQL($connection), + default => null + }; + + $adapter->setDatabase($connectionDsn->getPath()); + $dbForProject = new Database($adapter, $cache); + $dbForProject->setAuthorization($authorization); if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $dbForProject @@ -208,10 +225,8 @@ App::post('/v1/projects') $audit = new Audit($dbForProject); $audit->setup(); - $abuse = new TimeLimit('', 0, 1, $dbForProject); $abuse->setup(); - /** @var array $collections */ $collections = Config::getParam('collections', [])['projects'] ?? []; @@ -234,17 +249,17 @@ App::post('/v1/projects') // Collection already exists } } - + $connections->reclaim(); // Hook allowing instant project mirroring during migration // Outside of migration, hook is not registered and has no effect - $hooks->trigger('afterProjectCreation', [ $project, $pools, $cache ]); + $hooks->trigger('afterProjectCreation', [$project, $pools, $cache]); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($project, Response::MODEL_PROJECT); }); -App::get('/v1/projects') +Http::get('/v1/projects') ->desc('List projects') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -259,7 +274,6 @@ App::get('/v1/projects') ->inject('response') ->inject('dbForConsole') ->action(function (array $queries, string $search, Response $response, Database $dbForConsole) { - try { $queries = Query::parseQueries($queries); } catch (QueryException $e) { @@ -297,7 +311,7 @@ App::get('/v1/projects') ]), Response::MODEL_PROJECT_LIST); }); -App::get('/v1/projects/:projectId') +Http::get('/v1/projects/:projectId') ->desc('Get project') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -311,7 +325,6 @@ App::get('/v1/projects/:projectId') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -321,7 +334,7 @@ App::get('/v1/projects/:projectId') $response->dynamic($project, Response::MODEL_PROJECT); }); -App::patch('/v1/projects/:projectId') +Http::patch('/v1/projects/:projectId') ->desc('Update project') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -345,31 +358,34 @@ App::patch('/v1/projects/:projectId') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $name, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } - $project = $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('name', $name) - ->setAttribute('description', $description) - ->setAttribute('logo', $logo) - ->setAttribute('url', $url) - ->setAttribute('legalName', $legalName) - ->setAttribute('legalCountry', $legalCountry) - ->setAttribute('legalState', $legalState) - ->setAttribute('legalCity', $legalCity) - ->setAttribute('legalAddress', $legalAddress) - ->setAttribute('legalTaxId', $legalTaxId) - ->setAttribute('search', implode(' ', [$projectId, $name]))); + $project = $dbForConsole->updateDocument( + 'projects', + $project->getId(), + $project + ->setAttribute('name', $name) + ->setAttribute('description', $description) + ->setAttribute('logo', $logo) + ->setAttribute('url', $url) + ->setAttribute('legalName', $legalName) + ->setAttribute('legalCountry', $legalCountry) + ->setAttribute('legalState', $legalState) + ->setAttribute('legalCity', $legalCity) + ->setAttribute('legalAddress', $legalAddress) + ->setAttribute('legalTaxId', $legalTaxId) + ->setAttribute('search', implode(' ', [$projectId, $name])) + ); $response->dynamic($project, Response::MODEL_PROJECT); }); -App::patch('/v1/projects/:projectId/team') - ->desc('Update project team') +Http::patch('/v1/projects/:projectId/team') + ->desc('Update Project Team') ->groups(['api', 'projects']) ->label('scope', 'projects.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) @@ -383,7 +399,6 @@ App::patch('/v1/projects/:projectId/team') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $teamId, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); $team = $dbForConsole->getDocument('teams', $teamId); @@ -436,7 +451,7 @@ App::patch('/v1/projects/:projectId/team') $response->dynamic($project, Response::MODEL_PROJECT); }); -App::patch('/v1/projects/:projectId/service') +Http::patch('/v1/projects/:projectId/service') ->desc('Update service status') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -452,7 +467,6 @@ App::patch('/v1/projects/:projectId/service') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $service, bool $status, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -467,7 +481,7 @@ App::patch('/v1/projects/:projectId/service') $response->dynamic($project, Response::MODEL_PROJECT); }); -App::patch('/v1/projects/:projectId/service/all') +Http::patch('/v1/projects/:projectId/service/all') ->desc('Update all service status') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -482,7 +496,6 @@ App::patch('/v1/projects/:projectId/service/all') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, bool $status, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -501,7 +514,7 @@ App::patch('/v1/projects/:projectId/service/all') $response->dynamic($project, Response::MODEL_PROJECT); }); -App::patch('/v1/projects/:projectId/api') +Http::patch('/v1/projects/:projectId/api') ->desc('Update API status') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -517,7 +530,6 @@ App::patch('/v1/projects/:projectId/api') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $api, bool $status, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -532,7 +544,7 @@ App::patch('/v1/projects/:projectId/api') $response->dynamic($project, Response::MODEL_PROJECT); }); -App::patch('/v1/projects/:projectId/api/all') +Http::patch('/v1/projects/:projectId/api/all') ->desc('Update all API status') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -547,7 +559,6 @@ App::patch('/v1/projects/:projectId/api/all') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, bool $status, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -566,7 +577,7 @@ App::patch('/v1/projects/:projectId/api/all') $response->dynamic($project, Response::MODEL_PROJECT); }); -App::patch('/v1/projects/:projectId/oauth2') +Http::patch('/v1/projects/:projectId/oauth2') ->desc('Update project OAuth2') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -584,7 +595,6 @@ App::patch('/v1/projects/:projectId/oauth2') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $provider, ?string $appId, ?string $secret, ?bool $enabled, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -610,7 +620,7 @@ App::patch('/v1/projects/:projectId/oauth2') $response->dynamic($project, Response::MODEL_PROJECT); }); -App::patch('/v1/projects/:projectId/auth/session-alerts') +Http::patch('/v1/projects/:projectId/auth/session-alerts') ->desc('Update project sessions emails') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -641,7 +651,7 @@ App::patch('/v1/projects/:projectId/auth/session-alerts') $response->dynamic($project, Response::MODEL_PROJECT); }); -App::patch('/v1/projects/:projectId/auth/limit') +Http::patch('/v1/projects/:projectId/auth/limit') ->desc('Update project users limit') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -656,7 +666,6 @@ App::patch('/v1/projects/:projectId/auth/limit') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -666,13 +675,17 @@ App::patch('/v1/projects/:projectId/auth/limit') $auths = $project->getAttribute('auths', []); $auths['limit'] = $limit; - $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('auths', $auths)); + $dbForConsole->updateDocument( + 'projects', + $project->getId(), + $project + ->setAttribute('auths', $auths) + ); $response->dynamic($project, Response::MODEL_PROJECT); }); -App::patch('/v1/projects/:projectId/auth/duration') +Http::patch('/v1/projects/:projectId/auth/duration') ->desc('Update project authentication duration') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -687,7 +700,6 @@ App::patch('/v1/projects/:projectId/auth/duration') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, int $duration, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -697,13 +709,17 @@ App::patch('/v1/projects/:projectId/auth/duration') $auths = $project->getAttribute('auths', []); $auths['duration'] = $duration; - $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('auths', $auths)); + $dbForConsole->updateDocument( + 'projects', + $project->getId(), + $project + ->setAttribute('auths', $auths) + ); $response->dynamic($project, Response::MODEL_PROJECT); }); -App::patch('/v1/projects/:projectId/auth/:method') +Http::patch('/v1/projects/:projectId/auth/:method') ->desc('Update project auth method status. Use this endpoint to enable or disable a given auth method for this project.') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -719,10 +735,9 @@ App::patch('/v1/projects/:projectId/auth/:method') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $method, bool $status, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); - $auth = Config::getParam('auth')[$method] ?? []; - $authKey = $auth['key'] ?? ''; + $authConfig = Config::getParam('auth')[$method] ?? []; + $authKey = $authConfig['key'] ?? ''; $status = ($status === '1' || $status === 'true' || $status === 1 || $status === true); if ($project->isEmpty()) { @@ -737,7 +752,7 @@ App::patch('/v1/projects/:projectId/auth/:method') $response->dynamic($project, Response::MODEL_PROJECT); }); -App::patch('/v1/projects/:projectId/auth/password-history') +Http::patch('/v1/projects/:projectId/auth/password-history') ->desc('Update authentication password history. Use this endpoint to set the number of password history to save and 0 to disable password history.') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -752,7 +767,6 @@ App::patch('/v1/projects/:projectId/auth/password-history') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -762,13 +776,17 @@ App::patch('/v1/projects/:projectId/auth/password-history') $auths = $project->getAttribute('auths', []); $auths['passwordHistory'] = $limit; - $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('auths', $auths)); + $dbForConsole->updateDocument( + 'projects', + $project->getId(), + $project + ->setAttribute('auths', $auths) + ); $response->dynamic($project, Response::MODEL_PROJECT); }); -App::patch('/v1/projects/:projectId/auth/password-dictionary') +Http::patch('/v1/projects/:projectId/auth/password-dictionary') ->desc('Update authentication password dictionary status. Use this endpoint to enable or disable the dicitonary check for user password') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -783,7 +801,6 @@ App::patch('/v1/projects/:projectId/auth/password-dictionary') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, bool $enabled, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -793,13 +810,17 @@ App::patch('/v1/projects/:projectId/auth/password-dictionary') $auths = $project->getAttribute('auths', []); $auths['passwordDictionary'] = $enabled; - $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('auths', $auths)); + $dbForConsole->updateDocument( + 'projects', + $project->getId(), + $project + ->setAttribute('auths', $auths) + ); $response->dynamic($project, Response::MODEL_PROJECT); }); -App::patch('/v1/projects/:projectId/auth/personal-data') +Http::patch('/v1/projects/:projectId/auth/personal-data') ->desc('Enable or disable checking user passwords for similarity with their personal data.') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -814,7 +835,6 @@ App::patch('/v1/projects/:projectId/auth/personal-data') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, bool $enabled, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -824,13 +844,17 @@ App::patch('/v1/projects/:projectId/auth/personal-data') $auths = $project->getAttribute('auths', []); $auths['personalDataCheck'] = $enabled; - $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('auths', $auths)); + $dbForConsole->updateDocument( + 'projects', + $project->getId(), + $project + ->setAttribute('auths', $auths) + ); $response->dynamic($project, Response::MODEL_PROJECT); }); -App::patch('/v1/projects/:projectId/auth/max-sessions') +Http::patch('/v1/projects/:projectId/auth/max-sessions') ->desc('Update project user sessions limit') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -845,7 +869,6 @@ App::patch('/v1/projects/:projectId/auth/max-sessions') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -855,13 +878,17 @@ App::patch('/v1/projects/:projectId/auth/max-sessions') $auths = $project->getAttribute('auths', []); $auths['maxSessions'] = $limit; - $dbForConsole->updateDocument('projects', $project->getId(), $project - ->setAttribute('auths', $auths)); + $dbForConsole->updateDocument( + 'projects', + $project->getId(), + $project + ->setAttribute('auths', $auths) + ); $response->dynamic($project, Response::MODEL_PROJECT); }); -App::patch('/v1/projects/:projectId/auth/mock-numbers') +Http::patch('/v1/projects/:projectId/auth/mock-numbers') ->desc('Update the mock numbers for the project') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -900,7 +927,7 @@ App::patch('/v1/projects/:projectId/auth/mock-numbers') $response->dynamic($project, Response::MODEL_PROJECT); }); -App::delete('/v1/projects/:projectId') +Http::delete('/v1/projects/:projectId') ->desc('Delete project') ->groups(['api', 'projects']) ->label('audits.event', 'projects.delete') @@ -935,7 +962,7 @@ App::delete('/v1/projects/:projectId') // Webhooks -App::post('/v1/projects/:projectId/webhooks') +Http::post('/v1/projects/:projectId/webhooks') ->desc('Create webhook') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -956,14 +983,13 @@ App::post('/v1/projects/:projectId/webhooks') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $name, bool $enabled, array $events, string $url, bool $security, string $httpUser, string $httpPass, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } - $security = (bool) filter_var($security, FILTER_VALIDATE_BOOLEAN); + $security = (bool)filter_var($security, FILTER_VALIDATE_BOOLEAN); $webhook = new Document([ '$id' => ID::unique(), @@ -993,7 +1019,7 @@ App::post('/v1/projects/:projectId/webhooks') ->dynamic($webhook, Response::MODEL_WEBHOOK); }); -App::get('/v1/projects/:projectId/webhooks') +Http::get('/v1/projects/:projectId/webhooks') ->desc('List webhooks') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -1007,7 +1033,6 @@ App::get('/v1/projects/:projectId/webhooks') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1025,7 +1050,7 @@ App::get('/v1/projects/:projectId/webhooks') ]), Response::MODEL_WEBHOOK_LIST); }); -App::get('/v1/projects/:projectId/webhooks/:webhookId') +Http::get('/v1/projects/:projectId/webhooks/:webhookId') ->desc('Get webhook') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -1040,7 +1065,6 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1059,7 +1083,7 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId') $response->dynamic($webhook, Response::MODEL_WEBHOOK); }); -App::put('/v1/projects/:projectId/webhooks/:webhookId') +Http::put('/v1/projects/:projectId/webhooks/:webhookId') ->desc('Update webhook') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1081,7 +1105,6 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $webhookId, string $name, bool $enabled, array $events, string $url, bool $security, string $httpUser, string $httpPass, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1118,7 +1141,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') $response->dynamic($webhook, Response::MODEL_WEBHOOK); }); -App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature') +Http::patch('/v1/projects/:projectId/webhooks/:webhookId/signature') ->desc('Update webhook signature key') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1133,7 +1156,6 @@ App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1157,7 +1179,7 @@ App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature') $response->dynamic($webhook, Response::MODEL_WEBHOOK); }); -App::delete('/v1/projects/:projectId/webhooks/:webhookId') +Http::delete('/v1/projects/:projectId/webhooks/:webhookId') ->desc('Delete webhook') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1171,7 +1193,6 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1196,7 +1217,7 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId') // Keys -App::post('/v1/projects/:projectId/keys') +Http::post('/v1/projects/:projectId/keys') ->desc('Create key') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1213,7 +1234,6 @@ App::post('/v1/projects/:projectId/keys') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1246,7 +1266,7 @@ App::post('/v1/projects/:projectId/keys') ->dynamic($key, Response::MODEL_KEY); }); -App::get('/v1/projects/:projectId/keys') +Http::get('/v1/projects/:projectId/keys') ->desc('List keys') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -1260,7 +1280,6 @@ App::get('/v1/projects/:projectId/keys') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1278,7 +1297,7 @@ App::get('/v1/projects/:projectId/keys') ]), Response::MODEL_KEY_LIST); }); -App::get('/v1/projects/:projectId/keys/:keyId') +Http::get('/v1/projects/:projectId/keys/:keyId') ->desc('Get key') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -1293,7 +1312,6 @@ App::get('/v1/projects/:projectId/keys/:keyId') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1312,7 +1330,7 @@ App::get('/v1/projects/:projectId/keys/:keyId') $response->dynamic($key, Response::MODEL_KEY); }); -App::put('/v1/projects/:projectId/keys/:keyId') +Http::put('/v1/projects/:projectId/keys/:keyId') ->desc('Update key') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1330,7 +1348,6 @@ App::put('/v1/projects/:projectId/keys/:keyId') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $keyId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1358,7 +1375,7 @@ App::put('/v1/projects/:projectId/keys/:keyId') $response->dynamic($key, Response::MODEL_KEY); }); -App::delete('/v1/projects/:projectId/keys/:keyId') +Http::delete('/v1/projects/:projectId/keys/:keyId') ->desc('Delete key') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1372,7 +1389,6 @@ App::delete('/v1/projects/:projectId/keys/:keyId') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1397,7 +1413,7 @@ App::delete('/v1/projects/:projectId/keys/:keyId') // JWT Keys -App::post('/v1/projects/:projectId/jwts') +Http::post('/v1/projects/:projectId/jwts') ->groups(['api', 'projects']) ->desc('Create JWT') ->label('scope', 'projects.write') @@ -1432,7 +1448,7 @@ App::post('/v1/projects/:projectId/jwts') // Platforms -App::post('/v1/projects/:projectId/platforms') +Http::post('/v1/projects/:projectId/platforms') ->desc('Create platform') ->groups(['api', 'projects']) ->label('audits.event', 'platforms.create') @@ -1483,7 +1499,7 @@ App::post('/v1/projects/:projectId/platforms') ->dynamic($platform, Response::MODEL_PLATFORM); }); -App::get('/v1/projects/:projectId/platforms') +Http::get('/v1/projects/:projectId/platforms') ->desc('List platforms') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -1497,7 +1513,6 @@ App::get('/v1/projects/:projectId/platforms') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1515,7 +1530,7 @@ App::get('/v1/projects/:projectId/platforms') ]), Response::MODEL_PLATFORM_LIST); }); -App::get('/v1/projects/:projectId/platforms/:platformId') +Http::get('/v1/projects/:projectId/platforms/:platformId') ->desc('Get platform') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -1530,7 +1545,6 @@ App::get('/v1/projects/:projectId/platforms/:platformId') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1549,7 +1563,7 @@ App::get('/v1/projects/:projectId/platforms/:platformId') $response->dynamic($platform, Response::MODEL_PLATFORM); }); -App::put('/v1/projects/:projectId/platforms/:platformId') +Http::put('/v1/projects/:projectId/platforms/:platformId') ->desc('Update platform') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1596,7 +1610,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId') $response->dynamic($platform, Response::MODEL_PLATFORM); }); -App::delete('/v1/projects/:projectId/platforms/:platformId') +Http::delete('/v1/projects/:projectId/platforms/:platformId') ->desc('Delete platform') ->groups(['api', 'projects']) ->label('audits.event', 'platforms.delete') @@ -1611,7 +1625,6 @@ App::delete('/v1/projects/:projectId/platforms/:platformId') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1636,7 +1649,7 @@ App::delete('/v1/projects/:projectId/platforms/:platformId') // CUSTOM SMTP and Templates -App::patch('/v1/projects/:projectId/smtp') +Http::patch('/v1/projects/:projectId/smtp') ->desc('Update SMTP') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1659,7 +1672,6 @@ App::patch('/v1/projects/:projectId/smtp') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, bool $enabled, string $senderName, string $senderEmail, string $replyTo, string $host, int $port, string $username, string $password, string $secure, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1726,7 +1738,7 @@ App::patch('/v1/projects/:projectId/smtp') $response->dynamic($project, Response::MODEL_PROJECT); }); -App::post('/v1/projects/:projectId/smtp/tests') +Http::post('/v1/projects/:projectId/smtp/tests') ->desc('Create SMTP test') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1785,7 +1797,7 @@ App::post('/v1/projects/:projectId/smtp/tests') return $response->noContent(); }); -App::get('/v1/projects/:projectId/templates/sms/:type/:locale') +Http::get('/v1/projects/:projectId/templates/sms/:type/:locale') ->desc('Get custom SMS template') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1801,7 +1813,6 @@ App::get('/v1/projects/:projectId/templates/sms/:type/:locale') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) { - throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED); $project = $dbForConsole->getDocument('projects', $projectId); @@ -1811,7 +1822,7 @@ App::get('/v1/projects/:projectId/templates/sms/:type/:locale') } $templates = $project->getAttribute('templates', []); - $template = $templates['sms.' . $type . '-' . $locale] ?? null; + $template = $templates['sms.' . $type . '-' . $locale] ?? null; if (is_null($template)) { $template = [ @@ -1826,7 +1837,7 @@ App::get('/v1/projects/:projectId/templates/sms/:type/:locale') }); -App::get('/v1/projects/:projectId/templates/email/:type/:locale') +Http::get('/v1/projects/:projectId/templates/email/:type/:locale') ->desc('Get custom email template') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1842,7 +1853,6 @@ App::get('/v1/projects/:projectId/templates/email/:type/:locale') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1850,7 +1860,7 @@ App::get('/v1/projects/:projectId/templates/email/:type/:locale') } $templates = $project->getAttribute('templates', []); - $template = $templates['email.' . $type . '-' . $locale] ?? null; + $template = $templates['email.' . $type . '-' . $locale] ?? null; $localeObj = new Locale($locale); if (is_null($template)) { @@ -1858,7 +1868,7 @@ App::get('/v1/projects/:projectId/templates/email/:type/:locale') $message ->setParam('{{hello}}', $localeObj->getText("emails.{$type}.hello")) ->setParam('{{footer}}', $localeObj->getText("emails.{$type}.footer")) - ->setParam('{{body}}', $localeObj->getText('emails.' . $type . '.body'), escapeHtml: false) + ->setParam('{{body}}', $localeObj->getText('emails.' . $type . '.body'), escape: false) ->setParam('{{thanks}}', $localeObj->getText("emails.{$type}.thanks")) ->setParam('{{signature}}', $localeObj->getText("emails.{$type}.signature")) ->setParam('{{direction}}', $localeObj->getText('settings.direction')); @@ -1878,7 +1888,7 @@ App::get('/v1/projects/:projectId/templates/email/:type/:locale') $response->dynamic(new Document($template), Response::MODEL_EMAIL_TEMPLATE); }); -App::patch('/v1/projects/:projectId/templates/sms/:type/:locale') +Http::patch('/v1/projects/:projectId/templates/sms/:type/:locale') ->desc('Update custom SMS template') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1895,7 +1905,6 @@ App::patch('/v1/projects/:projectId/templates/sms/:type/:locale') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $type, string $locale, string $message, Response $response, Database $dbForConsole) { - throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED); $project = $dbForConsole->getDocument('projects', $projectId); @@ -1918,7 +1927,7 @@ App::patch('/v1/projects/:projectId/templates/sms/:type/:locale') ]), Response::MODEL_SMS_TEMPLATE); }); -App::patch('/v1/projects/:projectId/templates/email/:type/:locale') +Http::patch('/v1/projects/:projectId/templates/email/:type/:locale') ->desc('Update custom email templates') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1939,7 +1948,6 @@ App::patch('/v1/projects/:projectId/templates/email/:type/:locale') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $type, string $locale, string $subject, string $message, string $senderName, string $senderEmail, string $replyTo, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1968,7 +1976,7 @@ App::patch('/v1/projects/:projectId/templates/email/:type/:locale') ]), Response::MODEL_EMAIL_TEMPLATE); }); -App::delete('/v1/projects/:projectId/templates/sms/:type/:locale') +Http::delete('/v1/projects/:projectId/templates/sms/:type/:locale') ->desc('Reset custom SMS template') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1984,7 +1992,6 @@ App::delete('/v1/projects/:projectId/templates/sms/:type/:locale') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) { - throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED); $project = $dbForConsole->getDocument('projects', $projectId); @@ -1994,7 +2001,7 @@ App::delete('/v1/projects/:projectId/templates/sms/:type/:locale') } $templates = $project->getAttribute('templates', []); - $template = $templates['sms.' . $type . '-' . $locale] ?? null; + $template = $templates['sms.' . $type . '-' . $locale] ?? null; if (is_null($template)) { throw new Exception(Exception::PROJECT_TEMPLATE_DEFAULT_DELETION); @@ -2011,7 +2018,7 @@ App::delete('/v1/projects/:projectId/templates/sms/:type/:locale') ]), Response::MODEL_SMS_TEMPLATE); }); -App::delete('/v1/projects/:projectId/templates/email/:type/:locale') +Http::delete('/v1/projects/:projectId/templates/email/:type/:locale') ->desc('Reset custom email template') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -2027,7 +2034,6 @@ App::delete('/v1/projects/:projectId/templates/email/:type/:locale') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -2035,7 +2041,7 @@ App::delete('/v1/projects/:projectId/templates/email/:type/:locale') } $templates = $project->getAttribute('templates', []); - $template = $templates['email.' . $type . '-' . $locale] ?? null; + $template = $templates['email.' . $type . '-' . $locale] ?? null; if (is_null($template)) { throw new Exception(Exception::PROJECT_TEMPLATE_DEFAULT_DELETION); diff --git a/app/controllers/api/proxy.php b/app/controllers/api/proxy.php index 84484a7209..3ca019c9ce 100644 --- a/app/controllers/api/proxy.php +++ b/app/controllers/api/proxy.php @@ -7,7 +7,6 @@ use Appwrite\Extend\Exception; use Appwrite\Network\Validator\CNAME; use Appwrite\Utopia\Database\Validator\Queries\Rules; use Appwrite\Utopia\Response; -use Utopia\App; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Query as QueryException; @@ -15,13 +14,14 @@ use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Database\Validator\UID; use Utopia\Domains\Domain; +use Utopia\Http\Http; +use Utopia\Http\Validator\Domain as ValidatorDomain; +use Utopia\Http\Validator\Text; +use Utopia\Http\Validator\WhiteList; use Utopia\Logger\Log; use Utopia\System\System; -use Utopia\Validator\Domain as ValidatorDomain; -use Utopia\Validator\Text; -use Utopia\Validator\WhiteList; -App::post('/v1/proxy/rules') +Http::post('/v1/proxy/rules') ->groups(['api', 'proxy']) ->desc('Create rule') ->label('scope', 'rules.write') @@ -147,7 +147,7 @@ App::post('/v1/proxy/rules') ->dynamic($rule, Response::MODEL_PROXY_RULE); }); -App::get('/v1/proxy/rules') +Http::get('/v1/proxy/rules') ->groups(['api', 'proxy']) ->desc('List rules') ->label('scope', 'rules.read') @@ -210,7 +210,7 @@ App::get('/v1/proxy/rules') ]), Response::MODEL_PROXY_RULE_LIST); }); -App::get('/v1/proxy/rules/:ruleId') +Http::get('/v1/proxy/rules/:ruleId') ->groups(['api', 'proxy']) ->desc('Get rule') ->label('scope', 'rules.read') @@ -239,7 +239,7 @@ App::get('/v1/proxy/rules/:ruleId') $response->dynamic($rule, Response::MODEL_PROXY_RULE); }); -App::delete('/v1/proxy/rules/:ruleId') +Http::delete('/v1/proxy/rules/:ruleId') ->groups(['api', 'proxy']) ->desc('Delete rule') ->label('scope', 'rules.write') @@ -276,8 +276,8 @@ App::delete('/v1/proxy/rules/:ruleId') $response->noContent(); }); -App::patch('/v1/proxy/rules/:ruleId/verification') - ->desc('Update rule verification status') +Http::patch('/v1/proxy/rules/:ruleId/verification') + ->desc('Update Rule Verification Status') ->groups(['api', 'proxy']) ->label('scope', 'rules.write') ->label('event', 'rules.[ruleId].update') diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index ca248c2628..e0408acb37 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -11,8 +11,8 @@ use Appwrite\OpenSSL\OpenSSL; use Appwrite\Utopia\Database\Validator\CustomId; use Appwrite\Utopia\Database\Validator\Queries\Buckets; use Appwrite\Utopia\Database\Validator\Queries\Files; +use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; -use Utopia\App; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; @@ -23,8 +23,16 @@ use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; +use Utopia\Database\Validator\Authorization\Input; use Utopia\Database\Validator\Permissions; use Utopia\Database\Validator\UID; +use Utopia\Http\Http; +use Utopia\Http\Validator\ArrayList; +use Utopia\Http\Validator\Boolean; +use Utopia\Http\Validator\HexColor; +use Utopia\Http\Validator\Range; +use Utopia\Http\Validator\Text; +use Utopia\Http\Validator\WhiteList; use Utopia\Image\Image; use Utopia\Storage\Compression\Algorithms\GZIP; use Utopia\Storage\Compression\Algorithms\Zstd; @@ -35,16 +43,9 @@ 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\ArrayList; -use Utopia\Validator\Boolean; -use Utopia\Validator\HexColor; -use Utopia\Validator\Range; -use Utopia\Validator\Text; -use Utopia\Validator\WhiteList; -App::post('/v1/storage/buckets') +Http::post('/v1/storage/buckets') ->desc('Create bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.write') @@ -142,7 +143,7 @@ App::post('/v1/storage/buckets') ->dynamic($bucket, Response::MODEL_BUCKET); }); -App::get('/v1/storage/buckets') +Http::get('/v1/storage/buckets') ->desc('List buckets') ->groups(['api', 'storage']) ->label('scope', 'buckets.read') @@ -196,7 +197,7 @@ App::get('/v1/storage/buckets') ]), Response::MODEL_BUCKET_LIST); }); -App::get('/v1/storage/buckets/:bucketId') +Http::get('/v1/storage/buckets/:bucketId') ->desc('Get bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.read') @@ -221,7 +222,7 @@ App::get('/v1/storage/buckets/:bucketId') $response->dynamic($bucket, Response::MODEL_BUCKET); }); -App::put('/v1/storage/buckets/:bucketId') +Http::put('/v1/storage/buckets/:bucketId') ->desc('Update bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.write') @@ -288,7 +289,7 @@ App::put('/v1/storage/buckets/:bucketId') $response->dynamic($bucket, Response::MODEL_BUCKET); }); -App::delete('/v1/storage/buckets/:bucketId') +Http::delete('/v1/storage/buckets/:bucketId') ->desc('Delete bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.write') @@ -329,7 +330,7 @@ App::delete('/v1/storage/buckets/:bucketId') $response->noContent(); }); -App::post('/v1/storage/buckets/:bucketId/files') +Http::post('/v1/storage/buckets/:bucketId/files') ->alias('/v1/storage/files', ['bucketId' => 'default']) ->desc('Create file') ->groups(['api', 'storage']) @@ -361,19 +362,19 @@ App::post('/v1/storage/buckets/:bucketId/files') ->inject('mode') ->inject('deviceForFiles') ->inject('deviceForLocal') - ->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, string $mode, Device $deviceForFiles, Device $deviceForLocal) { + ->inject('authorization') + ->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, string $mode, Device $deviceForFiles, Device $deviceForLocal, Authorization $authorization) { - $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $isAPIKey = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } - $validator = new Authorization(Database::PERMISSION_CREATE); - if (!$validator->isValid($bucket->getCreate())) { + if (!$authorization->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) { throw new Exception(Exception::USER_UNAUTHORIZED); } @@ -397,7 +398,7 @@ App::post('/v1/storage/buckets/:bucketId/files') } // Users can only manage their own roles, API keys and Admin users can manage any - $roles = Authorization::getRoles(); + $roles = $authorization->getRoles(); if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) { foreach (Database::PERMISSIONS as $type) { foreach ($permissions as $permission) { @@ -410,7 +411,7 @@ App::post('/v1/storage/buckets/:bucketId/files') $permission->getIdentifier(), $permission->getDimension() ))->toString(); - if (!Authorization::isRole($role)) { + if (!$authorization->isRole($role)) { throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')'); } } @@ -630,11 +631,10 @@ App::post('/v1/storage/buckets/:bucketId/files') * However as with chunk upload even if we are updating, we are essentially creating a file * adding it's new chunk so we validate create permission instead of update */ - $validator = new Authorization(Database::PERMISSION_CREATE); - if (!$validator->isValid($bucket->getCreate())) { + if (!$authorization->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) { throw new Exception(Exception::USER_UNAUTHORIZED); } - $file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); + $file = $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); } } else { if ($file->isEmpty()) { @@ -669,11 +669,10 @@ App::post('/v1/storage/buckets/:bucketId/files') * However as with chunk upload even if we are updating, we are essentially creating a file * adding it's new chunk so we validate create permission instead of update */ - $validator = new Authorization(Database::PERMISSION_CREATE); - if (!$validator->isValid($bucket->getCreate())) { + if (!$authorization->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) { throw new Exception(Exception::USER_UNAUTHORIZED); } - $file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); + $file = $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); } } @@ -690,7 +689,7 @@ App::post('/v1/storage/buckets/:bucketId/files') ->dynamic($file, Response::MODEL_FILE); }); -App::get('/v1/storage/buckets/:bucketId/files') +Http::get('/v1/storage/buckets/:bucketId/files') ->alias('/v1/storage/files', ['bucketId' => 'default']) ->desc('List files') ->groups(['api', 'storage']) @@ -708,19 +707,19 @@ App::get('/v1/storage/buckets/:bucketId/files') ->inject('response') ->inject('dbForProject') ->inject('mode') - ->action(function (string $bucketId, array $queries, string $search, Response $response, Database $dbForProject, string $mode) { - $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + ->inject('authorization') + ->action(function (string $bucketId, array $queries, string $search, Response $response, Database $dbForProject, string $mode, Authorization $authorization) { + $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $isAPIKey = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } $fileSecurity = $bucket->getAttribute('fileSecurity', false); - $validator = new Authorization(Database::PERMISSION_READ); - $valid = $validator->isValid($bucket->getRead()); + $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead())); if (!$fileSecurity && !$valid) { throw new Exception(Exception::USER_UNAUTHORIZED); } @@ -749,7 +748,7 @@ App::get('/v1/storage/buckets/:bucketId/files') if ($fileSecurity && !$valid) { $cursorDocument = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } else { - $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); } if ($cursorDocument->isEmpty()) { @@ -765,8 +764,8 @@ App::get('/v1/storage/buckets/:bucketId/files') $files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries); $total = $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT); } else { - $files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries)); - $total = Authorization::skip(fn () => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT)); + $files = $authorization->skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries)); + $total = $authorization->skip(fn () => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT)); } $response->dynamic(new Document([ @@ -775,7 +774,7 @@ App::get('/v1/storage/buckets/:bucketId/files') ]), Response::MODEL_FILE_LIST); }); -App::get('/v1/storage/buckets/:bucketId/files/:fileId') +Http::get('/v1/storage/buckets/:bucketId/files/:fileId') ->alias('/v1/storage/files/:fileId', ['bucketId' => 'default']) ->desc('Get file') ->groups(['api', 'storage']) @@ -792,19 +791,19 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId') ->inject('response') ->inject('dbForProject') ->inject('mode') - ->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, string $mode) { - $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + ->inject('authorization') + ->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, string $mode, Authorization $authorization) { + $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $isAPIKey = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } $fileSecurity = $bucket->getAttribute('fileSecurity', false); - $validator = new Authorization(Database::PERMISSION_READ); - $valid = $validator->isValid($bucket->getRead()); + $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead())); if (!$fileSecurity && !$valid) { throw new Exception(Exception::USER_UNAUTHORIZED); } @@ -812,7 +811,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId') if ($fileSecurity && !$valid) { $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } else { - $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); } if ($file->isEmpty()) { @@ -822,7 +821,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId') $response->dynamic($file, Response::MODEL_FILE); }); -App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') +Http::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') ->alias('/v1/storage/files/:fileId/preview', ['bucketId' => 'default']) ->desc('Get file preview') ->groups(['api', 'storage']) @@ -857,24 +856,24 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') ->inject('mode') ->inject('deviceForFiles') ->inject('deviceForLocal') - ->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, string $mode, Device $deviceForFiles, Device $deviceForLocal) { + ->inject('authorization') + ->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, string $mode, Device $deviceForFiles, Device $deviceForLocal, Authorization $authorization) { if (!\extension_loaded('imagick')) { throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing'); } - $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $isAPIKey = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } $fileSecurity = $bucket->getAttribute('fileSecurity', false); - $validator = new Authorization(Database::PERMISSION_READ); - $valid = $validator->isValid($bucket->getRead()); + $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead())); if (!$fileSecurity && !$valid) { throw new Exception(Exception::USER_UNAUTHORIZED); } @@ -882,7 +881,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') if ($fileSecurity && !$valid) { $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } else { - $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); } if ($file->isEmpty()) { @@ -998,7 +997,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') unset($image); }); -App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') +Http::get('/v1/storage/buckets/:bucketId/files/:fileId/download') ->alias('/v1/storage/files/:fileId/download', ['bucketId' => 'default']) ->desc('Get file for download') ->groups(['api', 'storage']) @@ -1017,20 +1016,20 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') ->inject('dbForProject') ->inject('mode') ->inject('deviceForFiles') - ->action(function (string $bucketId, string $fileId, Request $request, Response $response, Database $dbForProject, string $mode, Device $deviceForFiles) { + ->inject('authorization') + ->action(function (string $bucketId, string $fileId, Request $request, Response $response, Database $dbForProject, string $mode, Device $deviceForFiles, Authorization $authorization) { - $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $isAPIKey = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } $fileSecurity = $bucket->getAttribute('fileSecurity', false); - $validator = new Authorization(Database::PERMISSION_READ); - $valid = $validator->isValid($bucket->getRead()); + $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead())); if (!$fileSecurity && !$valid) { throw new Exception(Exception::USER_UNAUTHORIZED); } @@ -1038,7 +1037,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') if ($fileSecurity && !$valid) { $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } else { - $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); } if ($file->isEmpty()) { @@ -1138,7 +1137,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') } }); -App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') +Http::get('/v1/storage/buckets/:bucketId/files/:fileId/view') ->alias('/v1/storage/files/:fileId/view', ['bucketId' => 'default']) ->desc('Get file for view') ->groups(['api', 'storage']) @@ -1157,19 +1156,19 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') ->inject('dbForProject') ->inject('mode') ->inject('deviceForFiles') - ->action(function (string $bucketId, string $fileId, Response $response, Request $request, Database $dbForProject, string $mode, Device $deviceForFiles) { - $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + ->inject('authorization') + ->action(function (string $bucketId, string $fileId, Response $response, Request $request, Database $dbForProject, string $mode, Device $deviceForFiles, Authorization $authorization) { + $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $isAPIKey = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } $fileSecurity = $bucket->getAttribute('fileSecurity', false); - $validator = new Authorization(Database::PERMISSION_READ); - $valid = $validator->isValid($bucket->getRead()); + $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead())); if (!$fileSecurity && !$valid) { throw new Exception(Exception::USER_UNAUTHORIZED); } @@ -1177,7 +1176,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') if ($fileSecurity && !$valid) { $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } else { - $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); } if ($file->isEmpty()) { @@ -1290,7 +1289,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') } }); -App::get('/v1/storage/buckets/:bucketId/files/:fileId/push') +Http::get('/v1/storage/buckets/:bucketId/files/:fileId/push') ->desc('Get file for push notification') ->groups(['api', 'storage']) ->label('scope', 'public') @@ -1306,8 +1305,9 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push') ->inject('project') ->inject('mode') ->inject('deviceForFiles') - ->action(function (string $bucketId, string $fileId, string $jwt, Response $response, Request $request, Database $dbForProject, Document $project, string $mode, Device $deviceForFiles) { - $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + ->inject('authorization') + ->action(function (string $bucketId, string $fileId, string $jwt, Response $response, Request $request, Database $dbForProject, Document $project, string $mode, Device $deviceForFiles, Authorization $authorization) { + $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); $decoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0); @@ -1325,14 +1325,14 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push') throw new Exception(Exception::USER_UNAUTHORIZED); } - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $isAPIKey = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } - $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); if ($file->isEmpty()) { throw new Exception(Exception::STORAGE_FILE_NOT_FOUND); @@ -1443,7 +1443,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push') } }); -App::put('/v1/storage/buckets/:bucketId/files/:fileId') +Http::put('/v1/storage/buckets/:bucketId/files/:fileId') ->alias('/v1/storage/files/:fileId', ['bucketId' => 'default']) ->desc('Update file') ->groups(['api', 'storage']) @@ -1470,26 +1470,26 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') ->inject('user') ->inject('mode') ->inject('queueForEvents') - ->action(function (string $bucketId, string $fileId, ?string $name, ?array $permissions, Response $response, Database $dbForProject, Document $user, string $mode, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $bucketId, string $fileId, ?string $name, ?array $permissions, Response $response, Database $dbForProject, Document $user, string $mode, Event $queueForEvents, Authorization $authorization) { - $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $isAPIKey = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } $fileSecurity = $bucket->getAttribute('fileSecurity', false); - $validator = new Authorization(Database::PERMISSION_UPDATE); - $valid = $validator->isValid($bucket->getUpdate()); + $valid = $authorization->isValid(new Input(Database::PERMISSION_UPDATE, $bucket->getUpdate())); if (!$fileSecurity && !$valid) { throw new Exception(Exception::USER_UNAUTHORIZED); } // Read permission should not be required for update - $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); if ($file->isEmpty()) { throw new Exception(Exception::STORAGE_FILE_NOT_FOUND); @@ -1503,7 +1503,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') ]); // Users can only manage their own roles, API keys and Admin users can manage any - $roles = Authorization::getRoles(); + $roles = $authorization->getRoles(); if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles) && !\is_null($permissions)) { foreach (Database::PERMISSIONS as $type) { foreach ($permissions as $permission) { @@ -1516,7 +1516,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') $permission->getIdentifier(), $permission->getDimension() ))->toString(); - if (!Authorization::isRole($role)) { + if (!$authorization->isRole($role)) { throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')'); } } @@ -1536,7 +1536,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') if ($fileSecurity && !$valid) { $file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file); } else { - $file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); + $file = $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); } $queueForEvents @@ -1548,8 +1548,8 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') $response->dynamic($file, Response::MODEL_FILE); }); -App::delete('/v1/storage/buckets/:bucketId/files/:fileId') - ->desc('Delete file') +Http::delete('/v1/storage/buckets/:bucketId/files/:fileId') + ->desc('Delete File') ->groups(['api', 'storage']) ->label('scope', 'files.write') ->label('event', 'buckets.[bucketId].files.[fileId].delete') @@ -1572,32 +1572,32 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') ->inject('mode') ->inject('deviceForFiles') ->inject('queueForDeletes') - ->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Device $deviceForFiles, Delete $queueForDeletes) { - $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + ->inject('authorization') + ->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Device $deviceForFiles, Delete $queueForDeletes, Authorization $authorization) { + $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $isAPIKey = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } $fileSecurity = $bucket->getAttribute('fileSecurity', false); - $validator = new Authorization(Database::PERMISSION_DELETE); - $valid = $validator->isValid($bucket->getDelete()); + $valid = $authorization->isValid(new Input(Database::PERMISSION_DELETE, $bucket->getDelete())); if (!$fileSecurity && !$valid) { throw new Exception(Exception::USER_UNAUTHORIZED); } // Read permission should not be required for delete - $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); if ($file->isEmpty()) { throw new Exception(Exception::STORAGE_FILE_NOT_FOUND); } // Make sure we don't delete the file before the document permission check occurs - if ($fileSecurity && !$valid && !$validator->isValid($file->getDelete())) { + if ($fileSecurity && !$valid && !$authorization->isValid(new Input(Database::PERMISSION_DELETE, $file->getDelete()))) { throw new Exception(Exception::USER_UNAUTHORIZED); } @@ -1621,7 +1621,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') if ($fileSecurity && !$valid) { $deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId); } else { - $deleted = Authorization::skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $deleted = $authorization->skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId)); } if (!$deleted) { @@ -1641,7 +1641,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') $response->noContent(); }); -App::get('/v1/storage/usage') +Http::get('/v1/storage/usage') ->desc('Get storage usage stats') ->groups(['api', 'storage']) ->label('scope', 'files.read') @@ -1654,7 +1654,8 @@ App::get('/v1/storage/usage') ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true) ->inject('response') ->inject('dbForProject') - ->action(function (string $range, Response $response, Database $dbForProject) { + ->inject('authorization') + ->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) { $periods = Config::getParam('usage', []); $stats = $usage = []; @@ -1666,7 +1667,7 @@ App::get('/v1/storage/usage') ]; $total = []; - Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) { + $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) { foreach ($metrics as $metric) { $result = $dbForProject->findOne('stats', [ Query::equal('metric', [$metric]), @@ -1720,7 +1721,7 @@ App::get('/v1/storage/usage') ]), Response::MODEL_USAGE_STORAGE); }); -App::get('/v1/storage/:bucketId/usage') +Http::get('/v1/storage/:bucketId/usage') ->desc('Get bucket usage stats') ->groups(['api', 'storage']) ->label('scope', 'files.read') @@ -1734,7 +1735,8 @@ App::get('/v1/storage/:bucketId/usage') ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true) ->inject('response') ->inject('dbForProject') - ->action(function (string $bucketId, string $range, Response $response, Database $dbForProject) { + ->inject('authorization') + ->action(function (string $bucketId, string $range, Response $response, Database $dbForProject, Authorization $authorization) { $bucket = $dbForProject->getDocument('buckets', $bucketId); @@ -1750,8 +1752,7 @@ App::get('/v1/storage/:bucketId/usage') str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_STORAGE), ]; - - Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) { + $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) { foreach ($metrics as $metric) { $result = $dbForProject->findOne('stats', [ Query::equal('metric', [$metric]), diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index f98cdd721c..16529d3dbd 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -1,6 +1,7 @@ desc('Create team') ->groups(['api', 'teams']) ->label('event', 'teams.[teamId].create') @@ -65,15 +66,16 @@ App::post('/v1/teams') ->inject('user') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); - $isAppUser = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAppUser = Auth::isAppUser($authorization->getRoles()); $teamId = $teamId == 'unique()' ? ID::unique() : $teamId; try { - $team = Authorization::skip(fn () => $dbForProject->createDocument('teams', new Document([ + $team = $authorization->skip(fn () => $dbForProject->createDocument('teams', new Document([ '$id' => $teamId, '$permissions' => [ Permission::read(Role::team($teamId)), @@ -132,7 +134,7 @@ App::post('/v1/teams') ->dynamic($team, Response::MODEL_TEAM); }); -App::get('/v1/teams') +Http::get('/v1/teams') ->desc('List teams') ->groups(['api', 'teams']) ->label('scope', 'teams.read') @@ -190,7 +192,7 @@ App::get('/v1/teams') ]), Response::MODEL_TEAM_LIST); }); -App::get('/v1/teams/:teamId') +Http::get('/v1/teams/:teamId') ->desc('Get team') ->groups(['api', 'teams']) ->label('scope', 'teams.read') @@ -217,7 +219,7 @@ App::get('/v1/teams/:teamId') $response->dynamic($team, Response::MODEL_TEAM); }); -App::get('/v1/teams/:teamId/prefs') +Http::get('/v1/teams/:teamId/prefs') ->desc('Get team preferences') ->groups(['api', 'teams']) ->label('scope', 'teams.read') @@ -245,7 +247,7 @@ App::get('/v1/teams/:teamId/prefs') $response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES); }); -App::put('/v1/teams/:teamId') +Http::put('/v1/teams/:teamId') ->desc('Update name') ->groups(['api', 'teams']) ->label('event', 'teams.[teamId].update') @@ -288,7 +290,7 @@ App::put('/v1/teams/:teamId') $response->dynamic($team, Response::MODEL_TEAM); }); -App::put('/v1/teams/:teamId/prefs') +Http::put('/v1/teams/:teamId/prefs') ->desc('Update preferences') ->groups(['api', 'teams']) ->label('event', 'teams.[teamId].update.prefs') @@ -324,7 +326,7 @@ App::put('/v1/teams/:teamId/prefs') $response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES); }); -App::delete('/v1/teams/:teamId') +Http::delete('/v1/teams/:teamId') ->desc('Delete team') ->groups(['api', 'teams']) ->label('event', 'teams.[teamId].delete') @@ -373,7 +375,7 @@ App::delete('/v1/teams/:teamId') $response->noContent(); }); -App::post('/v1/teams/:teamId/memberships') +Http::post('/v1/teams/:teamId/memberships') ->desc('Create team membership') ->groups(['api', 'teams', 'auth']) ->label('event', 'teams.[teamId].memberships.[membershipId].create') @@ -405,9 +407,10 @@ App::post('/v1/teams/:teamId/memberships') ->inject('queueForMails') ->inject('queueForMessaging') ->inject('queueForEvents') - ->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents) { - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + ->inject('authorization') + ->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, Authorization $authorization) { + $isAPIKey = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); $url = htmlentities($url); if (empty($url)) { @@ -419,8 +422,8 @@ App::post('/v1/teams/:teamId/memberships') if (empty($userId) && empty($email) && empty($phone)) { throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'At least one of userId, email, or phone is required'); } - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); - $isAppUser = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAppUser = Auth::isAppUser($authorization->getRoles()); if (!$isPrivilegedUser && !$isAppUser && empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED); @@ -480,7 +483,7 @@ App::post('/v1/teams/:teamId/memberships') try { $userId = ID::unique(); - $invitee = Authorization::skip(fn () => $dbForProject->createDocument('users', new Document([ + $invitee = $authorization->skip(fn () => $dbForProject->createDocument('users', new Document([ '$id' => $userId, '$permissions' => [ Permission::read(Role::any()), @@ -516,7 +519,7 @@ App::post('/v1/teams/:teamId/memberships') } } - $isOwner = Authorization::isRole('team:' . $team->getId() . '/owner'); + $isOwner = $authorization->isRole('team:' . $team->getId() . '/owner'); if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server) throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to send invitations for this team'); @@ -548,12 +551,12 @@ App::post('/v1/teams/:teamId/memberships') if ($isPrivilegedUser || $isAppUser) { // Allow admin to create membership try { - $membership = Authorization::skip(fn () => $dbForProject->createDocument('memberships', $membership)); + $membership = $authorization->skip(fn () => $dbForProject->createDocument('memberships', $membership)); } catch (Duplicate $th) { throw new Exception(Exception::TEAM_INVITE_ALREADY_EXISTS); } - Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1)); + $authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1)); $dbForProject->purgeCachedDocument('users', $invitee->getId()); } else { @@ -575,7 +578,7 @@ App::post('/v1/teams/:teamId/memberships') $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl'); $message - ->setParam('{{body}}', $body, escapeHtml: false) + ->setParam('{{body}}', $body, escape: false) ->setParam('{{hello}}', $locale->getText("emails.invitation.hello")) ->setParam('{{footer}}', $locale->getText("emails.invitation.footer")) ->setParam('{{thanks}}', $locale->getText("emails.invitation.thanks")) @@ -693,7 +696,7 @@ App::post('/v1/teams/:teamId/memberships') ); }); -App::get('/v1/teams/:teamId/memberships') +Http::get('/v1/teams/:teamId/memberships') ->desc('List team memberships') ->groups(['api', 'teams']) ->label('scope', 'teams.read') @@ -796,7 +799,7 @@ App::get('/v1/teams/:teamId/memberships') ]), Response::MODEL_MEMBERSHIP_LIST); }); -App::get('/v1/teams/:teamId/memberships/:membershipId') +Http::get('/v1/teams/:teamId/memberships/:membershipId') ->desc('Get team membership') ->groups(['api', 'teams']) ->label('scope', 'teams.read') @@ -852,7 +855,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId') $response->dynamic($membership, Response::MODEL_MEMBERSHIP); }); -App::patch('/v1/teams/:teamId/memberships/:membershipId') +Http::patch('/v1/teams/:teamId/memberships/:membershipId') ->desc('Update membership') ->groups(['api', 'teams']) ->label('event', 'teams.[teamId].memberships.[membershipId].update') @@ -874,7 +877,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') ->inject('user') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { $team = $dbForProject->getDocument('teams', $teamId); if ($team->isEmpty()) { @@ -891,9 +895,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') throw new Exception(Exception::USER_NOT_FOUND); } - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); - $isAppUser = Auth::isAppUser(Authorization::getRoles()); - $isOwner = Authorization::isRole('team:' . $team->getId() . '/owner'); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAppUser = Auth::isAppUser($authorization->getRoles()); + $isOwner = $authorization->isRole('team:' . $team->getId() . '/owner'); if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server) throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to modify roles'); @@ -924,7 +928,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') ); }); -App::patch('/v1/teams/:teamId/memberships/:membershipId/status') +Http::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->desc('Update team membership status') ->groups(['api', 'teams']) ->label('event', 'teams.[teamId].memberships.[membershipId].update.status') @@ -950,7 +954,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->inject('project') ->inject('geodb') ->inject('queueForEvents') - ->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents) { + ->inject('authorization') + ->inject('authentication') + ->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents, Authorization $authorization, Authentication $authentication) { $protocol = $request->getProtocol(); $membership = $dbForProject->getDocument('memberships', $membershipId); @@ -959,7 +965,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') throw new Exception(Exception::MEMBERSHIP_NOT_FOUND); } - $team = Authorization::skip(fn () => $dbForProject->getDocument('teams', $teamId)); + $team = $authorization->skip(fn () => $dbForProject->getDocument('teams', $teamId)); if ($team->isEmpty()) { throw new Exception(Exception::TEAM_NOT_FOUND); @@ -994,11 +1000,11 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->setAttribute('confirm', true) ; - Authorization::skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true))); + $authorization->skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true))); // Log user in - Authorization::setRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); $detector = new Detector($request->getUserAgent('UNKNOWN')); $record = $geodb->get($request->getIP()); @@ -1028,13 +1034,13 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') $dbForProject->purgeCachedDocument('users', $user->getId()); - Authorization::setRole(Role::user($userId)->toString()); + $authorization->addRole(Role::user($userId)->toString()); $membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership); $dbForProject->purgeCachedDocument('users', $user->getId()); - Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1)); + $authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1)); $queueForEvents ->setParam('userId', $user->getId()) @@ -1044,13 +1050,13 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') if (!Config::getParam('domainVerification')) { $response - ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])) + ->addHeader('X-Fallback-Cookies', \json_encode([$authentication->getCookieName() => 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')) + ->addCookie($authentication->getCookieName() . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie($authentication->getCookieName(), Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ; $response->dynamic( @@ -1062,7 +1068,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') ); }); -App::delete('/v1/teams/:teamId/memberships/:membershipId') +Http::delete('/v1/teams/:teamId/memberships/:membershipId') ->desc('Delete team membership') ->groups(['api', 'teams']) ->label('event', 'teams.[teamId].memberships.[membershipId].delete') @@ -1080,7 +1086,8 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject, Event $queueForEvents) { + ->inject('authorization') + ->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { $membership = $dbForProject->getDocument('memberships', $membershipId); @@ -1115,7 +1122,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') $dbForProject->purgeCachedDocument('users', $user->getId()); if ($membership->getAttribute('confirm')) { // Count only confirmed members - Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0)); + $authorization->skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0)); } $queueForEvents @@ -1128,7 +1135,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') $response->noContent(); }); -App::get('/v1/teams/:teamId/logs') +Http::get('/v1/teams/:teamId/logs') ->desc('List team logs') ->groups(['api', 'teams']) ->label('scope', 'teams.read') @@ -1145,7 +1152,8 @@ App::get('/v1/teams/:teamId/logs') ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->action(function (string $teamId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { + ->inject('authorization') + ->action(function (string $teamId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) { $team = $dbForProject->getDocument('teams', $teamId); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 571df4fdb2..3d080fd98e 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -22,7 +22,6 @@ use Appwrite\Utopia\Database\Validator\Queries\Users; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use MaxMind\Db\Reader; -use Utopia\App; use Utopia\Audit\Audit; use Utopia\Config\Config; use Utopia\Database\Database; @@ -39,15 +38,16 @@ use Utopia\Database\Validator\Queries; use Utopia\Database\Validator\Query\Limit; use Utopia\Database\Validator\Query\Offset; use Utopia\Database\Validator\UID; +use Utopia\Http\Http; +use Utopia\Http\Validator\ArrayList; +use Utopia\Http\Validator\Assoc; +use Utopia\Http\Validator\Boolean; +use Utopia\Http\Validator\Integer; +use Utopia\Http\Validator\Range; +use Utopia\Http\Validator\Text; +use Utopia\Http\Validator\WhiteList; use Utopia\Locale\Locale; use Utopia\System\System; -use Utopia\Validator\ArrayList; -use Utopia\Validator\Assoc; -use Utopia\Validator\Boolean; -use Utopia\Validator\Integer; -use Utopia\Validator\Range; -use Utopia\Validator\Text; -use Utopia\Validator\WhiteList; /** TODO: Remove function when we move to using utopia/platform */ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks): Document @@ -180,7 +180,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e return $user; } -App::post('/v1/users') +Http::post('/v1/users') ->desc('Create user') ->groups(['api', 'users']) ->label('event', 'users.[userId].create') @@ -211,7 +211,7 @@ App::post('/v1/users') ->dynamic($user, Response::MODEL_USER); }); -App::post('/v1/users/bcrypt') +Http::post('/v1/users/bcrypt') ->desc('Create user with bcrypt password') ->groups(['api', 'users']) ->label('event', 'users.[userId].create') @@ -242,7 +242,7 @@ App::post('/v1/users/bcrypt') ->dynamic($user, Response::MODEL_USER); }); -App::post('/v1/users/md5') +Http::post('/v1/users/md5') ->desc('Create user with MD5 password') ->groups(['api', 'users']) ->label('event', 'users.[userId].create') @@ -273,7 +273,7 @@ App::post('/v1/users/md5') ->dynamic($user, Response::MODEL_USER); }); -App::post('/v1/users/argon2') +Http::post('/v1/users/argon2') ->desc('Create user with Argon2 password') ->groups(['api', 'users']) ->label('event', 'users.[userId].create') @@ -304,7 +304,7 @@ App::post('/v1/users/argon2') ->dynamic($user, Response::MODEL_USER); }); -App::post('/v1/users/sha') +Http::post('/v1/users/sha') ->desc('Create user with SHA password') ->groups(['api', 'users']) ->label('event', 'users.[userId].create') @@ -342,7 +342,7 @@ App::post('/v1/users/sha') ->dynamic($user, Response::MODEL_USER); }); -App::post('/v1/users/phpass') +Http::post('/v1/users/phpass') ->desc('Create user with PHPass password') ->groups(['api', 'users']) ->label('event', 'users.[userId].create') @@ -373,7 +373,7 @@ App::post('/v1/users/phpass') ->dynamic($user, Response::MODEL_USER); }); -App::post('/v1/users/scrypt') +Http::post('/v1/users/scrypt') ->desc('Create user with Scrypt password') ->groups(['api', 'users']) ->label('event', 'users.[userId].create') @@ -417,7 +417,7 @@ App::post('/v1/users/scrypt') ->dynamic($user, Response::MODEL_USER); }); -App::post('/v1/users/scrypt-modified') +Http::post('/v1/users/scrypt-modified') ->desc('Create user with Scrypt modified password') ->groups(['api', 'users']) ->label('event', 'users.[userId].create') @@ -451,7 +451,7 @@ App::post('/v1/users/scrypt-modified') ->dynamic($user, Response::MODEL_USER); }); -App::post('/v1/users/:userId/targets') +Http::post('/v1/users/:userId/targets') ->desc('Create user target') ->groups(['api', 'users']) ->label('audits.event', 'target.create') @@ -540,7 +540,7 @@ App::post('/v1/users/:userId/targets') ->dynamic($target, Response::MODEL_TARGET); }); -App::get('/v1/users') +Http::get('/v1/users') ->desc('List users') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -594,7 +594,7 @@ App::get('/v1/users') ]), Response::MODEL_USER_LIST); }); -App::get('/v1/users/:userId') +Http::get('/v1/users/:userId') ->desc('Get user') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -619,7 +619,7 @@ App::get('/v1/users/:userId') $response->dynamic($user, Response::MODEL_USER); }); -App::get('/v1/users/:userId/prefs') +Http::get('/v1/users/:userId/prefs') ->desc('Get user preferences') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -646,7 +646,7 @@ App::get('/v1/users/:userId/prefs') $response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES); }); -App::get('/v1/users/:userId/targets/:targetId') +Http::get('/v1/users/:userId/targets/:targetId') ->desc('Get user target') ->groups(['api', 'users']) ->label('scope', 'targets.read') @@ -678,7 +678,7 @@ App::get('/v1/users/:userId/targets/:targetId') $response->dynamic($target, Response::MODEL_TARGET); }); -App::get('/v1/users/:userId/sessions') +Http::get('/v1/users/:userId/sessions') ->desc('List user sessions') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -719,7 +719,7 @@ App::get('/v1/users/:userId/sessions') ]), Response::MODEL_SESSION_LIST); }); -App::get('/v1/users/:userId/memberships') +Http::get('/v1/users/:userId/memberships') ->desc('List user memberships') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -758,7 +758,7 @@ App::get('/v1/users/:userId/memberships') ]), Response::MODEL_MEMBERSHIP_LIST); }); -App::get('/v1/users/:userId/logs') +Http::get('/v1/users/:userId/logs') ->desc('List user logs') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -775,7 +775,8 @@ App::get('/v1/users/:userId/logs') ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->action(function (string $userId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { + ->inject('authorization') + ->action(function (string $userId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) { $user = $dbForProject->getDocument('users', $userId); @@ -847,7 +848,7 @@ App::get('/v1/users/:userId/logs') ]), Response::MODEL_LOG_LIST); }); -App::get('/v1/users/:userId/targets') +Http::get('/v1/users/:userId/targets') ->desc('List user targets') ->groups(['api', 'users']) ->label('scope', 'targets.read') @@ -902,7 +903,7 @@ App::get('/v1/users/:userId/targets') ]), Response::MODEL_TARGET_LIST); }); -App::get('/v1/users/identities') +Http::get('/v1/users/identities') ->desc('List identities') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -956,7 +957,7 @@ App::get('/v1/users/identities') ]), Response::MODEL_IDENTITY_LIST); }); -App::patch('/v1/users/:userId/status') +Http::patch('/v1/users/:userId/status') ->desc('Update user status') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.status') @@ -992,7 +993,7 @@ App::patch('/v1/users/:userId/status') $response->dynamic($user, Response::MODEL_USER); }); -App::put('/v1/users/:userId/labels') +Http::put('/v1/users/:userId/labels') ->desc('Update user labels') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.labels') @@ -1029,7 +1030,7 @@ App::put('/v1/users/:userId/labels') $response->dynamic($user, Response::MODEL_USER); }); -App::patch('/v1/users/:userId/verification/phone') +Http::patch('/v1/users/:userId/verification/phone') ->desc('Update phone verification') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.verification') @@ -1064,7 +1065,7 @@ App::patch('/v1/users/:userId/verification/phone') $response->dynamic($user, Response::MODEL_USER); }); -App::patch('/v1/users/:userId/name') +Http::patch('/v1/users/:userId/name') ->desc('Update name') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.name') @@ -1101,7 +1102,7 @@ App::patch('/v1/users/:userId/name') $response->dynamic($user, Response::MODEL_USER); }); -App::patch('/v1/users/:userId/password') +Http::patch('/v1/users/:userId/password') ->desc('Update password') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.password') @@ -1178,7 +1179,7 @@ App::patch('/v1/users/:userId/password') $response->dynamic($user, Response::MODEL_USER); }); -App::patch('/v1/users/:userId/email') +Http::patch('/v1/users/:userId/email') ->desc('Update email') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.email') @@ -1273,7 +1274,7 @@ App::patch('/v1/users/:userId/email') $response->dynamic($user, Response::MODEL_USER); }); -App::patch('/v1/users/:userId/phone') +Http::patch('/v1/users/:userId/phone') ->desc('Update phone') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.phone') @@ -1356,7 +1357,7 @@ App::patch('/v1/users/:userId/phone') $response->dynamic($user, Response::MODEL_USER); }); -App::patch('/v1/users/:userId/verification') +Http::patch('/v1/users/:userId/verification') ->desc('Update email verification') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.verification') @@ -1391,7 +1392,7 @@ App::patch('/v1/users/:userId/verification') $response->dynamic($user, Response::MODEL_USER); }); -App::patch('/v1/users/:userId/prefs') +Http::patch('/v1/users/:userId/prefs') ->desc('Update user preferences') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.prefs') @@ -1424,7 +1425,7 @@ App::patch('/v1/users/:userId/prefs') $response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES); }); -App::patch('/v1/users/:userId/targets/:targetId') +Http::patch('/v1/users/:userId/targets/:targetId') ->desc('Update user target') ->groups(['api', 'users']) ->label('audits.event', 'target.update') @@ -1518,7 +1519,7 @@ App::patch('/v1/users/:userId/targets/:targetId') ->dynamic($target, Response::MODEL_TARGET); }); -App::patch('/v1/users/:userId/mfa') +Http::patch('/v1/users/:userId/mfa') ->desc('Update MFA') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.mfa') @@ -1556,7 +1557,7 @@ App::patch('/v1/users/:userId/mfa') $response->dynamic($user, Response::MODEL_USER); }); -App::get('/v1/users/:userId/mfa/factors') +Http::get('/v1/users/:userId/mfa/factors') ->desc('List factors') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -1589,7 +1590,7 @@ App::get('/v1/users/:userId/mfa/factors') $response->dynamic($factors, Response::MODEL_MFA_FACTORS); }); -App::get('/v1/users/:userId/mfa/recovery-codes') +Http::get('/v1/users/:userId/mfa/recovery-codes') ->desc('Get MFA recovery codes') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -1624,7 +1625,7 @@ App::get('/v1/users/:userId/mfa/recovery-codes') $response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES); }); -App::patch('/v1/users/:userId/mfa/recovery-codes') +Http::patch('/v1/users/:userId/mfa/recovery-codes') ->desc('Create MFA recovery codes') ->groups(['api', 'users']) ->label('event', 'users.[userId].create.mfa.recovery-codes') @@ -1670,7 +1671,7 @@ App::patch('/v1/users/:userId/mfa/recovery-codes') $response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES); }); -App::put('/v1/users/:userId/mfa/recovery-codes') +Http::put('/v1/users/:userId/mfa/recovery-codes') ->desc('Regenerate MFA recovery codes') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.mfa.recovery-codes') @@ -1715,7 +1716,7 @@ App::put('/v1/users/:userId/mfa/recovery-codes') $response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES); }); -App::delete('/v1/users/:userId/mfa/authenticators/:type') +Http::delete('/v1/users/:userId/mfa/authenticators/:type') ->desc('Delete authenticator') ->groups(['api', 'users']) ->label('event', 'users.[userId].delete.mfa') @@ -1757,7 +1758,7 @@ App::delete('/v1/users/:userId/mfa/authenticators/:type') $response->noContent(); }); -App::post('/v1/users/:userId/sessions') +Http::post('/v1/users/:userId/sessions') ->desc('Create session') ->groups(['api', 'users']) ->label('event', 'users.[userId].sessions.[sessionId].create') @@ -1827,7 +1828,7 @@ App::post('/v1/users/:userId/sessions') ->dynamic($session, Response::MODEL_SESSION); }); -App::post('/v1/users/:userId/tokens') +Http::post('/v1/users/:userId/tokens') ->desc('Create token') ->groups(['api', 'users']) ->label('event', 'users.[userId].tokens.[tokenId].create') @@ -1884,7 +1885,7 @@ App::post('/v1/users/:userId/tokens') ->dynamic($token, Response::MODEL_TOKEN); }); -App::delete('/v1/users/:userId/sessions/:sessionId') +Http::delete('/v1/users/:userId/sessions/:sessionId') ->desc('Delete user session') ->groups(['api', 'users']) ->label('event', 'users.[userId].sessions.[sessionId].delete') @@ -1927,7 +1928,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId') $response->noContent(); }); -App::delete('/v1/users/:userId/sessions') +Http::delete('/v1/users/:userId/sessions') ->desc('Delete user sessions') ->groups(['api', 'users']) ->label('event', 'users.[userId].sessions.delete') @@ -1969,7 +1970,7 @@ App::delete('/v1/users/:userId/sessions') $response->noContent(); }); -App::delete('/v1/users/:userId') +Http::delete('/v1/users/:userId') ->desc('Delete user') ->groups(['api', 'users']) ->label('event', 'users.[userId].delete') @@ -2011,7 +2012,7 @@ App::delete('/v1/users/:userId') $response->noContent(); }); -App::delete('/v1/users/:userId/targets/:targetId') +Http::delete('/v1/users/:userId/targets/:targetId') ->desc('Delete user target') ->groups(['api', 'users']) ->label('audits.event', 'target.delete') @@ -2062,7 +2063,7 @@ App::delete('/v1/users/:userId/targets/:targetId') $response->noContent(); }); -App::delete('/v1/users/identities/:identityId') +Http::delete('/v1/users/identities/:identityId') ->desc('Delete identity') ->groups(['api', 'users']) ->label('event', 'users.[userId].identities.[identityId].delete') @@ -2097,7 +2098,7 @@ App::delete('/v1/users/identities/:identityId') return $response->noContent(); }); -App::post('/v1/users/:userId/jwts') +Http::post('/v1/users/:userId/jwts') ->desc('Create user JWT') ->groups(['api', 'users']) ->label('scope', 'users.write') @@ -2147,7 +2148,7 @@ App::post('/v1/users/:userId/jwts') ])]), Response::MODEL_JWT); }); -App::get('/v1/users/usage') +Http::get('/v1/users/usage') ->desc('Get users usage stats') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -2160,8 +2161,8 @@ App::get('/v1/users/usage') ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true) ->inject('response') ->inject('dbForProject') - ->inject('register') - ->action(function (string $range, Response $response, Database $dbForProject) { + ->inject('authorization') + ->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) { $periods = Config::getParam('usage', []); $stats = $usage = []; @@ -2171,7 +2172,7 @@ App::get('/v1/users/usage') METRIC_SESSIONS, ]; - Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) { + $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) { foreach ($metrics as $count => $metric) { $result = $dbForProject->findOne('stats', [ Query::equal('metric', [$metric]), diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index f3381490ec..987d84bb7e 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -8,7 +8,6 @@ use Appwrite\Utopia\Database\Validator\Queries\Installations; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use Appwrite\Vcs\Comment; -use Utopia\App; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Database\Database; @@ -32,17 +31,17 @@ use Utopia\Detector\Adapter\Python; use Utopia\Detector\Adapter\Ruby; use Utopia\Detector\Adapter\Swift; use Utopia\Detector\Detector; +use Utopia\Http\Http; +use Utopia\Http\Validator\Boolean; +use Utopia\Http\Validator\Host; +use Utopia\Http\Validator\Text; use Utopia\System\System; -use Utopia\Validator\Boolean; -use Utopia\Validator\Host; -use Utopia\Validator\Text; use Utopia\VCS\Adapter\Git\GitHub; use Utopia\VCS\Exception\RepositoryNotFound; use function Swoole\Coroutine\batch; -$createGitDeployments = function (GitHub $github, string $providerInstallationId, array $repositories, string $providerBranch, string $providerBranchUrl, string $providerRepositoryName, string $providerRepositoryUrl, string $providerRepositoryOwner, string $providerCommitHash, string $providerCommitAuthor, string $providerCommitAuthorUrl, string $providerCommitMessage, string $providerCommitUrl, string $providerPullRequestId, bool $external, Database $dbForConsole, Build $queueForBuilds, callable $getProjectDB, Request $request) { - $errors = []; +$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, Authorization $auth) { foreach ($repositories as $resource) { try { $resourceType = $resource->getAttribute('resourceType'); @@ -52,11 +51,11 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId } $projectId = $resource->getAttribute('projectId'); - $project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId)); + $project = $auth->skip(fn () => $dbForConsole->getDocument('projects', $projectId)); $dbForProject = $getProjectDB($project); $functionId = $resource->getAttribute('resourceId'); - $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); + $function = $auth->skip(fn () => $dbForProject->getDocument('functions', $functionId)); $functionInternalId = $function->getInternalId(); $deploymentId = ID::unique(); @@ -102,8 +101,8 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId $latestCommentId = ''; - if (!empty($providerPullRequestId) && $function->getAttribute('providerSilentMode', false) === false) { - $latestComment = Authorization::skip(fn () => $dbForConsole->findOne('vcsComments', [ + if (!empty($providerPullRequestId) && $function->getAttribute('providerSilentMode', false)) { + $latestComment = $auth->skip(fn () => $dbForConsole->findOne('vcsComments', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::equal('providerPullRequestId', [$providerPullRequestId]), Query::orderDesc('$createdAt'), @@ -124,7 +123,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId if (!empty($latestCommentId)) { $teamId = $project->getAttribute('teamId', ''); - $latestComment = Authorization::skip(fn () => $dbForConsole->createDocument('vcsComments', new Document([ + $latestComment = $auth->skip(fn () => $dbForConsole->createDocument('vcsComments', new Document([ '$id' => ID::unique(), '$permissions' => [ Permission::read(Role::team(ID::custom($teamId))), @@ -145,7 +144,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId } } } elseif (!empty($providerBranch)) { - $latestComments = Authorization::skip(fn () => $dbForConsole->find('vcsComments', [ + $latestComments = $auth->skip(fn () => $dbForConsole->find('vcsComments', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::equal('providerBranch', [$providerBranch]), Query::orderDesc('$createdAt'), @@ -262,8 +261,8 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId } }; -App::get('/v1/vcs/github/authorize') - ->desc('Install GitHub app') +Http::get('/v1/vcs/github/authorize') + ->desc('Install GitHub App') ->groups(['api', 'vcs']) ->label('scope', 'vcs.read') ->label('sdk.namespace', 'vcs') @@ -304,8 +303,8 @@ App::get('/v1/vcs/github/authorize') ->redirect($url); }); -App::get('/v1/vcs/github/callback') - ->desc('Capture installation and authorization from GitHub app') +Http::get('/v1/vcs/github/callback') + ->desc('Capture installation and authorization from GitHub App') ->groups(['api', 'vcs']) ->label('scope', 'public') ->label('error', __DIR__ . '/../../views/general/error.phtml') @@ -464,7 +463,7 @@ App::get('/v1/vcs/github/callback') ->redirect($redirectSuccess); }); -App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/contents') +Http::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/contents') ->desc('Get files and directories of a VCS repository') ->groups(['api', 'vcs']) ->label('scope', 'vcs.read') @@ -525,7 +524,7 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro ]), Response::MODEL_VCS_CONTENT_LIST); }); -App::post('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/detection') +Http::post('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/detection') ->desc('Detect runtime settings from source code') ->groups(['api', 'vcs']) ->label('scope', 'vcs.write') @@ -597,8 +596,8 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories/:pr $response->dynamic(new Document($detection), Response::MODEL_DETECTION); }); -App::get('/v1/vcs/github/installations/:installationId/providerRepositories') - ->desc('List repositories') +Http::get('/v1/vcs/github/installations/:installationId/providerRepositories') + ->desc('List Repositories') ->groups(['api', 'vcs']) ->label('scope', 'vcs.read') ->label('sdk.namespace', 'vcs') @@ -692,7 +691,7 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories') ]), Response::MODEL_PROVIDER_REPOSITORY_LIST); }); -App::post('/v1/vcs/github/installations/:installationId/providerRepositories') +Http::post('/v1/vcs/github/installations/:installationId/providerRepositories') ->desc('Create repository') ->groups(['api', 'vcs']) ->label('scope', 'vcs.write') @@ -793,7 +792,7 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories') $response->dynamic(new Document($repository), Response::MODEL_PROVIDER_REPOSITORY); }); -App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId') +Http::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId') ->desc('Get repository') ->groups(['api', 'vcs']) ->label('scope', 'vcs.read') @@ -842,8 +841,8 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro $response->dynamic(new Document($repository), Response::MODEL_PROVIDER_REPOSITORY); }); -App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/branches') - ->desc('List repository branches') +Http::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/branches') + ->desc('List Repository Branches') ->groups(['api', 'vcs']) ->label('scope', 'vcs.read') ->label('sdk.namespace', 'vcs') @@ -891,8 +890,8 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro ]), Response::MODEL_BRANCH_LIST); }); -App::post('/v1/vcs/github/events') - ->desc('Create event') +Http::post('/v1/vcs/github/events') + ->desc('Create Event') ->groups(['api', 'vcs']) ->label('scope', 'public') ->inject('gitHub') @@ -901,8 +900,9 @@ App::post('/v1/vcs/github/events') ->inject('dbForConsole') ->inject('getProjectDB') ->inject('queueForBuilds') + ->inject('auth') ->action( - function (GitHub $github, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) { + function (GitHub $github, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds, Authorization $auth) use ($createGitDeployments) { $payload = $request->getRawPayload(); $signatureRemote = $request->getHeader('x-hub-signature-256', ''); $signatureLocal = System::getEnv('_APP_VCS_GITHUB_WEBHOOK_SECRET', ''); @@ -936,14 +936,14 @@ App::post('/v1/vcs/github/events') $github->initializeVariables($providerInstallationId, $privateKey, $githubAppId); //find functionId from functions table - $repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [ + $repositories = $auth->skip(fn () => $dbForConsole->find('repositories', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::limit(100), ])); // create new deployment only on push and not when branch is created if (!$providerBranchCreated) { - $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForConsole, $queueForBuilds, $getProjectDB, $request); + $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForConsole, $queueForBuilds, $getProjectDB, $request, $auth); } } elseif ($event == $github::EVENT_INSTALLATION) { if ($parsedPayload["action"] == "deleted") { @@ -956,13 +956,13 @@ App::post('/v1/vcs/github/events') ]); foreach ($installations as $installation) { - $repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [ + $repositories = $auth->skip(fn () => $dbForConsole->find('repositories', [ Query::equal('installationInternalId', [$installation->getInternalId()]), Query::limit(1000) ])); foreach ($repositories as $repository) { - Authorization::skip(fn () => $dbForConsole->deleteDocument('repositories', $repository->getId())); + $auth->skip(fn () => $dbForConsole->deleteDocument('repositories', $repository->getId())); } $dbForConsole->deleteDocument('installations', $installation->getId()); @@ -994,12 +994,12 @@ App::post('/v1/vcs/github/events') $providerCommitAuthor = $commitDetails["commitAuthor"] ?? ''; $providerCommitMessage = $commitDetails["commitMessage"] ?? ''; - $repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [ + $repositories = $auth->skip(fn () => $dbForConsole->find('repositories', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::orderDesc('$createdAt') ])); - $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForConsole, $queueForBuilds, $getProjectDB, $request); + $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForConsole, $queueForBuilds, $getProjectDB, $request, $auth); } elseif ($parsedPayload["action"] == "closed") { // Allowed external contributions cleanup @@ -1008,7 +1008,7 @@ App::post('/v1/vcs/github/events') $external = $parsedPayload["external"] ?? true; if ($external) { - $repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [ + $repositories = $auth->skip(fn () => $dbForConsole->find('repositories', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::orderDesc('$createdAt') ])); @@ -1019,7 +1019,7 @@ App::post('/v1/vcs/github/events') if (\in_array($providerPullRequestId, $providerPullRequestIds)) { $providerPullRequestIds = \array_diff($providerPullRequestIds, [$providerPullRequestId]); $repository = $repository->setAttribute('providerPullRequestIds', $providerPullRequestIds); - $repository = Authorization::skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository)); + $repository = $auth->skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository)); } } } @@ -1030,7 +1030,7 @@ App::post('/v1/vcs/github/events') } ); -App::get('/v1/vcs/installations') +Http::get('/v1/vcs/installations') ->desc('List installations') ->groups(['api', 'vcs']) ->label('scope', 'vcs.read') @@ -1090,7 +1090,7 @@ App::get('/v1/vcs/installations') ]), Response::MODEL_INSTALLATION_LIST); }); -App::get('/v1/vcs/installations/:installationId') +Http::get('/v1/vcs/installations/:installationId') ->desc('Get installation') ->groups(['api', 'vcs']) ->label('scope', 'vcs.read') @@ -1119,8 +1119,8 @@ App::get('/v1/vcs/installations/:installationId') $response->dynamic($installation, Response::MODEL_INSTALLATION); }); -App::delete('/v1/vcs/installations/:installationId') - ->desc('Delete installation') +Http::delete('/v1/vcs/installations/:installationId') + ->desc('Delete Installation') ->groups(['api', 'vcs']) ->label('scope', 'vcs.write') ->label('sdk.namespace', 'vcs') @@ -1152,7 +1152,7 @@ App::delete('/v1/vcs/installations/:installationId') $response->noContent(); }); -App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositoryId') +Http::patch('/v1/vcs/github/installations/:installationId/repositories/:repositoryId') ->desc('Authorize external deployment') ->groups(['api', 'vcs']) ->label('scope', 'vcs.write') @@ -1172,14 +1172,15 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor ->inject('dbForConsole') ->inject('getProjectDB') ->inject('queueForBuilds') - ->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) { + ->inject('auth') + ->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds, Authorization $auth) use ($createGitDeployments) { $installation = $dbForConsole->getDocument('installations', $installationId); if ($installation->isEmpty()) { throw new Exception(Exception::INSTALLATION_NOT_FOUND); } - $repository = Authorization::skip(fn () => $dbForConsole->getDocument('repositories', $repositoryId, [ + $repository = $auth->skip(fn () => $dbForConsole->getDocument('repositories', $repositoryId, [ Query::equal('projectInternalId', [$project->getInternalId()]) ])); @@ -1196,7 +1197,7 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor // TODO: Delete from array when PR is closed - $repository = Authorization::skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository)); + $repository = $auth->skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository)); $privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY'); $githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID'); @@ -1220,7 +1221,7 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor $providerBranch = \explode(':', $pullRequestResponse['head']['label'])[1] ?? ''; $providerCommitHash = $pullRequestResponse['head']['sha'] ?? ''; - $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerCommitHash, $providerPullRequestId, true, $dbForConsole, $queueForBuilds, $getProjectDB, $request); + $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerCommitHash, $providerPullRequestId, true, $dbForConsole, $queueForBuilds, $getProjectDB, $request, $auth); $response->noContent(); }); diff --git a/app/controllers/general.php b/app/controllers/general.php index 04554a940e..1e403ef93a 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -1,7 +1,5 @@ getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml'); + $route?->label('error', __DIR__ . '/../views/general/error.phtml'); $host = $request->getHostname() ?? ''; - $route = Authorization::skip( + $rule = $auth->skip( fn () => $dbForConsole->find('rules', [ Query::equal('domain', [$host]), Query::limit(1) ]) )[0] ?? null; - if ($route === null) { + if ($rule === null) { 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.'); } @@ -73,12 +72,12 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo } // Act as API - no Proxy logic - $utopia->getRoute()?->label('error', ''); + $route?->label('error', ''); return false; } - $projectId = $route->getAttribute('projectId'); - $project = Authorization::skip( + $projectId = $rule->getAttribute('projectId'); + $project = $auth->skip( fn () => $dbForConsole->getDocument('projects', $projectId) ); if (array_key_exists('proxy', $project->getAttribute('services', []))) { @@ -89,16 +88,16 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo } // Skip Appwrite Router for ACME challenge. Nessessary for certificate generation - $path = ($swooleRequest->server['request_uri'] ?? '/'); + $path = ($request->getURI() ?? '/'); if (\str_starts_with($path, '/.well-known/acme-challenge')) { return false; } - $type = $route->getAttribute('resourceType'); + $type = $rule->getAttribute('resourceType'); if ($type === 'function') { - $utopia->getRoute()?->label('sdk.namespace', 'functions'); - $utopia->getRoute()?->label('sdk.method', 'createExecution'); + $route->label('sdk.namespace', 'functions'); + $route->label('sdk.method', 'createExecution'); if (System::getEnv('_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS if ($request->getProtocol() !== 'https') { @@ -110,26 +109,25 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo } } - $functionId = $route->getAttribute('resourceId'); - $projectId = $route->getAttribute('projectId'); + $functionId = $rule->getAttribute('resourceId'); + $projectId = $rule->getAttribute('projectId'); - $path = ($swooleRequest->server['request_uri'] ?? '/'); - $query = ($swooleRequest->server['query_string'] ?? ''); + $path = ($request->getURI() ?? '/'); + $query = ($request->getQueryString() ?? ''); if (!empty($query)) { $path .= '?' . $query; } - - $body = $swooleRequest->getContent() ?? ''; - $method = $swooleRequest->server['request_method']; + $body = $request->getRawPayload() ?? ''; + $method = $request->getMethod(); $requestHeaders = $request->getHeaders(); - $project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId)); + $project = $auth->skip(fn () => $dbForConsole->getDocument('projects', $projectId)); $dbForProject = $getProjectDB($project); - $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); + $function = $auth->skip(fn () => $dbForProject->getDocument('functions', $functionId)); if ($function->isEmpty() || !$function->getAttribute('enabled')) { throw new AppwriteException(AppwriteException::FUNCTION_NOT_FOUND); @@ -145,7 +143,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo throw new AppwriteException(AppwriteException::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $function->getAttribute('runtime', '') . '" is not supported'); } - $deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', ''))); + $deployment = $auth->skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', ''))); 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'); @@ -156,7 +154,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo } /** Check if build has completed */ - $build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''))); + $build = $auth->skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''))); if ($build->isEmpty()) { throw new AppwriteException(AppwriteException::BUILD_NOT_FOUND); } @@ -217,7 +215,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo 'deploymentInternalId' => $deployment->getInternalId(), 'deploymentId' => $deployment->getId(), 'trigger' => 'http', // http / schedule / event - 'status' => 'processing', // waiting / processing / completed / failed + 'status' => 'processing', // waiting / processing / completed / failed 'responseStatusCode' => 0, 'responseHeaders' => [], 'requestPath' => $path, @@ -313,7 +311,6 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo cpus: $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT, memory: $spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT, logging: $function->getAttribute('logging', true), - requestTimeout: 30 ); $headersFiltered = []; @@ -331,7 +328,6 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo $execution->setAttribute('logs', $executionResponse['logs']); $execution->setAttribute('errors', $executionResponse['errors']); $execution->setAttribute('duration', $executionResponse['duration']); - } catch (\Throwable $th) { $durationEnd = \microtime(true); @@ -366,7 +362,8 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo ->trigger() ; - $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution)); + /** @var Document $execution */ + $execution = $auth->skip(fn () => $dbForProject->createDocument('executions', $execution)); } $execution->setAttribute('logs', ''); @@ -398,18 +395,15 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo return true; } elseif ($type === 'api') { - $utopia->getRoute()?->label('error', ''); + $route?->label('error', ''); return false; } else { throw new AppwriteException(AppwriteException::GENERAL_SERVER_ERROR, 'Unknown resource type ' . $type); } - - $utopia->getRoute()?->label('error', ''); - return false; } /* -App::init() +Http::init() ->groups(['api']) ->inject('project') ->inject('mode') @@ -420,7 +414,7 @@ App::init() }); */ -App::init() +Http::init() ->groups(['database', 'functions', 'storage', 'messaging']) ->inject('project') ->inject('request') @@ -433,12 +427,11 @@ App::init() } }); -App::init() +Http::init() ->groups(['api', 'web']) - ->inject('utopia') - ->inject('swooleRequest') ->inject('request') ->inject('response') + ->inject('route') ->inject('console') ->inject('project') ->inject('dbForConsole') @@ -450,7 +443,27 @@ App::init() ->inject('queueForUsage') ->inject('queueForEvents') ->inject('queueForCertificates') - ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, Reader $geodb, Usage $queueForUsage, Event $queueForEvents, Certificate $queueForCertificates) { + ->inject('authorization') + ->action(function ( + Request $request, + Response $response, + Route $route, + Document $console, + Document $project, + Database $dbForConsole, + $getProjectDB, + Locale $locale, + array $localeCodes, + array $clients, + /** + * @disregard P1009 Undefined type + */ + Reader $geodb, + Usage $queueForUsage, + Event $queueForEvents, + Certificate $queueForCertificates, + Authorization $authorization + ) { /* * Appwrite Router */ @@ -458,7 +471,7 @@ App::init() $mainDomain = System::getEnv('_APP_DOMAIN', ''); // Only run Router when external domain if ($host !== $mainDomain) { - if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb)) { + if (router($dbForConsole, $getProjectDB, $request, $response, $route, $queueForEvents, $queueForUsage, $geodb, $authorization)) { return; } } @@ -466,8 +479,8 @@ App::init() /* * Request format */ - $route = $utopia->getRoute(); - Request::setRoute($route); + //$route = $utopia->getRoute(); + //Request::setRoute($route); if ($route === null) { return $response @@ -499,7 +512,7 @@ App::init() } elseif (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) { Console::warning('Skipping SSL certificates generation on ACME challenge.'); } else { - Authorization::disable(); + $authorization->disable(); $envDomain = System::getEnv('_APP_DOMAIN', ''); $mainDomain = null; @@ -538,12 +551,12 @@ App::init() } $domains[$domain->get()] = true; - Authorization::reset(); // ensure authorization is re-enabled + $authorization->reset(); // ensure authorization is re-enabled } Config::setParam('domains', $domains); } - $localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', '')); + $localeParam = (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')); if (\in_array($localeParam, $localeCodes)) { $locale->setDefault($localeParam); } @@ -571,7 +584,7 @@ App::init() Config::setParam( 'domainVerification', ($selfDomain->getRegisterable() === $endDomain->getRegisterable()) && - $endDomain->getRegisterable() !== '' + $endDomain->getRegisterable() !== '' ); $isLocalHost = $request->getHostname() === 'localhost' || $request->getHostname() === 'localhost:' . $request->getPort(); @@ -586,8 +599,8 @@ App::init() ? null : ( $isConsoleProject && $isConsoleRootSession - ? '.' . $selfDomain->getRegisterable() - : '.' . $request->getHostname() + ? '.' . $selfDomain->getRegisterable() + : '.' . $request->getHostname() ) ); @@ -606,7 +619,7 @@ App::init() $response->addFilter(new ResponseV18()); } if (version_compare($responseFormat, APP_VERSION_STABLE, '>')) { - $response->addHeader('X-Appwrite-Warning', "The current SDK is built for Appwrite " . $responseFormat . ". However, the current Appwrite server version is ". APP_VERSION_STABLE . ". Please downgrade your SDK to match the Appwrite version: https://appwrite.io/docs/sdks"); + $response->addHeader('X-Appwrite-Warning', "The current SDK is built for Appwrite " . $responseFormat . ". However, the current Appwrite server version is " . APP_VERSION_STABLE . ". Please downgrade your SDK to match the Appwrite version: https://appwrite.io/docs/sdks"); } } @@ -617,7 +630,9 @@ App::init() * @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers */ if (System::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS - if ($request->getProtocol() !== 'https' && ($swooleRequest->header['host'] ?? '') !== 'localhost' && ($swooleRequest->header['host'] ?? '') !== APP_HOSTNAME_INTERNAL) { // localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations + if ($request->getProtocol() !== 'https' // localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations + && ($request->getHeader('host') ?? '') !== 'localhost' + && ($request->getHeader('host') ?? '') !== APP_HOSTNAME_INTERNAL) { if ($request->getMethod() !== Request::METHOD_GET) { throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP. Please use HTTPS instead.'); } @@ -657,9 +672,8 @@ App::init() } }); -App::options() - ->inject('utopia') - ->inject('swooleRequest') +Http::options() + ->inject('route') ->inject('request') ->inject('response') ->inject('dbForConsole') @@ -667,7 +681,8 @@ App::options() ->inject('queueForEvents') ->inject('queueForUsage') ->inject('geodb') - ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb) { + ->inject('authorization') + ->action(function (Route $route, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb, Authorization $authorization) { /* * Appwrite Router */ @@ -675,7 +690,7 @@ App::options() $mainDomain = System::getEnv('_APP_DOMAIN', ''); // Only run Router when external domain if ($host !== $mainDomain) { - if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb)) { + if (router($dbForConsole, $getProjectDB, $request, $response, $route, $queueForEvents, $queueForUsage, $geodb, $authorization)) { return; } } @@ -692,18 +707,25 @@ App::options() ->noContent(); }); -App::error() +Http::error() ->inject('error') - ->inject('utopia') + ->inject('user') + ->inject('route') ->inject('request') ->inject('response') ->inject('project') ->inject('logger') ->inject('log') + ->inject('authorization') + ->inject('connections') ->inject('queueForUsage') - ->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, Usage $queueForUsage) { + ->action(function (Throwable $error, Document $user, ?Route $route, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, Authorization $authorization, Connections $connections, Usage $queueForUsage) { $version = System::getEnv('_APP_VERSION', 'UNKNOWN'); - $route = $utopia->getRoute(); + + if (is_null($route)) { + $route = new Route($request->getMethod(), $request->getURI()); + } + $class = \get_class($error); $code = $error->getCode(); $message = $error->getMessage(); @@ -724,9 +746,9 @@ App::error() Console::error('[Error] File: ' . $file); Console::error('[Error] Line: ' . $line); } - switch ($class) { - case 'Utopia\Exception': + case 'Utopia\Servers\Exception': + case 'Utopia\Http\Exception': $error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error); switch ($code) { case 400: @@ -771,35 +793,36 @@ App::error() } else { $publish = $error->getCode() === 0 || $error->getCode() >= 500; } - if ($error->getCode() >= 400 && $error->getCode() < 500) { // Register error logger $providerName = System::getEnv('_APP_EXPERIMENT_LOGGING_PROVIDER', ''); $providerConfig = System::getEnv('_APP_EXPERIMENT_LOGGING_CONFIG', ''); - try { - $loggingProvider = new DSN($providerConfig ?? ''); - $providerName = $loggingProvider->getScheme(); + if (!(empty($providerName) || empty($providerConfig))) { + try { + $loggingProvider = new DSN($providerConfig); + $providerName = $loggingProvider->getScheme(); - if (!empty($providerName) && $providerName === 'sentry') { - $key = $loggingProvider->getPassword(); - $projectId = $loggingProvider->getUser() ?? ''; - $host = 'https://' . $loggingProvider->getHost(); + if (!empty($providerName) && $providerName === 'sentry') { + $key = $loggingProvider->getPassword(); + $projectId = $loggingProvider->getUser() ?? ''; + $host = 'https://' . $loggingProvider->getHost(); - $adapter = new Sentry($projectId, $key, $host); - $logger = new Logger($adapter); - $logger->setSample(0.04); - $publish = true; - } else { - throw new \Exception('Invalid experimental logging provider'); + $adapter = new Sentry($projectId, $key, $host); + $logger = new Logger($adapter); + $logger->setSample(0.04); + $publish = true; + } else { + throw new \Exception('Invalid experimental logging provider'); + } + } catch (\Throwable $th) { + Console::warning('Failed to initialize logging provider: ' . $th->getMessage()); } - } catch (\Throwable $th) { - Console::warning('Failed to initialize logging provider: ' . $th->getMessage()); } } if ($publish && $project->getId() !== 'console') { - if (!Auth::isPrivilegedUser(Authorization::getRoles())) { + if (!Auth::isPrivilegedUser($authorization->getRoles())) { $fileSize = 0; $file = $request->getFiles('file'); if (!empty($file)) { @@ -818,14 +841,7 @@ App::error() } - if ($logger && $publish) { - try { - /** @var Utopia\Database\Document $user */ - $user = $utopia->getResource('user'); - } catch (\Throwable) { - // All good, user is optional information for logger - } - + if ($logger && ($publish || $error->getCode() === 0)) { if (isset($user) && !$user->isEmpty()) { $log->setUser(new User($user->getId())); } @@ -855,7 +871,7 @@ App::error() $log->addExtra('file', $error->getFile()); $log->addExtra('line', $error->getLine()); $log->addExtra('trace', $error->getTraceAsString()); - $log->addExtra('roles', Authorization::getRoles()); + $log->addExtra('roles', $authorization->getRoles()); $action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD"); $log->setAction($action); @@ -873,7 +889,7 @@ App::error() /** Wrap all exceptions inside Appwrite\Extend\Exception */ if (!($error instanceof AppwriteException)) { - $error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error); + $error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, (int)$code, $error); } switch ($code) { // Don't show 500 errors! @@ -893,14 +909,14 @@ App::error() break; default: $code = 500; // All other errors get the generic 500 server error status code - $message = 'Server Error'; + $message = (Http::getMode() === Http::MODE_TYPE_DEVELOPMENT) ? $message : 'Server Error'; } //$_SERVER = []; // Reset before reporting to error log to avoid keys being compromised $type = $error->getType(); - $output = ((App::isDevelopment())) ? [ + $output = ((Http::isDevelopment())) ? [ 'message' => $message, 'code' => $code, 'file' => $file, @@ -928,7 +944,7 @@ App::error() $layout ->setParam('title', $project->getAttribute('name') . ' - Error') - ->setParam('development', App::isDevelopment()) + ->setParam('development', Http::isDevelopment()) ->setParam('projectName', $project->getAttribute('name')) ->setParam('projectURL', $project->getAttribute('url')) ->setParam('message', $output['message'] ?? '') @@ -939,18 +955,18 @@ App::error() $response->html($layout->render()); } + $connections->reclaim(); + $response->dynamic( new Document($output), - $utopia->isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_ERROR + Http::isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_ERROR ); }); -App::get('/robots.txt') +Http::get('/robots.txt') ->desc('Robots.txt File') ->label('scope', 'public') ->label('docs', false) - ->inject('utopia') - ->inject('swooleRequest') ->inject('request') ->inject('response') ->inject('dbForConsole') @@ -958,7 +974,9 @@ App::get('/robots.txt') ->inject('queueForEvents') ->inject('queueForUsage') ->inject('geodb') - ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb) { + ->inject('route') + ->inject('authorization') + ->action(function (Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb, ?Route $route, Authorization $authorization) { $host = $request->getHostname() ?? ''; $mainDomain = System::getEnv('_APP_DOMAIN', ''); @@ -966,16 +984,17 @@ App::get('/robots.txt') $template = new View(__DIR__ . '/../views/general/robots.phtml'); $response->text($template->render(false)); } else { - router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb); + if (is_null($route)) { + $route = new Route($request->getMethod(), $request->getURI()); + } + router($dbForConsole, $getProjectDB, $request, $response, $route, $queueForEvents, $queueForUsage, $geodb, $authorization); } }); -App::get('/humans.txt') +Http::get('/humans.txt') ->desc('Humans.txt File') ->label('scope', 'public') ->label('docs', false) - ->inject('utopia') - ->inject('swooleRequest') ->inject('request') ->inject('response') ->inject('dbForConsole') @@ -983,7 +1002,9 @@ App::get('/humans.txt') ->inject('queueForEvents') ->inject('queueForUsage') ->inject('geodb') - ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb) { + ->inject('route') + ->inject('authorization') + ->action(function (Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb, Route $route, Authorization $authorization) { $host = $request->getHostname() ?? ''; $mainDomain = System::getEnv('_APP_DOMAIN', ''); @@ -991,11 +1012,11 @@ App::get('/humans.txt') $template = new View(__DIR__ . '/../views/general/humans.phtml'); $response->text($template->render(false)); } else { - router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb); + router($dbForConsole, $getProjectDB, $request, $response, $route, $queueForEvents, $queueForUsage, $geodb, $authorization); } }); -App::get('/.well-known/acme-challenge/*') +Http::get('/.well-known/acme-challenge/*') ->desc('SSL Verification') ->label('scope', 'public') ->label('docs', false) @@ -1045,16 +1066,32 @@ App::get('/.well-known/acme-challenge/*') $response->text($content); }); -include_once __DIR__ . '/shared/api.php'; -include_once __DIR__ . '/shared/api/auth.php'; - -App::wildcard() +Http::wildcard() ->groups(['api']) ->label('scope', 'global') ->action(function () { throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND); }); -foreach (Config::getParam('services', []) as $service) { - include_once $service['controller']; -} +include_once 'mock.php'; +include_once 'shared/api.php'; +include_once 'shared/api/auth.php'; +include_once 'api/account.php'; +include_once 'api/avatars.php'; +include_once 'api/console.php'; +include_once 'api/databases.php'; +include_once 'api/functions.php'; +include_once 'api/graphql.php'; +include_once 'api/health.php'; +include_once 'api/locale.php'; +include_once 'api/messaging.php'; +include_once 'api/migrations.php'; +include_once 'api/project.php'; +include_once 'api/projects.php'; +include_once 'api/proxy.php'; +include_once 'api/storage.php'; +include_once 'api/teams.php'; +include_once 'api/users.php'; +include_once 'api/vcs.php'; +include_once 'web/console.php'; +include_once 'web/home.php'; diff --git a/app/controllers/mock.php b/app/controllers/mock.php index fdb1d80dcc..70ca5e9048 100644 --- a/app/controllers/mock.php +++ b/app/controllers/mock.php @@ -3,9 +3,7 @@ global $utopia, $request, $response; use Appwrite\Extend\Exception; -use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; -use Utopia\App; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; @@ -13,13 +11,15 @@ use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Validator\UID; +use Utopia\Http\Http; +use Utopia\Http\Route; +use Utopia\Http\Validator\Host; +use Utopia\Http\Validator\Text; +use Utopia\Http\Validator\WhiteList; use Utopia\System\System; -use Utopia\Validator\Host; -use Utopia\Validator\Text; -use Utopia\Validator\WhiteList; use Utopia\VCS\Adapter\Git\GitHub; -App::get('/v1/mock/tests/general/oauth2') +Http::get('/v1/mock/tests/general/oauth2') ->desc('OAuth Login') ->groups(['mock']) ->label('scope', 'public') @@ -35,7 +35,7 @@ App::get('/v1/mock/tests/general/oauth2') $response->redirect($redirectURI . '?' . \http_build_query(['code' => 'abcdef', 'state' => $state])); }); -App::get('/v1/mock/tests/general/oauth2/token') +Http::get('/v1/mock/tests/general/oauth2/token') ->desc('OAuth2 Token') ->groups(['mock']) ->label('scope', 'public') @@ -81,7 +81,7 @@ App::get('/v1/mock/tests/general/oauth2/token') } }); -App::get('/v1/mock/tests/general/oauth2/user') +Http::get('/v1/mock/tests/general/oauth2/user') ->desc('OAuth2 User') ->groups(['mock']) ->label('scope', 'public') @@ -101,7 +101,7 @@ App::get('/v1/mock/tests/general/oauth2/user') ]); }); -App::get('/v1/mock/tests/general/oauth2/success') +Http::get('/v1/mock/tests/general/oauth2/success') ->desc('OAuth2 Success') ->groups(['mock']) ->label('scope', 'public') @@ -114,7 +114,7 @@ App::get('/v1/mock/tests/general/oauth2/success') ]); }); -App::get('/v1/mock/tests/general/oauth2/failure') +Http::get('/v1/mock/tests/general/oauth2/failure') ->desc('OAuth2 Failure') ->groups(['mock']) ->label('scope', 'public') @@ -129,7 +129,7 @@ App::get('/v1/mock/tests/general/oauth2/failure') ]); }); -App::patch('/v1/mock/functions-v2') +Http::patch('/v1/mock/functions-v2') ->desc('Update Function Version to V2 (outdated code syntax)') ->groups(['mock', 'api', 'functions']) ->label('scope', 'functions.write') @@ -155,7 +155,7 @@ App::patch('/v1/mock/functions-v2') $response->noContent(); }); -App::post('/v1/mock/api-key-unprefixed') +Http::post('/v1/mock/api-key-unprefixed') ->desc('Create API Key (without standard prefix)') ->groups(['mock', 'api', 'projects']) ->label('scope', 'projects.write') @@ -204,7 +204,7 @@ App::post('/v1/mock/api-key-unprefixed') ->dynamic($key, Response::MODEL_KEY); }); -App::get('/v1/mock/github/callback') +Http::get('/v1/mock/github/callback') ->desc('Create installation document using GitHub installation id') ->groups(['mock', 'api', 'vcs']) ->label('scope', 'public') @@ -264,15 +264,13 @@ App::get('/v1/mock/github/callback') ]); }); -App::shutdown() +Http::shutdown() ->groups(['mock']) - ->inject('utopia') + ->inject('route') ->inject('response') - ->inject('request') - ->action(function (App $utopia, Response $response, Request $request) { + ->action(function (Route $route, Response $response) { $result = []; - $route = $utopia->getRoute(); $path = APP_STORAGE_CACHE . '/tests.json'; $tests = (\file_exists($path)) ? \json_decode(\file_get_contents($path), true) : []; diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 357d73adc8..f35643dad5 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -15,11 +15,11 @@ use Appwrite\Event\Usage; use Appwrite\Extend\Exception; use Appwrite\Extend\Exception as AppwriteException; use Appwrite\Messaging\Adapter\Realtime; +use Appwrite\Utopia\Queue\Connections; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapters\Database\TimeLimit; -use Utopia\App; use Utopia\Cache\Adapter\Filesystem; use Utopia\Cache\Cache; use Utopia\Config\Config; @@ -28,8 +28,11 @@ use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\Role; use Utopia\Database\Validator\Authorization; +use Utopia\Database\Validator\Authorization\Input; +use Utopia\Http\Http; +use Utopia\Http\Route; +use Utopia\Http\Validator\WhiteList; use Utopia\System\System; -use Utopia\Validator\WhiteList; $parseLabel = function (string $label, array $responsePayload, array $requestParams, Document $user) { preg_match_all('/{(.*?)}/', $label, $matches); @@ -150,9 +153,9 @@ $databaseListener = function (string $event, Document $document, Document $proje } }; -App::init() +Http::init() ->groups(['api']) - ->inject('utopia') + ->inject('route') ->inject('request') ->inject('dbForConsole') ->inject('project') @@ -160,9 +163,8 @@ App::init() ->inject('session') ->inject('servers') ->inject('mode') - ->action(function (App $utopia, Request $request, Database $dbForConsole, Document $project, Document $user, ?Document $session, array $servers, string $mode) { - $route = $utopia->getRoute(); - + ->inject('authorization') + ->action(function (Route $route, Request $request, Database $dbForConsole, Document $project, Document $user, ?Document $session, array $servers, string $mode, Authorization $authorization) { if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } @@ -241,8 +243,8 @@ App::init() $role = Auth::USER_ROLE_APPS; $scopes = \array_merge($roles[$role]['scopes'], $tokenScopes); - Authorization::setRole(Auth::USER_ROLE_APPS); - Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys. + $authorization->addRole(Auth::USER_ROLE_APPS); + $authorization->setDefaultStatus(false); // Cancel security segmentation for API keys. } } elseif ($keyType === API_KEY_STANDARD) { // No underline means no prefix. Backwards compatibility. @@ -267,8 +269,8 @@ App::init() throw new Exception(Exception::PROJECT_KEY_EXPIRED); } - Authorization::setRole(Auth::USER_ROLE_APPS); - Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys. + $authorization->addRole(Auth::USER_ROLE_APPS); + $authorization->setDefaultStatus(false); // Cancel security segmentation for API keys. $accessedAt = $key->getAttribute('accessedAt', ''); if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) { @@ -295,10 +297,10 @@ App::init() } } - Authorization::setRole($role); + $authorization->addRole($role); - foreach (Auth::getRoles($user) as $authRole) { - Authorization::setRole($authRole); + foreach (Auth::getRoles($user, $authorization) as $authRole) { + $authorization->addRole($authRole); } $service = $route->getLabel('sdk.namespace', ''); @@ -306,7 +308,7 @@ App::init() if ( array_key_exists($service, $project->getAttribute('services', [])) && !$project->getAttribute('services', [])[$service] - && !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles())) + && !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles())) ) { throw new Exception(Exception::GENERAL_SERVICE_DISABLED); } @@ -341,9 +343,9 @@ App::init() } }); -App::init() +Http::init() ->groups(['api']) - ->inject('utopia') + ->inject('route') ->inject('request') ->inject('response') ->inject('project') @@ -357,14 +359,12 @@ App::init() ->inject('queueForUsage') ->inject('dbForProject') ->inject('mode') - ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode) use ($databaseListener) { - - $route = $utopia->getRoute(); - + ->inject('authorization') + ->action(function (Route $route, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode, Authorization $authorization) use ($databaseListener) { if ( array_key_exists('rest', $project->getAttribute('apis', [])) && !$project->getAttribute('apis', [])['rest'] - && !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles())) + && !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles())) ) { throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED); } @@ -394,7 +394,7 @@ App::init() $closestLimit = null; - $roles = Authorization::getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -458,7 +458,7 @@ App::init() $useCache = $route->getLabel('cache', false); if ($useCache) { $key = md5($request->getURI() . '*' . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER); - $cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key)); + $cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key)); $cache = new Cache( new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId()) ); @@ -471,19 +471,18 @@ App::init() if ($type === 'bucket') { $bucketId = $parts[1] ?? null; - $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + + $isAPIKey = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } $fileSecurity = $bucket->getAttribute('fileSecurity', false); - $validator = new Authorization(Database::PERMISSION_READ); - $valid = $validator->isValid($bucket->getRead()); - + $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead())); if (!$fileSecurity && !$valid) { throw new Exception(Exception::USER_UNAUTHORIZED); } @@ -494,7 +493,7 @@ App::init() if ($fileSecurity && !$valid) { $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } else { - $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); } if ($file->isEmpty()) { @@ -518,7 +517,7 @@ App::init() } }); -App::init() +Http::init() ->groups(['session']) ->inject('user') ->inject('request') @@ -538,14 +537,12 @@ App::init() * Delete older sessions if the number of sessions have crossed * the session limit set for the project */ -App::shutdown() +Http::shutdown() ->groups(['session']) - ->inject('utopia') - ->inject('request') ->inject('response') ->inject('project') ->inject('dbForProject') - ->action(function (App $utopia, Request $request, Response $response, Document $project, Database $dbForProject) { + ->action(function (Response $response, Document $project, Database $dbForProject) { $sessionLimit = $project->getAttribute('auths', [])['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT; $session = $response->getPayload(); $userId = $session['userId'] ?? ''; @@ -572,9 +569,9 @@ App::shutdown() $dbForProject->purgeCachedDocument('users', $userId); }); -App::shutdown() +Http::shutdown() ->groups(['api']) - ->inject('utopia') + ->inject('route') ->inject('request') ->inject('response') ->inject('project') @@ -590,7 +587,29 @@ App::shutdown() ->inject('queueForFunctions') ->inject('mode') ->inject('dbForConsole') - ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Usage $queueForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Database $dbForProject, Func $queueForFunctions, string $mode, Database $dbForConsole) use ($parseLabel) { + ->inject('authorization') + ->action(function ( + Route $route, + Request $request, + Response $response, + Document $project, + Document $user, + Event $queueForEvents, + Audit $queueForAudits, + Usage $queueForUsage, + Delete $queueForDeletes, + EventDatabase $queueForDatabase, + Build $queueForBuilds, + Messaging $queueForMessaging, + Database $dbForProject, + Func $queueForFunctions, + string $mode, + Database $dbForConsole, + Authorization $authorization, + ) use ($parseLabel) { + if (!empty($user) && !$user->isEmpty() && empty($user->getInternalId())) { + $user = $authorization->skip(fn () => $dbForProject->getDocument('users', $user->getId())); + } $responsePayload = $response->getPayload(); @@ -650,7 +669,6 @@ App::shutdown() } } - $route = $utopia->getRoute(); $requestParams = $route->getParamsValues(); /** @@ -720,11 +738,11 @@ App::shutdown() $key = md5($request->getURI() . '*' . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER); $signature = md5($data['payload']); - $cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key)); + $cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key)); $accessedAt = $cacheLog->getAttribute('accessedAt', ''); $now = DateTime::now(); if ($cacheLog->isEmpty()) { - Authorization::skip(fn () => $dbForProject->createDocument('cache', new Document([ + $authorization->skip(fn () => $dbForProject->createDocument('cache', new Document([ '$id' => $key, 'resource' => $resource, 'resourceType' => $resourceType, @@ -734,7 +752,7 @@ App::shutdown() ]))); } elseif (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_CACHE_UPDATE)) > $accessedAt) { $cacheLog->setAttribute('accessedAt', $now); - Authorization::skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog)); + $authorization->skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog)); } if ($signature !== $cacheLog->getAttribute('signature')) { @@ -746,10 +764,8 @@ App::shutdown() } } - - if ($project->getId() !== 'console') { - if (!Auth::isPrivilegedUser(Authorization::getRoles())) { + if (!Auth::isPrivilegedUser($authorization->getRoles())) { $fileSize = 0; $file = $request->getFiles('file'); if (!empty($file)) { @@ -774,7 +790,7 @@ App::shutdown() $accessedAt = $project->getAttribute('accessedAt', ''); if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) { $project->setAttribute('accessedAt', DateTime::now()); - Authorization::skip(fn () => $dbForConsole->updateDocument('projects', $project->getId(), $project)); + $authorization->skip(fn () => $dbForConsole->updateDocument('projects', $project->getId(), $project)); } } @@ -795,10 +811,16 @@ App::shutdown() } }); -App::init() +Http::init() ->groups(['usage']) ->action(function () { if (System::getEnv('_APP_USAGE_STATS', 'enabled') !== 'enabled') { throw new Exception(Exception::GENERAL_USAGE_DISABLED); } }); + +Http::shutdown() + ->inject('connections') + ->action(function (Connections $connections) { + $connections->reclaim(); + }); diff --git a/app/controllers/shared/api/auth.php b/app/controllers/shared/api/auth.php index 53aacabe21..224dcb3392 100644 --- a/app/controllers/shared/api/auth.php +++ b/app/controllers/shared/api/auth.php @@ -4,13 +4,14 @@ use Appwrite\Auth\Auth; use Appwrite\Extend\Exception; use Appwrite\Utopia\Request; use MaxMind\Db\Reader; -use Utopia\App; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; +use Utopia\Http\Http; +use Utopia\Http\Route; use Utopia\System\System; -App::init() +Http::init() ->groups(['mfaProtected']) ->inject('session') ->action(function (Document $session) { @@ -29,13 +30,14 @@ App::init() } }); -App::init() +Http::init() ->groups(['auth']) - ->inject('utopia') + ->inject('route') ->inject('request') ->inject('project') ->inject('geodb') - ->action(function (App $utopia, Request $request, Document $project, Reader $geodb) { + ->inject('authorization') + ->action(function (Route $route, Request $request, Document $project, Reader $geodb, Authorization $authorization) { $denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', ''); if (!empty($denylist && $project->getId() === 'console')) { $countries = explode(',', $denylist); @@ -46,15 +48,17 @@ App::init() } } - $route = $utopia->match($request); - - $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); - $isAppUser = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAppUser = Auth::isAppUser($authorization->getRoles()); if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs return; } + if ($route->getLabel('sdk.namespace', '') === 'graphql') { // Skip for graphQL recursive call + return; + } + $auths = $project->getAttribute('auths', []); switch ($route->getLabel('auth.type', '')) { case 'emailPassword': diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php index c02e140270..60be4acf54 100644 --- a/app/controllers/web/console.php +++ b/app/controllers/web/console.php @@ -2,9 +2,9 @@ use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; -use Utopia\App; +use Utopia\Http\Http; -App::init() +Http::init() ->groups(['web']) ->inject('request') ->inject('response') @@ -16,7 +16,7 @@ App::init() ; }); -App::get('/') +Http::get('/') ->alias('auth/*') ->alias('/invite') ->alias('/login') diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php index 27b2614c37..3577061d69 100644 --- a/app/controllers/web/home.php +++ b/app/controllers/web/home.php @@ -1,10 +1,10 @@ desc('Get Version') ->groups(['home', 'web']) ->label('scope', 'public') diff --git a/app/http.php b/app/http.php index bec772c770..320621ad33 100644 --- a/app/http.php +++ b/app/http.php @@ -1,335 +1,242 @@ set([ - 'worker_num' => $workerNumber, - 'open_http2_protocol' => true, - 'http_compression' => true, - 'http_compression_level' => 6, - 'package_max_length' => $payloadSize, - 'buffer_output_size' => $payloadSize, - ]); +$server = new Server('0.0.0.0', '80', [ + 'open_http2_protocol' => true, + 'http_compression' => true, + 'http_compression_level' => 6, + 'package_max_length' => $payloadSize, + 'buffer_output_size' => $payloadSize, + // Server + // 'log_level' => 0, + 'dispatch_mode' => 2, + 'worker_num' => $workerNumber, + 'reactor_num' => swoole_cpu_num() * 2, + 'open_cpu_affinity' => true, + // Coroutine + 'enable_coroutine' => true, + 'send_yield' => true, + 'tcp_fastopen' => true, +]); -$http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) { - Console::success('Worker ' . ++$workerId . ' started successfully'); -}); - -$http->on(Constant::EVENT_BEFORE_RELOAD, function ($server, $workerId) { - Console::success('Starting reload...'); -}); - -$http->on(Constant::EVENT_AFTER_RELOAD, function ($server, $workerId) { - Console::success('Reload completed...'); -}); - -include __DIR__ . '/controllers/general.php'; - -$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $register) { - $app = new App('UTC'); - - go(function () use ($register, $app) { - $pools = $register->get('pools'); - /** @var Group $pools */ - App::setResource('pools', fn () => $pools); - - // wait for database to be ready - $attempts = 0; - $max = 10; - $sleep = 1; - - do { - try { - $attempts++; - $dbForConsole = $app->getResource('dbForConsole'); - /** @var Utopia\Database\Database $dbForConsole */ - break; // leave the do-while if successful - } catch (\Throwable $e) { - Console::warning("Database not ready. Retrying connection ({$attempts})..."); - if ($attempts >= $max) { - throw new \Exception('Failed to connect to database: ' . $e->getMessage()); - } - sleep($sleep); - } - } while ($attempts < $max); - - Console::success('[Setup] - Server database init started...'); +$http = new Http($server, $container, 'UTC'); +$http->setRequestClass(Request::class); +$http->setResponseClass(Response::class); +Http::onStart() + ->inject('authorization') + ->inject('cache') + ->inject('pools') + ->inject('connections') + ->action(function (Authorization $authorization, Cache $cache, array $pools, Connections $connections) { try { - Console::success('[Setup] - Creating database: appwrite...'); - $dbForConsole->create(); + // wait for database to be ready + $attempts = 0; + $max = 15; + $sleep = 2; + + do { + try { + $attempts++; + $pool = $pools['pools-console-console']['pool']; + $dsn = $pools['pools-console-console']['dsn']; + $connection = $pool->get(); + $connections->add($connection, $pool); + + $adapter = match ($dsn->getScheme()) { + 'mariadb' => new MariaDB($connection), + 'mysql' => new MySQL($connection), + default => null + }; + + $adapter->setDatabase($dsn->getPath()); + + $dbForConsole = new Database($adapter, $cache); + $dbForConsole->setAuthorization($authorization); + + $dbForConsole + ->setNamespace('_console') + ->setMetadata('host', \gethostname()) + ->setMetadata('project', 'console') + ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); + + $dbForConsole->ping(); + break; // leave the do-while if successful + } catch (\Throwable $e) { + Console::warning("Database not ready. Retrying connection ({$attempts})..."); + if ($attempts >= $max) { + throw new \Exception('Failed to connect to database: ' . $e->getMessage()); + } + sleep($sleep); + } + } while ($attempts < $max); + + Console::success('[Setup] - Server database init started...'); + + try { + Console::success('[Setup] - Creating database: appwrite...'); + $dbForConsole->create(); + } catch (\Throwable $e) { + Console::success('[Setup] - Skip: metadata table already exists'); + return true; + } + + if ($dbForConsole->getCollection(Audit::COLLECTION)->isEmpty()) { + $audit = new Audit($dbForConsole); + $audit->setup(); + } + + if ($dbForConsole->getCollection(TimeLimit::COLLECTION)->isEmpty()) { + $abuse = new TimeLimit("", 0, 1, $dbForConsole); + $abuse->setup(); + } + + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $consoleCollections = $collections['console']; + foreach ($consoleCollections as $key => $collection) { + if (($collection['$collection'] ?? '') !== Database::METADATA) { + continue; + } + if (!$dbForConsole->getCollection($key)->isEmpty()) { + continue; + } + + Console::success('[Setup] - Creating 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'], + ]); + } + + $dbForConsole->createCollection($key, $attributes, $indexes); + } + + if ($dbForConsole->getDocument('buckets', 'default')->isEmpty() && !$dbForConsole->exists($dbForConsole->getDatabase(), 'bucket_1')) { + Console::success('[Setup] - Creating default bucket...'); + $dbForConsole->createDocument('buckets', new Document([ + '$id' => ID::custom('default'), + '$collection' => ID::custom('buckets'), + 'name' => 'Default', + 'maximumFileSize' => (int) System::getEnv('_APP_STORAGE_LIMIT', 0), // 10MB + 'allowedFileExtensions' => [], + 'enabled' => true, + 'compression' => 'gzip', + 'encryption' => true, + 'antivirus' => true, + 'fileSecurity' => true, + '$permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'search' => 'buckets Default', + ])); + + $bucket = $dbForConsole->getDocument('buckets', 'default'); + + Console::success('[Setup] - Creating files collection for default bucket...'); + + $files = $collections['buckets']['files'] ?? []; + if (empty($files)) { + throw new Exception('Files collection is not configured.'); + } + + $attributes = []; + $indexes = []; + + foreach ($files['attributes'] as $attribute) { + $attributes[] = new Document([ + '$id' => ID::custom($attribute['$id']), + 'type' => $attribute['type'], + 'size' => $attribute['size'], + 'required' => $attribute['required'], + 'signed' => $attribute['signed'], + 'array' => $attribute['array'], + 'filters' => $attribute['filters'], + 'default' => $attribute['default'] ?? null, + 'format' => $attribute['format'] ?? '' + ]); + } + + foreach ($files['indexes'] as $index) { + $indexes[] = new Document([ + '$id' => ID::custom($index['$id']), + 'type' => $index['type'], + 'attributes' => $index['attributes'], + 'lengths' => $index['lengths'], + 'orders' => $index['orders'], + ]); + } + + $dbForConsole->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes); + } + + $connections->reclaim(); + + Console::success('[Setup] - Server database init completed...'); + Console::success('Server started successfully'); } catch (\Throwable $e) { - Console::success('[Setup] - Skip: metadata table already exists'); + Console::warning('Database not ready: ' . $e->getMessage()); + exit(1); } - - if ($dbForConsole->getCollection(Audit::COLLECTION)->isEmpty()) { - $audit = new Audit($dbForConsole); - $audit->setup(); - } - - if ($dbForConsole->getCollection(TimeLimit::COLLECTION)->isEmpty()) { - $adapter = new TimeLimit("", 0, 1, $dbForConsole); - $adapter->setup(); - } - - /** @var array $collections */ - $collections = Config::getParam('collections', []); - $consoleCollections = $collections['console']; - foreach ($consoleCollections as $key => $collection) { - if (($collection['$collection'] ?? '') !== Database::METADATA) { - continue; - } - if (!$dbForConsole->getCollection($key)->isEmpty()) { - continue; - } - - Console::success('[Setup] - Creating 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'], - ]); - } - - $dbForConsole->createCollection($key, $attributes, $indexes); - } - - if ($dbForConsole->getDocument('buckets', 'default')->isEmpty() && !$dbForConsole->exists($dbForConsole->getDatabase(), 'bucket_1')) { - Console::success('[Setup] - Creating default bucket...'); - $dbForConsole->createDocument('buckets', new Document([ - '$id' => ID::custom('default'), - '$collection' => ID::custom('buckets'), - 'name' => 'Default', - 'maximumFileSize' => (int) System::getEnv('_APP_STORAGE_LIMIT', 0), // 10MB - 'allowedFileExtensions' => [], - 'enabled' => true, - 'compression' => 'gzip', - 'encryption' => true, - 'antivirus' => true, - 'fileSecurity' => true, - '$permissions' => [ - Permission::create(Role::any()), - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - 'search' => 'buckets Default', - ])); - - $bucket = $dbForConsole->getDocument('buckets', 'default'); - - Console::success('[Setup] - Creating files collection for default bucket...'); - $files = $collections['buckets']['files'] ?? []; - if (empty($files)) { - throw new Exception('Files collection is not configured.'); - } - - $attributes = []; - $indexes = []; - - foreach ($files['attributes'] as $attribute) { - $attributes[] = new Document([ - '$id' => ID::custom($attribute['$id']), - 'type' => $attribute['type'], - 'size' => $attribute['size'], - 'required' => $attribute['required'], - 'signed' => $attribute['signed'], - 'array' => $attribute['array'], - 'filters' => $attribute['filters'], - 'default' => $attribute['default'] ?? null, - 'format' => $attribute['format'] ?? '' - ]); - } - - foreach ($files['indexes'] as $index) { - $indexes[] = new Document([ - '$id' => ID::custom($index['$id']), - 'type' => $index['type'], - 'attributes' => $index['attributes'], - 'lengths' => $index['lengths'], - 'orders' => $index['orders'], - ]); - } - - $dbForConsole->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes); - } - - $pools->reclaim(); - - Console::success('[Setup] - Server database init completed...'); }); - Console::success('Server started successfully (max payload is ' . number_format($payloadSize) . ' bytes)'); - Console::info("Master pid {$http->master_pid}, manager pid {$http->manager_pid}"); - - // listen ctrl + c - Process::signal(2, function () use ($http) { - Console::log('Stop by Ctrl+C'); - $http->shutdown(); +Http::init() + ->inject('authorization') + ->action(function (Authorization $authorization) { + $authorization->cleanRoles(); + $authorization->addRole(Role::any()->toString()); }); -}); - -$http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register) { - App::setResource('swooleRequest', fn () => $swooleRequest); - App::setResource('swooleResponse', fn () => $swooleResponse); - - $request = new Request($swooleRequest); - $response = new Response($swooleResponse); - - if (Files::isFileLoaded($request->getURI())) { - $time = (60 * 60 * 24 * 365 * 2); // 45 days cache - - $response - ->setContentType(Files::getFileMimeType($request->getURI())) - ->addHeader('Cache-Control', 'public, max-age=' . $time) - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache - ->send(Files::getFileContents($request->getURI())); - - return; - } - - $app = new App('UTC'); - - $pools = $register->get('pools'); - App::setResource('pools', fn () => $pools); - - try { - Authorization::cleanRoles(); - Authorization::setRole(Role::any()->toString()); - - $app->run($request, $response); - } catch (\Throwable $th) { - $version = System::getEnv('_APP_VERSION', 'UNKNOWN'); - - $logger = $app->getResource("logger"); - if ($logger) { - try { - /** @var Utopia\Database\Document $user */ - $user = $app->getResource('user'); - } catch (\Throwable $_th) { - // All good, user is optional information for logger - } - - $route = $app->getRoute(); - - $log = $app->getResource("log"); - - if (isset($user) && !$user->isEmpty()) { - $log->setUser(new User($user->getId())); - } - - $log->setNamespace("http"); - $log->setServer(\gethostname()); - $log->setVersion($version); - $log->setType(Log::TYPE_ERROR); - $log->setMessage($th->getMessage()); - - $log->addTag('method', $route->getMethod()); - $log->addTag('url', $route->getPath()); - $log->addTag('verboseType', get_class($th)); - $log->addTag('code', $th->getCode()); - // $log->addTag('projectId', $project->getId()); // TODO: Figure out how to get ProjectID, if it becomes relevant - $log->addTag('hostname', $request->getHostname()); - $log->addTag('locale', (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', ''))); - - $log->addExtra('file', $th->getFile()); - $log->addExtra('line', $th->getLine()); - $log->addExtra('trace', $th->getTraceAsString()); - $log->addExtra('roles', Authorization::getRoles()); - - $action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD"); - $log->setAction($action); - - $isProduction = System::getEnv('_APP_ENV', 'development') === 'production'; - $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING); - - try { - $responseCode = $logger->addLog($log); - Console::info('Error log pushed with status code: ' . $responseCode); - } catch (Throwable $th) { - Console::error('Error pushing log: ' . $th->getMessage()); - } - } - - Console::error('[Error] Type: ' . get_class($th)); - Console::error('[Error] Message: ' . $th->getMessage()); - Console::error('[Error] File: ' . $th->getFile()); - Console::error('[Error] Line: ' . $th->getLine()); - - $swooleResponse->setStatusCode(500); - - $output = ((App::isDevelopment())) ? [ - 'message' => 'Error: ' . $th->getMessage(), - 'code' => 500, - 'file' => $th->getFile(), - 'line' => $th->getLine(), - 'trace' => $th->getTrace(), - 'version' => $version, - ] : [ - 'message' => 'Error: Server Error', - 'code' => 500, - 'version' => $version, - ]; - - $swooleResponse->end(\json_encode($output)); - } finally { - $pools->reclaim(); - } -}); $http->start(); diff --git a/app/init.php b/app/init.php index f94c9665e7..0f9e8f0ef6 100644 --- a/app/init.php +++ b/app/init.php @@ -1,26 +1,12 @@ $value], JSON_PRESERVE_ZERO_FRACTION); - }, - function (mixed $value) { - if (is_null($value)) { - return; - } +$container = new Container(); +$registry = new Registry(); - return json_decode($value, true)['value']; - } -); - -Database::addFilter( - 'enum', - function (mixed $value, Document $attribute) { - if ($attribute->isSet('elements')) { - $attribute->removeAttribute('elements'); - } - - return $value; - }, - function (mixed $value, Document $attribute) { - $formatOptions = \json_decode($attribute->getAttribute('formatOptions', '[]'), true); - if (isset($formatOptions['elements'])) { - $attribute->setAttribute('elements', $formatOptions['elements']); - } - - return $value; - } -); - -Database::addFilter( - 'range', - function (mixed $value, Document $attribute) { - if ($attribute->isSet('min')) { - $attribute->removeAttribute('min'); - } - if ($attribute->isSet('max')) { - $attribute->removeAttribute('max'); - } - - return $value; - }, - function (mixed $value, Document $attribute) { - $formatOptions = json_decode($attribute->getAttribute('formatOptions', '[]'), true); - if (isset($formatOptions['min']) || isset($formatOptions['max'])) { - $attribute - ->setAttribute('min', $formatOptions['min']) - ->setAttribute('max', $formatOptions['max']); - } - - return $value; - } -); - -Database::addFilter( - 'subQueryAttributes', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - $attributes = $database->find('attributes', [ - Query::equal('collectionInternalId', [$document->getInternalId()]), - Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]), - Query::limit($database->getLimitForAttributes()), - ]); - - foreach ($attributes as $attribute) { - if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) { - $options = $attribute->getAttribute('options'); - foreach ($options as $key => $value) { - $attribute->setAttribute($key, $value); - } - $attribute->removeAttribute('options'); - } - } - - return $attributes; - } -); - -Database::addFilter( - 'subQueryIndexes', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return $database - ->find('indexes', [ - Query::equal('collectionInternalId', [$document->getInternalId()]), - Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]), - Query::limit($database->getLimitForIndexes()), - ]); - } -); - -Database::addFilter( - 'subQueryPlatforms', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return $database - ->find('platforms', [ - Query::equal('projectInternalId', [$document->getInternalId()]), - Query::limit(APP_LIMIT_SUBQUERY), - ]); - } -); - -Database::addFilter( - 'subQueryKeys', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return $database - ->find('keys', [ - Query::equal('projectInternalId', [$document->getInternalId()]), - Query::limit(APP_LIMIT_SUBQUERY), - ]); - } -); - -Database::addFilter( - 'subQueryWebhooks', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return $database - ->find('webhooks', [ - Query::equal('projectInternalId', [$document->getInternalId()]), - Query::limit(APP_LIMIT_SUBQUERY), - ]); - } -); - -Database::addFilter( - 'subQuerySessions', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return Authorization::skip(fn () => $database->find('sessions', [ - Query::equal('userInternalId', [$document->getInternalId()]), - Query::limit(APP_LIMIT_SUBQUERY), - ])); - } -); - -Database::addFilter( - 'subQueryTokens', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return Authorization::skip(fn () => $database - ->find('tokens', [ - Query::equal('userInternalId', [$document->getInternalId()]), - Query::limit(APP_LIMIT_SUBQUERY), - ])); - } -); - -Database::addFilter( - 'subQueryChallenges', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return Authorization::skip(fn () => $database - ->find('challenges', [ - Query::equal('userInternalId', [$document->getInternalId()]), - Query::limit(APP_LIMIT_SUBQUERY), - ])); - } -); - -Database::addFilter( - 'subQueryAuthenticators', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return Authorization::skip(fn () => $database - ->find('authenticators', [ - Query::equal('userInternalId', [$document->getInternalId()]), - Query::limit(APP_LIMIT_SUBQUERY), - ])); - } -); - -Database::addFilter( - 'subQueryMemberships', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return Authorization::skip(fn () => $database - ->find('memberships', [ - Query::equal('userInternalId', [$document->getInternalId()]), - Query::limit(APP_LIMIT_SUBQUERY), - ])); - } -); - -Database::addFilter( - 'subQueryVariables', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return $database - ->find('variables', [ - Query::equal('resourceInternalId', [$document->getInternalId()]), - Query::equal('resourceType', ['function']), - Query::limit(APP_LIMIT_SUBQUERY), - ]); - } -); - -Database::addFilter( - 'encrypt', - function (mixed $value) { - $key = System::getEnv('_APP_OPENSSL_KEY_V1'); - $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); - $tag = null; - - return json_encode([ - 'data' => OpenSSL::encrypt($value, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag), - 'method' => OpenSSL::CIPHER_AES_128_GCM, - 'iv' => \bin2hex($iv), - 'tag' => \bin2hex($tag ?? ''), - 'version' => '1', - ]); - }, - function (mixed $value) { - if (is_null($value)) { - return; - } - $value = json_decode($value, true); - $key = System::getEnv('_APP_OPENSSL_KEY_V' . $value['version']); - - return OpenSSL::decrypt($value['data'], $value['method'], $key, 0, hex2bin($value['iv']), hex2bin($value['tag'])); - } -); - -Database::addFilter( - 'subQueryProjectVariables', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return $database - ->find('variables', [ - Query::equal('resourceType', ['project']), - Query::limit(APP_LIMIT_SUBQUERY) - ]); - } -); - -Database::addFilter( - 'userSearch', - function (mixed $value, Document $user) { - $searchValues = [ - $user->getId(), - $user->getAttribute('email', ''), - $user->getAttribute('name', ''), - $user->getAttribute('phone', '') - ]; - - foreach ($user->getAttribute('labels', []) as $label) { - $searchValues[] = 'label:' . $label; - } - - $search = implode(' ', \array_filter($searchValues)); - - return $search; - }, - function (mixed $value) { - return $value; - } -); - -Database::addFilter( - 'subQueryTargets', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return Authorization::skip(fn () => $database - ->find('targets', [ - Query::equal('userInternalId', [$document->getInternalId()]), - Query::limit(APP_LIMIT_SUBQUERY) - ])); - } -); - -Database::addFilter( - 'subQueryTopicTargets', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - $targetIds = Authorization::skip(fn () => \array_map( - fn ($document) => $document->getAttribute('targetInternalId'), - $database->find('subscribers', [ - Query::equal('topicInternalId', [$document->getInternalId()]), - Query::limit(APP_LIMIT_SUBSCRIBERS_SUBQUERY) - ]) - )); - if (\count($targetIds) > 0) { - return $database->skipValidation(fn () => $database->find('targets', [ - Query::equal('$internalId', $targetIds) - ])); - } - return []; - } -); - -Database::addFilter( - 'providerSearch', - function (mixed $value, Document $provider) { - $searchValues = [ - $provider->getId(), - $provider->getAttribute('name', ''), - $provider->getAttribute('provider', ''), - $provider->getAttribute('type', '') - ]; - - $search = \implode(' ', \array_filter($searchValues)); - - return $search; - }, - function (mixed $value) { - return $value; - } -); - -Database::addFilter( - 'topicSearch', - function (mixed $value, Document $topic) { - $searchValues = [ - $topic->getId(), - $topic->getAttribute('name', ''), - $topic->getAttribute('description', ''), - ]; - - $search = \implode(' ', \array_filter($searchValues)); - - return $search; - }, - function (mixed $value) { - return $value; - } -); - -Database::addFilter( - 'messageSearch', - function (mixed $value, Document $message) { - $searchValues = [ - $message->getId(), - $message->getAttribute('description', ''), - $message->getAttribute('status', ''), - ]; - - $data = \json_decode($message->getAttribute('data', []), true); - $providerType = $message->getAttribute('providerType', ''); - - if ($providerType === MESSAGE_TYPE_EMAIL) { - $searchValues = \array_merge($searchValues, [$data['subject'], MESSAGE_TYPE_EMAIL]); - } elseif ($providerType === MESSAGE_TYPE_SMS) { - $searchValues = \array_merge($searchValues, [$data['content'], MESSAGE_TYPE_SMS]); - } else { - $searchValues = \array_merge($searchValues, [$data['title'], MESSAGE_TYPE_PUSH]); - } - - $search = \implode(' ', \array_filter($searchValues)); - - return $search; - }, - function (mixed $value) { - return $value; - } -); - -/** - * DB Formats - */ -Structure::addFormat(APP_DATABASE_ATTRIBUTE_EMAIL, function () { - return new Email(); -}, Database::VAR_STRING); - -Structure::addFormat(APP_DATABASE_ATTRIBUTE_DATETIME, function () { - return new DatetimeValidator(); -}, Database::VAR_DATETIME); - -Structure::addFormat(APP_DATABASE_ATTRIBUTE_ENUM, function ($attribute) { - $elements = $attribute['formatOptions']['elements']; - return new WhiteList($elements, true); -}, Database::VAR_STRING); - -Structure::addFormat(APP_DATABASE_ATTRIBUTE_IP, function () { - return new IP(); -}, Database::VAR_STRING); - -Structure::addFormat(APP_DATABASE_ATTRIBUTE_URL, function () { - return new URL(); -}, Database::VAR_STRING); - -Structure::addFormat(APP_DATABASE_ATTRIBUTE_INT_RANGE, function ($attribute) { - $min = $attribute['formatOptions']['min'] ?? -INF; - $max = $attribute['formatOptions']['max'] ?? INF; - return new Range($min, $max, Range::TYPE_INTEGER); -}, Database::VAR_INTEGER); - -Structure::addFormat(APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, function ($attribute) { - $min = $attribute['formatOptions']['min'] ?? -INF; - $max = $attribute['formatOptions']['max'] ?? INF; - return new Range($min, $max, Range::TYPE_FLOAT); -}, Database::VAR_FLOAT); - -/* - * Registry - */ -$register->set('logger', function () { +$registry->set('logger', function () { // Register error logger $providerName = System::getEnv('_APP_LOGGING_PROVIDER', ''); $providerConfig = System::getEnv('_APP_LOGGING_CONFIG', ''); @@ -776,12 +105,12 @@ $register->set('logger', function () { default => ['key' => $loggingProvider->getHost()], }; } catch (Throwable $th) { - // Fallback for older Appwrite versions up to 1.5.x that use _APP_LOGGING_PROVIDER and _APP_LOGGING_CONFIG environment variables Console::warning('Using deprecated logging configuration. Please update your configuration to use DSN format.' . $th->getMessage()); + // Fallback for older Appwrite versions up to 1.5.x that use _APP_LOGGING_PROVIDER and _APP_LOGGING_CONFIG environment variables $configChunks = \explode(";", $providerConfig); $providerConfig = match ($providerName) { - 'sentry' => [ 'key' => $configChunks[0], 'projectId' => $configChunks[1] ?? '', 'host' => '',], + 'sentry' => ['key' => $configChunks[0], 'projectId' => $configChunks[1] ?? '', 'host' => '',], 'logowl' => ['ticket' => $configChunks[0] ?? '', 'host' => ''], default => ['key' => $providerConfig], }; @@ -812,200 +141,167 @@ $register->set('logger', function () { return; } - return new Logger($adapter); + $logger = new Logger($adapter); + $logger->setSample(0.4); + return $logger; }); -$register->set('pools', function () { - $group = new Group(); +$registry->set('geodb', function () { + /** + * @disregard P1009 Undefined type + */ + return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2024-09.mmdb'); +}); - $fallbackForDB = 'db_main=' . AppwriteURL::unparse([ - 'scheme' => 'mariadb', - 'host' => System::getEnv('_APP_DB_HOST', 'mariadb'), - 'port' => System::getEnv('_APP_DB_PORT', '3306'), - 'user' => System::getEnv('_APP_DB_USER', ''), - 'pass' => System::getEnv('_APP_DB_PASS', ''), - 'path' => System::getEnv('_APP_DB_SCHEMA', ''), - ]); - $fallbackForRedis = 'redis_main=' . AppwriteURL::unparse([ - 'scheme' => 'redis', - 'host' => System::getEnv('_APP_REDIS_HOST', 'redis'), - 'port' => System::getEnv('_APP_REDIS_PORT', '6379'), - 'user' => System::getEnv('_APP_REDIS_USER', ''), - 'pass' => System::getEnv('_APP_REDIS_PASS', ''), - ]); +$registry->set('hooks', function () { + return new Hooks(); +}); - $connections = [ - 'console' => [ - 'type' => 'database', - 'dsns' => System::getEnv('_APP_CONNECTIONS_DB_CONSOLE', $fallbackForDB), - 'multiple' => false, - 'schemes' => ['mariadb', 'mysql'], - ], - 'database' => [ - 'type' => 'database', - 'dsns' => System::getEnv('_APP_CONNECTIONS_DB_PROJECT', $fallbackForDB), - 'multiple' => true, - 'schemes' => ['mariadb', 'mysql'], - ], - 'queue' => [ - 'type' => 'queue', - 'dsns' => System::getEnv('_APP_CONNECTIONS_QUEUE', $fallbackForRedis), - 'multiple' => false, - 'schemes' => ['redis'], - ], - 'pubsub' => [ - 'type' => 'pubsub', - 'dsns' => System::getEnv('_APP_CONNECTIONS_PUBSUB', $fallbackForRedis), - 'multiple' => false, - 'schemes' => ['redis'], - ], - 'cache' => [ - 'type' => 'cache', - 'dsns' => System::getEnv('_APP_CONNECTIONS_CACHE', $fallbackForRedis), - 'multiple' => true, - 'schemes' => ['redis'], - ], - ]; +$registry->set( + 'pools', + (function () { + $fallbackForDB = 'db_main=' . URL::unparse([ + 'scheme' => 'mariadb', + 'host' => System::getEnv('_APP_DB_HOST', 'mariadb'), + 'port' => System::getEnv('_APP_DB_PORT', '3306'), + 'user' => System::getEnv('_APP_DB_USER', ''), + 'pass' => System::getEnv('_APP_DB_PASS', ''), + 'path' => System::getEnv('_APP_DB_SCHEMA', ''), + ]); + $fallbackForRedis = 'redis_main=' . URL::unparse([ + 'scheme' => 'redis', + 'host' => System::getEnv('_APP_REDIS_HOST', 'redis'), + 'port' => System::getEnv('_APP_REDIS_PORT', '6379'), + 'user' => System::getEnv('_APP_REDIS_USER', ''), + 'pass' => System::getEnv('_APP_REDIS_PASS', ''), + ]); - $maxConnections = System::getEnv('_APP_CONNECTIONS_MAX', 151); - $instanceConnections = $maxConnections / System::getEnv('_APP_POOL_CLIENTS', 14); + $connections = [ + 'console' => [ + 'type' => 'database', + 'dsns' => System::getEnv('_APP_CONNECTIONS_DB_CONSOLE', $fallbackForDB), + 'multiple' => false, + 'schemes' => ['mariadb', 'mysql'], + ], + 'database' => [ + 'type' => 'database', + 'dsns' => System::getEnv('_APP_CONNECTIONS_DB_PROJECT', $fallbackForDB), + 'multiple' => true, + 'schemes' => ['mariadb', 'mysql'], + ], + 'queue' => [ + 'type' => 'queue', + 'dsns' => System::getEnv('_APP_CONNECTIONS_QUEUE', $fallbackForRedis), + 'multiple' => false, + 'schemes' => ['redis'], + ], + 'pubsub' => [ + 'type' => 'pubsub', + 'dsns' => System::getEnv('_APP_CONNECTIONS_PUBSUB', $fallbackForRedis), + 'multiple' => false, + 'schemes' => ['redis'], + ], + 'cache' => [ + 'type' => 'cache', + 'dsns' => System::getEnv('_APP_CONNECTIONS_CACHE', $fallbackForRedis), + 'multiple' => true, + 'schemes' => ['redis'], + ], + ]; - $multiprocessing = System::getEnv('_APP_SERVER_MULTIPROCESS', 'disabled') === 'enabled'; + $pools = []; + $poolSize = (int)System::getEnv('_APP_POOL_SIZE', 64); - if ($multiprocessing) { - $workerCount = swoole_cpu_num() * intval(System::getEnv('_APP_WORKER_PER_CORE', 6)); - } else { - $workerCount = 1; - } + foreach ($connections as $key => $connection) { + $dsns = $connection['dsns'] ?? ''; + $multiple = $connection['multiple'] ?? false; + $schemes = $connection['schemes'] ?? []; + $dsns = explode(',', $connection['dsns'] ?? ''); + $config = []; - if ($workerCount > $instanceConnections) { - throw new \Exception('Pool size is too small. Increase the number of allowed database connections or decrease the number of workers.', 500); - } + foreach ($dsns as &$dsn) { + $dsn = explode('=', $dsn); + $name = ($multiple) ? $key . '_' . $dsn[0] : $key; + $config[] = $name; + $dsn = $dsn[1] ?? ''; - $poolSize = (int)($instanceConnections / $workerCount); + if (empty($dsn)) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Missing value for DSN connection in {$key}"); + } - foreach ($connections as $key => $connection) { - $type = $connection['type'] ?? ''; - $multiple = $connection['multiple'] ?? false; - $schemes = $connection['schemes'] ?? []; - $config = []; - $dsns = explode(',', $connection['dsns'] ?? ''); - foreach ($dsns as &$dsn) { - $dsn = explode('=', $dsn); - $name = ($multiple) ? $key . '_' . $dsn[0] : $key; - $dsn = $dsn[1] ?? ''; - $config[] = $name; - if (empty($dsn)) { - //throw new Exception(Exception::GENERAL_SERVER_ERROR, "Missing value for DSN connection in {$key}"); - continue; - } + $dsn = new DSN($dsn); + $dsnHost = $dsn->getHost(); + $dsnPort = $dsn->getPort(); + $dsnUser = $dsn->getUser(); + $dsnPass = $dsn->getPassword(); + $dsnScheme = $dsn->getScheme(); + $dsnDatabase = $dsn->getPath(); - $dsn = new DSN($dsn); - $dsnHost = $dsn->getHost(); - $dsnPort = $dsn->getPort(); - $dsnUser = $dsn->getUser(); - $dsnPass = $dsn->getPassword(); - $dsnScheme = $dsn->getScheme(); - $dsnDatabase = $dsn->getPath(); + if (!in_array($dsnScheme, $schemes)) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Invalid console database scheme"); + } - if (!in_array($dsnScheme, $schemes)) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, "Invalid console database scheme"); - } + /** + * Get Resource + * + * Creation could be reused accross connection types like database, cache, queue, etc. + * + * Resource assignment to an adapter will happen below. + */ + switch ($dsnScheme) { + case 'mysql': + case 'mariadb': + $pool = new PDOPool( + (new PDOConfig()) + ->withHost($dsnHost) + ->withPort($dsnPort) + ->withDbName($dsnDatabase) + ->withCharset('utf8mb4') + ->withUsername($dsnUser) + ->withPassword($dsnPass) + ->withOptions([ + // No need to set PDO::ATTR_ERRMODE it is overwitten in PDOProxy + // PDO::ATTR_TIMEOUT => 3, // Seconds + // PDO::ATTR_PERSISTENT => true, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => true, + PDO::ATTR_STRINGIFY_FETCHES => true, + PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true, - /** - * Get Resource - * - * Creation could be reused across connection types like database, cache, queue, etc. - * - * Resource assignment to an adapter will happen below. - */ - $resource = match ($dsnScheme) { - 'mysql', - 'mariadb' => function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) { - return new PDOProxy(function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) { - return new PDO("mysql:host={$dsnHost};port={$dsnPort};dbname={$dsnDatabase};charset=utf8mb4", $dsnUser, $dsnPass, array( - PDO::ATTR_TIMEOUT => 3, // Seconds - PDO::ATTR_PERSISTENT => true, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::ATTR_EMULATE_PREPARES => true, - PDO::ATTR_STRINGIFY_FETCHES => true - )); - }); - }, - 'redis' => function () use ($dsnHost, $dsnPort, $dsnPass) { - $redis = new Redis(); - @$redis->pconnect($dsnHost, (int)$dsnPort); - if ($dsnPass) { - $redis->auth($dsnPass); - } - $redis->setOption(Redis::OPT_READ_TIMEOUT, -1); - - return $redis; - }, - default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Invalid scheme'), - }; - - $pool = new Pool($name, $poolSize, function () use ($type, $resource, $dsn) { - // Get Adapter - switch ($type) { - case 'database': - $adapter = match ($dsn->getScheme()) { - 'mariadb' => new MariaDB($resource()), - 'mysql' => new MySQL($resource()), - default => null - }; - - $adapter->setDatabase($dsn->getPath()); + ]), + $poolSize + ); break; - case 'pubsub': - $adapter = $resource(); - break; - case 'queue': - $adapter = match ($dsn->getScheme()) { - 'redis' => new Queue\Connection\Redis($dsn->getHost(), $dsn->getPort()), - default => null - }; - break; - case 'cache': - $adapter = match ($dsn->getScheme()) { - 'redis' => new RedisCache($resource()), - default => null - }; + case 'redis': + $pool = new RedisPool( + (new RedisConfig()) + ->withHost($dsnHost) + ->withPort((int)$dsnPort) + ->withAuth($dsnPass ?? ''), + $poolSize + ); break; default: - throw new Exception(Exception::GENERAL_SERVER_ERROR, "Server error: Missing adapter implementation."); + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Invalid scheme"); } - return $adapter; - }); + $pools['pools-' . $key . '-' . $name] = [ + 'pool' => $pool, + 'dsn' => $dsn, + ]; + } - $group->add($pool); + Config::setParam('pools-' . $key, $config); } - Config::setParam('pools-' . $key, $config); - } + return function () use ($pools): array { + return $pools; + }; + })() +); - return $group; -}); - -$register->set('db', function () { - // This is usually for our workers or CLI commands scope - $dbHost = System::getEnv('_APP_DB_HOST', ''); - $dbPort = System::getEnv('_APP_DB_PORT', ''); - $dbUser = System::getEnv('_APP_DB_USER', ''); - $dbPass = System::getEnv('_APP_DB_PASS', ''); - $dbScheme = System::getEnv('_APP_DB_SCHEMA', ''); - - return new PDO( - "mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4", - $dbUser, - $dbPass, - SQL::getPDOAttributes() - ); -}); - -$register->set('smtp', function () { +$registry->set('smtp', function () { $mail = new PHPMailer(true); $mail->isSMTP(); @@ -1033,739 +329,65 @@ $register->set('smtp', function () { return $mail; }); -$register->set('geodb', function () { - return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2024-09.mmdb'); -}); -$register->set('passwordsDictionary', function () { - $content = \file_get_contents(__DIR__ . '/assets/security/10k-common-passwords'); - $content = explode("\n", $content); - $content = array_flip($content); - return $content; -}); -$register->set('promiseAdapter', function () { + +$registry->set('promiseAdapter', function () { return new Swoole(); }); -$register->set('hooks', function () { - return new Hooks(); -}); -/* - * Localization - */ -Locale::$exceptions = false; -$locales = Config::getParam('locale-codes', []); +$registry->set('db', function () { + // This is usually for our workers or CLI commands scope + $dbHost = System::getEnv('_APP_DB_HOST', ''); + $dbPort = System::getEnv('_APP_DB_PORT', ''); + $dbUser = System::getEnv('_APP_DB_USER', ''); + $dbPass = System::getEnv('_APP_DB_PASS', ''); + $dbScheme = System::getEnv('_APP_DB_SCHEMA', ''); -foreach ($locales as $locale) { - $code = $locale['code']; - - $path = __DIR__ . '/config/locale/translations/' . $code . '.json'; - - if (!\file_exists($path)) { - $path = __DIR__ . '/config/locale/translations/' . \substr($code, 0, 2) . '.json'; // if `ar-ae` doesn't exist, look for `ar` - if (!\file_exists($path)) { - $path = __DIR__ . '/config/locale/translations/en.json'; // if none translation exists, use default from `en.json` - } - } - - Locale::setLanguageFromJSON($code, $path); -} - -\stream_context_set_default([ // Set global user agent and http settings - 'http' => [ - 'method' => 'GET', - 'user_agent' => \sprintf( - APP_USERAGENT, - System::getEnv('_APP_VERSION', 'UNKNOWN'), - System::getEnv('_APP_EMAIL_SECURITY', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)) - ), - 'timeout' => 2, - ], -]); - -// Runtime Execution -App::setResource('log', fn () => new Log()); -App::setResource('logger', function ($register) { - return $register->get('logger'); -}, ['register']); - -App::setResource('hooks', function ($register) { - return $register->get('hooks'); -}, ['register']); - -App::setResource('register', fn () => $register); -App::setResource('locale', fn () => new Locale(System::getEnv('_APP_LOCALE', 'en'))); - -App::setResource('localeCodes', function () { - return array_map(fn ($locale) => $locale['code'], Config::getParam('locale-codes', [])); -}); - -// Queues -App::setResource('queue', function (Group $pools) { - return $pools->get('queue')->pop()->getResource(); -}, ['pools']); -App::setResource('queueForMessaging', function (Connection $queue) { - return new Messaging($queue); -}, ['queue']); -App::setResource('queueForMails', function (Connection $queue) { - return new Mail($queue); -}, ['queue']); -App::setResource('queueForBuilds', function (Connection $queue) { - return new Build($queue); -}, ['queue']); -App::setResource('queueForDatabase', function (Connection $queue) { - return new EventDatabase($queue); -}, ['queue']); -App::setResource('queueForDeletes', function (Connection $queue) { - return new Delete($queue); -}, ['queue']); -App::setResource('queueForEvents', function (Connection $queue) { - return new Event($queue); -}, ['queue']); -App::setResource('queueForAudits', function (Connection $queue) { - return new Audit($queue); -}, ['queue']); -App::setResource('queueForFunctions', function (Connection $queue) { - return new Func($queue); -}, ['queue']); -App::setResource('queueForUsage', function (Connection $queue) { - return new Usage($queue); -}, ['queue']); -App::setResource('queueForCertificates', function (Connection $queue) { - return new Certificate($queue); -}, ['queue']); -App::setResource('queueForMigrations', function (Connection $queue) { - return new Migration($queue); -}, ['queue']); -App::setResource('clients', function ($request, $console, $project) { - $console->setAttribute('platforms', [ // Always allow current host - '$collection' => ID::custom('platforms'), - 'name' => 'Current Host', - 'type' => Origin::CLIENT_TYPE_WEB, - 'hostname' => $request->getHostname(), - ], Document::SET_TYPE_APPEND); - - $hostnames = explode(',', System::getEnv('_APP_CONSOLE_HOSTNAMES', '')); - $validator = new Hostname(); - foreach ($hostnames as $hostname) { - $hostname = trim($hostname); - if (!$validator->isValid($hostname)) { - continue; - } - $console->setAttribute('platforms', [ - '$collection' => ID::custom('platforms'), - 'type' => Origin::CLIENT_TYPE_WEB, - 'name' => $hostname, - 'hostname' => $hostname, - ], Document::SET_TYPE_APPEND); - } - - /** - * Get All verified client URLs for both console and current projects - * + Filter for duplicated entries - */ - $clientsConsole = \array_map( - fn ($node) => $node['hostname'], - \array_filter( - $console->getAttribute('platforms', []), - fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB) && !empty($node['hostname'])) - ) + return new PDO( + "mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4", + $dbUser, + $dbPass, + SQL::getPDOAttributes() ); - - $clients = $clientsConsole; - $platforms = $project->getAttribute('platforms', []); - - foreach ($platforms as $node) { - if ( - isset($node['type']) && - ($node['type'] === Origin::CLIENT_TYPE_WEB || - $node['type'] === Origin::CLIENT_TYPE_FLUTTER_WEB) && - !empty($node['hostname']) - ) { - $clients[] = $node['hostname']; - } - } - - return \array_unique($clients); -}, ['request', 'console', 'project']); - -App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForConsole) { - /** @var Appwrite\Utopia\Request $request */ - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Document $project */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var Utopia\Database\Database $dbForConsole */ - /** @var string $mode */ - - Authorization::setDefaultStatus(true); - - Auth::setCookieName('a_session_' . $project->getId()); - - if (APP_MODE_ADMIN === $mode) { - Auth::setCookieName('a_session_' . $console->getId()); - } - - $session = Auth::decodeSession( - $request->getCookie( - Auth::$cookieName, // Get sessions - $request->getCookie(Auth::$cookieName . '_legacy', '') - ) - ); - - // Get session from header for SSR clients - if (empty($session['id']) && empty($session['secret'])) { - $sessionHeader = $request->getHeader('x-appwrite-session', ''); - - if (!empty($sessionHeader)) { - $session = Auth::decodeSession($sessionHeader); - } - } - - // Get fallback session from old clients (no SameSite support) or clients who block 3rd-party cookies - if ($response) { - $response->addHeader('X-Debug-Fallback', 'false'); - } - - if (empty($session['id']) && empty($session['secret'])) { - if ($response) { - $response->addHeader('X-Debug-Fallback', 'true'); - } - $fallback = $request->getHeader('x-fallback-cookies', ''); - $fallback = \json_decode($fallback, true); - $session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : '')); - } - - Auth::$unique = $session['id'] ?? ''; - Auth::$secret = $session['secret'] ?? ''; - - if (APP_MODE_ADMIN !== $mode) { - if ($project->isEmpty()) { - $user = new Document([]); - } else { - if ($project->getId() === 'console') { - $user = $dbForConsole->getDocument('users', Auth::$unique); - } else { - $user = $dbForProject->getDocument('users', Auth::$unique); - } - } - } else { - $user = $dbForConsole->getDocument('users', Auth::$unique); - } - - if ( - $user->isEmpty() // Check a document has been found in the DB - || !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) - ) { // Validate user has valid login token - $user = new Document([]); - } - - if (APP_MODE_ADMIN === $mode) { - if ($user->find('teamInternalId', $project->getAttribute('teamInternalId'), 'memberships')) { - Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users. - } else { - $user = new Document([]); - } - } - - $authJWT = $request->getHeader('x-appwrite-jwt', ''); - - if (!empty($authJWT) && !$project->isEmpty()) { // JWT authentication - $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0); - - try { - $payload = $jwt->decode($authJWT); - } catch (JWTException $error) { - throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage()); - } - - $jwtUserId = $payload['userId'] ?? ''; - if (!empty($jwtUserId)) { - $user = $dbForProject->getDocument('users', $jwtUserId); - } - - $jwtSessionId = $payload['sessionId'] ?? ''; - if (!empty($jwtSessionId)) { - if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token - $user = new Document([]); - } - } - } - - $dbForProject->setMetadata('user', $user->getId()); - $dbForConsole->setMetadata('user', $user->getId()); - - return $user; -}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForConsole']); - -App::setResource('project', function ($dbForConsole, $request, $console) { - /** @var Appwrite\Utopia\Request $request */ - /** @var Utopia\Database\Database $dbForConsole */ - /** @var Utopia\Database\Document $console */ - - $projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', '')); - - if (empty($projectId) || $projectId === 'console') { - return $console; - } - - $project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId)); - - return $project; -}, ['dbForConsole', 'request', 'console']); - -App::setResource('session', function (Document $user) { - if ($user->isEmpty()) { - return; - } - - $sessions = $user->getAttribute('sessions', []); - $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret); - - if (!$sessionId) { - return; - } - - foreach ($sessions as $session) {/** @var Document $session */ - if ($sessionId === $session->getId()) { - return $session; - } - } - - return; -}, ['user']); - -App::setResource('console', function () { - return new Document([ - '$id' => ID::custom('console'), - '$internalId' => ID::custom('console'), - 'name' => 'Appwrite', - '$collection' => ID::custom('projects'), - 'description' => 'Appwrite core engine', - 'logo' => '', - 'teamId' => -1, - 'webhooks' => [], - 'keys' => [], - 'platforms' => [ - [ - '$collection' => ID::custom('platforms'), - 'name' => 'Localhost', - 'type' => Origin::CLIENT_TYPE_WEB, - 'hostname' => 'localhost', - ], // Current host is added on app init - ], - 'legalName' => '', - 'legalCountry' => '', - 'legalState' => '', - 'legalCity' => '', - 'legalAddress' => '', - 'legalTaxId' => '', - 'auths' => [ - 'mockNumbers' => [], - 'invites' => System::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled', - 'limit' => (System::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user - 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds - 'sessionAlerts' => System::getEnv('_APP_CONSOLE_SESSION_ALERTS', 'disabled') === 'enabled' - ], - 'authWhitelistEmails' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [], - 'authWhitelistIPs' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [], - 'oAuthProviders' => [ - 'githubEnabled' => true, - 'githubSecret' => System::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''), - 'githubAppid' => System::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '') - ], - ]); -}, []); - -App::setResource('dbForProject', function (Group $pools, Database $dbForConsole, Cache $cache, Document $project) { - if ($project->isEmpty() || $project->getId() === 'console') { - return $dbForConsole; - } - - try { - $dsn = new DSN($project->getAttribute('database')); - } catch (\InvalidArgumentException) { - // TODO: Temporary until all projects are using shared tables - $dsn = new DSN('mysql://' . $project->getAttribute('database')); - } - - $dbAdapter = $pools - ->get($dsn->getHost()) - ->pop() - ->getResource(); - - $database = new Database($dbAdapter, $cache); - - $database - ->setMetadata('host', \gethostname()) - ->setMetadata('project', $project->getId()) - ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); - - 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')); - } - - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { - $database - ->setSharedTables(true) - ->setTenant($project->getInternalId()) - ->setNamespace($dsn->getParam('namespace')); - } else { - $database - ->setSharedTables(false) - ->setTenant(null) - ->setNamespace('_' . $project->getInternalId()); - } - - return $database; -}, ['pools', 'dbForConsole', 'cache', 'project']); - -App::setResource('dbForConsole', function (Group $pools, Cache $cache) { - $dbAdapter = $pools - ->get('console') - ->pop() - ->getResource(); - - $database = new Database($dbAdapter, $cache); - - $database - ->setNamespace('_console') - ->setMetadata('host', \gethostname()) - ->setMetadata('project', 'console') - ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); - - return $database; -}, ['pools', 'cache']); - -App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) { - $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools - - return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases) { - if ($project->isEmpty() || $project->getId() === 'console') { - return $dbForConsole; - } - - try { - $dsn = new DSN($project->getAttribute('database')); - } catch (\InvalidArgumentException) { - // TODO: Temporary until all projects are using shared tables - $dsn = new DSN('mysql://' . $project->getAttribute('database')); - } - - $configure = (function (Database $database) use ($project, $dsn) { - $database - ->setMetadata('host', \gethostname()) - ->setMetadata('project', $project->getId()) - ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); - - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { - $database - ->setSharedTables(true) - ->setTenant($project->getInternalId()) - ->setNamespace($dsn->getParam('namespace')); - } else { - $database - ->setSharedTables(false) - ->setTenant(null) - ->setNamespace('_' . $project->getInternalId()); - } - }); - - if (isset($databases[$dsn->getHost()])) { - $database = $databases[$dsn->getHost()]; - $configure($database); - return $database; - } - - $dbAdapter = $pools - ->get($dsn->getHost()) - ->pop() - ->getResource(); - - $database = new Database($dbAdapter, $cache); - $databases[$dsn->getHost()] = $database; - $configure($database); - - return $database; - }; -}, ['pools', 'dbForConsole', 'cache']); - -App::setResource('cache', function (Group $pools) { - $list = Config::getParam('pools-cache', []); - $adapters = []; - - foreach ($list as $value) { - $adapters[] = $pools - ->get($value) - ->pop() - ->getResource() - ; - } - - return new Cache(new Sharding($adapters)); -}, ['pools']); - -App::setResource('deviceForLocal', function () { - return new Local(); }); -App::setResource('deviceForFiles', function ($project) { - return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId()); -}, ['project']); +// Autoload +class_exists(JWT::class, true); +class_exists(DSN::class, true); +class_exists(Log::class, true); +class_exists(TOTP::class, true); +class_exists(Mail::class, true); +class_exists(Func::class, true); +class_exists(Cache::class, true); +class_exists(Abuse::class, true); +class_exists(MySQL::class, true); +class_exists(Event::class, true); +class_exists(Audit::class, true); +class_exists(Usage::class, true); +class_exists(Local::class, true); +class_exists(Build::class, true); +class_exists(Locale::class, true); +class_exists(Delete::class, true); +class_exists(GitHub::class, true); +class_exists(Schema::class, true); +class_exists(Domain::class, true); +class_exists(Console::class, true); +class_exists(Request::class, true); +class_exists(MariaDB::class, true); +class_exists(Document::class, true); +class_exists(Sharding::class, true); +class_exists(Database::class, true); +class_exists(Hostname::class, true); +class_exists(TimeLimit::class, true); +class_exists(Migration::class, true); +class_exists(Messaging::class, true); +class_exists(CacheRedis::class, true); +class_exists(Connections::class, true); +class_exists(Certificate::class, true); +class_exists(EventDatabase::class, true); +class_exists(Authorization::class, true); +class_exists(Authentication::class, true); +class_exists(Queue\Connection\Redis::class, true); -App::setResource('deviceForFunctions', function ($project) { - return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId()); -}, ['project']); +require_once __DIR__ . '/init/resources.php'; -App::setResource('deviceForBuilds', function ($project) { - return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId()); -}, ['project']); - -function getDevice($root): Device -{ - $connection = System::getEnv('_APP_CONNECTIONS_STORAGE', ''); - - if (!empty($connection)) { - $acl = 'private'; - $device = Storage::DEVICE_LOCAL; - $accessKey = ''; - $accessSecret = ''; - $bucket = ''; - $region = ''; - - try { - $dsn = new DSN($connection); - $device = $dsn->getScheme(); - $accessKey = $dsn->getUser() ?? ''; - $accessSecret = $dsn->getPassword() ?? ''; - $bucket = $dsn->getPath() ?? ''; - $region = $dsn->getParam('region'); - } catch (\Throwable $e) { - Console::warning($e->getMessage() . 'Invalid DSN. Defaulting to Local device.'); - } - - switch ($device) { - case Storage::DEVICE_S3: - return new S3($root, $accessKey, $accessSecret, $bucket, $region, $acl); - case STORAGE::DEVICE_DO_SPACES: - $device = new DOSpaces($root, $accessKey, $accessSecret, $bucket, $region, $acl); - $device->setHttpVersion(S3::HTTP_VERSION_1_1); - return $device; - case Storage::DEVICE_BACKBLAZE: - return new Backblaze($root, $accessKey, $accessSecret, $bucket, $region, $acl); - case Storage::DEVICE_LINODE: - return new Linode($root, $accessKey, $accessSecret, $bucket, $region, $acl); - case Storage::DEVICE_WASABI: - return new Wasabi($root, $accessKey, $accessSecret, $bucket, $region, $acl); - case Storage::DEVICE_LOCAL: - default: - return new Local($root); - } - } else { - switch (strtolower(System::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) ?? '')) { - case Storage::DEVICE_LOCAL: - default: - return new Local($root); - case Storage::DEVICE_S3: - $s3AccessKey = System::getEnv('_APP_STORAGE_S3_ACCESS_KEY', ''); - $s3SecretKey = System::getEnv('_APP_STORAGE_S3_SECRET', ''); - $s3Region = System::getEnv('_APP_STORAGE_S3_REGION', ''); - $s3Bucket = System::getEnv('_APP_STORAGE_S3_BUCKET', ''); - $s3Acl = 'private'; - return new S3($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl); - case Storage::DEVICE_DO_SPACES: - $doSpacesAccessKey = System::getEnv('_APP_STORAGE_DO_SPACES_ACCESS_KEY', ''); - $doSpacesSecretKey = System::getEnv('_APP_STORAGE_DO_SPACES_SECRET', ''); - $doSpacesRegion = System::getEnv('_APP_STORAGE_DO_SPACES_REGION', ''); - $doSpacesBucket = System::getEnv('_APP_STORAGE_DO_SPACES_BUCKET', ''); - $doSpacesAcl = 'private'; - $device = new DOSpaces($root, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl); - $device->setHttpVersion(S3::HTTP_VERSION_1_1); - return $device; - case Storage::DEVICE_BACKBLAZE: - $backblazeAccessKey = System::getEnv('_APP_STORAGE_BACKBLAZE_ACCESS_KEY', ''); - $backblazeSecretKey = System::getEnv('_APP_STORAGE_BACKBLAZE_SECRET', ''); - $backblazeRegion = System::getEnv('_APP_STORAGE_BACKBLAZE_REGION', ''); - $backblazeBucket = System::getEnv('_APP_STORAGE_BACKBLAZE_BUCKET', ''); - $backblazeAcl = 'private'; - return new Backblaze($root, $backblazeAccessKey, $backblazeSecretKey, $backblazeBucket, $backblazeRegion, $backblazeAcl); - case Storage::DEVICE_LINODE: - $linodeAccessKey = System::getEnv('_APP_STORAGE_LINODE_ACCESS_KEY', ''); - $linodeSecretKey = System::getEnv('_APP_STORAGE_LINODE_SECRET', ''); - $linodeRegion = System::getEnv('_APP_STORAGE_LINODE_REGION', ''); - $linodeBucket = System::getEnv('_APP_STORAGE_LINODE_BUCKET', ''); - $linodeAcl = 'private'; - return new Linode($root, $linodeAccessKey, $linodeSecretKey, $linodeBucket, $linodeRegion, $linodeAcl); - case Storage::DEVICE_WASABI: - $wasabiAccessKey = System::getEnv('_APP_STORAGE_WASABI_ACCESS_KEY', ''); - $wasabiSecretKey = System::getEnv('_APP_STORAGE_WASABI_SECRET', ''); - $wasabiRegion = System::getEnv('_APP_STORAGE_WASABI_REGION', ''); - $wasabiBucket = System::getEnv('_APP_STORAGE_WASABI_BUCKET', ''); - $wasabiAcl = 'private'; - return new Wasabi($root, $wasabiAccessKey, $wasabiSecretKey, $wasabiBucket, $wasabiRegion, $wasabiAcl); - } - } -} - -App::setResource('mode', function ($request) { - /** @var Appwrite\Utopia\Request $request */ - - /** - * Defines the mode for the request: - * - 'default' => Requests for Client and Server Side - * - 'admin' => Request from the Console on non-console projects - */ - return $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT)); -}, ['request']); - -App::setResource('geodb', function ($register) { - /** @var Utopia\Registry\Registry $register */ - return $register->get('geodb'); -}, ['register']); - -App::setResource('passwordsDictionary', function ($register) { - /** @var Utopia\Registry\Registry $register */ - return $register->get('passwordsDictionary'); -}, ['register']); - - -App::setResource('servers', function () { - $platforms = Config::getParam('platforms'); - $server = $platforms[APP_PLATFORM_SERVER]; - - $languages = array_map(function ($language) { - return strtolower($language['name']); - }, $server['sdks']); - - return $languages; -}); - -App::setResource('promiseAdapter', function ($register) { - return $register->get('promiseAdapter'); -}, ['register']); - -App::setResource('schema', function ($utopia, $dbForProject) { - - $complexity = function (int $complexity, array $args) { - $queries = Query::parseQueries($args['queries'] ?? []); - $query = Query::getByType($queries, [Query::TYPE_LIMIT])[0] ?? null; - $limit = $query ? $query->getValue() : APP_LIMIT_LIST_DEFAULT; - - return $complexity * $limit; - }; - - $attributes = function (int $limit, int $offset) use ($dbForProject) { - $attrs = Authorization::skip(fn () => $dbForProject->find('attributes', [ - Query::limit($limit), - Query::offset($offset), - ])); - - return \array_map(function ($attr) { - return $attr->getArrayCopy(); - }, $attrs); - }; - - $urls = [ - 'list' => function (string $databaseId, string $collectionId, array $args) { - return "/v1/databases/$databaseId/collections/$collectionId/documents"; - }, - 'create' => function (string $databaseId, string $collectionId, array $args) { - return "/v1/databases/$databaseId/collections/$collectionId/documents"; - }, - 'read' => function (string $databaseId, string $collectionId, array $args) { - return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}"; - }, - 'update' => function (string $databaseId, string $collectionId, array $args) { - return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}"; - }, - 'delete' => function (string $databaseId, string $collectionId, array $args) { - return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}"; - }, - ]; - - $params = [ - 'list' => function (string $databaseId, string $collectionId, array $args) { - return [ 'queries' => $args['queries']]; - }, - 'create' => function (string $databaseId, string $collectionId, array $args) { - $id = $args['id'] ?? 'unique()'; - $permissions = $args['permissions'] ?? null; - - unset($args['id']); - unset($args['permissions']); - - // Order must be the same as the route params - return [ - 'databaseId' => $databaseId, - 'documentId' => $id, - 'collectionId' => $collectionId, - 'data' => $args, - 'permissions' => $permissions, - ]; - }, - 'update' => function (string $databaseId, string $collectionId, array $args) { - $documentId = $args['id']; - $permissions = $args['permissions'] ?? null; - - unset($args['id']); - unset($args['permissions']); - - // Order must be the same as the route params - return [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'documentId' => $documentId, - 'data' => $args, - 'permissions' => $permissions, - ]; - }, - ]; - - return Schema::build( - $utopia, - $complexity, - $attributes, - $urls, - $params, - ); -}, ['utopia', 'dbForProject']); - -App::setResource('contributors', function () { - $path = 'app/config/contributors.json'; - $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : []; - return $list; -}); - -App::setResource('employees', function () { - $path = 'app/config/employees.json'; - $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : []; - return $list; -}); - -App::setResource('heroes', function () { - $path = 'app/config/heroes.json'; - $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : []; - return $list; -}); - -App::setResource('gitHub', function (Cache $cache) { - return new VcsGitHub($cache); -}, ['cache']); - -App::setResource('requestTimestamp', function ($request) { - //TODO: Move this to the Request class itself - $timestampHeader = $request->getHeader('x-appwrite-timestamp'); - $requestTimestamp = null; - if (!empty($timestampHeader)) { - try { - $requestTimestamp = new \DateTime($timestampHeader); - } catch (\Throwable $e) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid X-Appwrite-Timestamp header value'); - } - } - return $requestTimestamp; -}, ['request']); -App::setResource('plan', function (array $plan = []) { - return []; -}); +Models::init(); diff --git a/app/init/config.php b/app/init/config.php new file mode 100644 index 0000000000..bc8333b5d9 --- /dev/null +++ b/app/init/config.php @@ -0,0 +1,37 @@ + $value], JSON_PRESERVE_ZERO_FRACTION); + }, + function (mixed $value) { + if (is_null($value)) { + return; + } + + return json_decode($value, true)['value']; + } +); + +Database::addFilter( + 'enum', + function (mixed $value, Document $attribute) { + if ($attribute->isSet('elements')) { + $attribute->removeAttribute('elements'); + } + + return $value; + }, + function (mixed $value, Document $attribute) { + $formatOptions = \json_decode($attribute->getAttribute('formatOptions', '[]'), true); + if (isset($formatOptions['elements'])) { + $attribute->setAttribute('elements', $formatOptions['elements']); + } + + return $value; + } +); + +Database::addFilter( + 'range', + function (mixed $value, Document $attribute) { + if ($attribute->isSet('min')) { + $attribute->removeAttribute('min'); + } + if ($attribute->isSet('max')) { + $attribute->removeAttribute('max'); + } + + return $value; + }, + function (mixed $value, Document $attribute) { + $formatOptions = json_decode($attribute->getAttribute('formatOptions', '[]'), true); + if (isset($formatOptions['min']) || isset($formatOptions['max'])) { + $attribute + ->setAttribute('min', $formatOptions['min']) + ->setAttribute('max', $formatOptions['max']) + ; + } + + return $value; + } +); + +Database::addFilter( + 'subQueryAttributes', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + $attributes = $database->find('attributes', [ + Query::equal('collectionInternalId', [$document->getInternalId()]), + Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]), + Query::limit($database->getLimitForAttributes()), + ]); + + foreach ($attributes as $attribute) { + if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) { + $options = $attribute->getAttribute('options'); + foreach ($options as $key => $value) { + $attribute->setAttribute($key, $value); + } + $attribute->removeAttribute('options'); + } + } + + return $attributes; + } +); + +Database::addFilter( + 'subQueryIndexes', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database + ->find('indexes', [ + Query::equal('collectionInternalId', [$document->getInternalId()]), + Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]), + Query::limit($database->getLimitForIndexes()), + ]); + } +); + +Database::addFilter( + 'subQueryPlatforms', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database + ->find('platforms', [ + Query::equal('projectInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ]); + } +); + +Database::addFilter( + 'subQueryKeys', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database + ->find('keys', [ + Query::equal('projectInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ]); + } +); + +Database::addFilter( + 'subQueryWebhooks', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database + ->find('webhooks', [ + Query::equal('projectInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ]); + } +); + +Database::addFilter( + 'subQuerySessions', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database->getAuthorization()->skip(fn () => $database->find('sessions', [ + Query::equal('userInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ])); + } +); + +Database::addFilter( + 'subQueryTokens', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database->getAuthorization()->skip(fn () => $database + ->find('tokens', [ + Query::equal('userInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ])); + } +); + +Database::addFilter( + 'subQueryChallenges', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database->getAuthorization()->skip(fn () => $database + ->find('challenges', [ + Query::equal('userInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ])); + } +); + +Database::addFilter( + 'subQueryAuthenticators', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database->getAuthorization()->skip(fn () => $database + ->find('authenticators', [ + Query::equal('userInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ])); + } +); + +Database::addFilter( + 'subQueryMemberships', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database->getAuthorization()->skip(fn () => $database + ->find('memberships', [ + Query::equal('userInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ])); + } +); + +Database::addFilter( + 'subQueryVariables', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database + ->find('variables', [ + Query::equal('resourceInternalId', [$document->getInternalId()]), + Query::equal('resourceType', ['function']), + Query::limit(APP_LIMIT_SUBQUERY), + ]); + } +); + +Database::addFilter( + 'encrypt', + function (mixed $value) { + $key = System::getEnv('_APP_OPENSSL_KEY_V1'); + $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); + $tag = null; + + return json_encode([ + 'data' => OpenSSL::encrypt($value, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag), + 'method' => OpenSSL::CIPHER_AES_128_GCM, + 'iv' => \bin2hex($iv), + 'tag' => \bin2hex($tag ?? ''), + 'version' => '1', + ]); + }, + function (mixed $value) { + if (is_null($value)) { + return; + } + $value = json_decode($value, true); + $key = System::getEnv('_APP_OPENSSL_KEY_V' . $value['version']); + + return OpenSSL::decrypt($value['data'], $value['method'], $key, 0, hex2bin($value['iv']), hex2bin($value['tag'])); + } +); + +Database::addFilter( + 'subQueryProjectVariables', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database + ->find('variables', [ + Query::equal('resourceType', ['project']), + Query::limit(APP_LIMIT_SUBQUERY) + ]); + } +); + +Database::addFilter( + 'userSearch', + function (mixed $value, Document $user) { + $searchValues = [ + $user->getId(), + $user->getAttribute('email', ''), + $user->getAttribute('name', ''), + $user->getAttribute('phone', '') + ]; + + foreach ($user->getAttribute('labels', []) as $label) { + $searchValues[] = 'label:' . $label; + } + + $search = implode(' ', \array_filter($searchValues)); + + return $search; + }, + function (mixed $value) { + return $value; + } +); + +Database::addFilter( + 'subQueryTargets', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database->getAuthorization()->skip(fn () => $database + ->find('targets', [ + Query::equal('userInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY) + ])); + } +); + +Database::addFilter( + 'subQueryTopicTargets', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + $targetIds = $database->getAuthorization()->skip(fn () => \array_map( + fn ($document) => $document->getAttribute('targetInternalId'), + $database->find('subscribers', [ + Query::equal('topicInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBSCRIBERS_SUBQUERY) + ]) + )); + if (\count($targetIds) > 0) { + return $database->find('targets', [ + Query::equal('$internalId', $targetIds) + ]); + } + return []; + } +); + +Database::addFilter( + 'providerSearch', + function (mixed $value, Document $provider) { + $searchValues = [ + $provider->getId(), + $provider->getAttribute('name', ''), + $provider->getAttribute('provider', ''), + $provider->getAttribute('type', '') + ]; + + $search = \implode(' ', \array_filter($searchValues)); + + return $search; + }, + function (mixed $value) { + return $value; + } +); + +Database::addFilter( + 'topicSearch', + function (mixed $value, Document $topic) { + $searchValues = [ + $topic->getId(), + $topic->getAttribute('name', ''), + $topic->getAttribute('description', ''), + ]; + + $search = \implode(' ', \array_filter($searchValues)); + + return $search; + }, + function (mixed $value) { + return $value; + } +); + +Database::addFilter( + 'messageSearch', + function (mixed $value, Document $message) { + $searchValues = [ + $message->getId(), + $message->getAttribute('description', ''), + $message->getAttribute('status', ''), + ]; + + $data = \json_decode($message->getAttribute('data', []), true); + $providerType = $message->getAttribute('providerType', ''); + + if ($providerType === MESSAGE_TYPE_EMAIL) { + $searchValues = \array_merge($searchValues, [$data['subject'], MESSAGE_TYPE_EMAIL]); + } elseif ($providerType === MESSAGE_TYPE_SMS) { + $searchValues = \array_merge($searchValues, [$data['content'], MESSAGE_TYPE_SMS]); + } else { + $searchValues = \array_merge($searchValues, [$data['title'], MESSAGE_TYPE_PUSH]); + } + + $search = \implode(' ', \array_filter($searchValues)); + + return $search; + }, + function (mixed $value) { + return $value; + } +); diff --git a/app/init/database/formats.php b/app/init/database/formats.php new file mode 100644 index 0000000000..88e46655ac --- /dev/null +++ b/app/init/database/formats.php @@ -0,0 +1,43 @@ +getScheme(); + $accessKey = $dsn->getUser() ?? ''; + $accessSecret = $dsn->getPassword() ?? ''; + $bucket = $dsn->getPath() ?? ''; + $region = $dsn->getParam('region'); + } catch (\Throwable $e) { + Console::warning($e->getMessage() . 'Invalid DSN. Defaulting to Local device.'); + } + + switch ($device) { + case Storage::DEVICE_S3: + return new S3($root, $accessKey, $accessSecret, $bucket, $region, $acl); + case STORAGE::DEVICE_DO_SPACES: + return new DOSpaces($root, $accessKey, $accessSecret, $bucket, $region, $acl); + case Storage::DEVICE_BACKBLAZE: + return new Backblaze($root, $accessKey, $accessSecret, $bucket, $region, $acl); + case Storage::DEVICE_LINODE: + return new Linode($root, $accessKey, $accessSecret, $bucket, $region, $acl); + case Storage::DEVICE_WASABI: + return new Wasabi($root, $accessKey, $accessSecret, $bucket, $region, $acl); + case Storage::DEVICE_LOCAL: + default: + return new Local($root); + } + } else { + switch (strtolower(System::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) ?? '')) { + case Storage::DEVICE_LOCAL: + default: + return new Local($root); + case Storage::DEVICE_S3: + $s3AccessKey = System::getEnv('_APP_STORAGE_S3_ACCESS_KEY', ''); + $s3SecretKey = System::getEnv('_APP_STORAGE_S3_SECRET', ''); + $s3Region = System::getEnv('_APP_STORAGE_S3_REGION', ''); + $s3Bucket = System::getEnv('_APP_STORAGE_S3_BUCKET', ''); + $s3Acl = 'private'; + return new S3($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl); + case Storage::DEVICE_DO_SPACES: + $doSpacesAccessKey = System::getEnv('_APP_STORAGE_DO_SPACES_ACCESS_KEY', ''); + $doSpacesSecretKey = System::getEnv('_APP_STORAGE_DO_SPACES_SECRET', ''); + $doSpacesRegion = System::getEnv('_APP_STORAGE_DO_SPACES_REGION', ''); + $doSpacesBucket = System::getEnv('_APP_STORAGE_DO_SPACES_BUCKET', ''); + $doSpacesAcl = 'private'; + return new DOSpaces($root, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl); + case Storage::DEVICE_BACKBLAZE: + $backblazeAccessKey = System::getEnv('_APP_STORAGE_BACKBLAZE_ACCESS_KEY', ''); + $backblazeSecretKey = System::getEnv('_APP_STORAGE_BACKBLAZE_SECRET', ''); + $backblazeRegion = System::getEnv('_APP_STORAGE_BACKBLAZE_REGION', ''); + $backblazeBucket = System::getEnv('_APP_STORAGE_BACKBLAZE_BUCKET', ''); + $backblazeAcl = 'private'; + return new Backblaze($root, $backblazeAccessKey, $backblazeSecretKey, $backblazeBucket, $backblazeRegion, $backblazeAcl); + case Storage::DEVICE_LINODE: + $linodeAccessKey = System::getEnv('_APP_STORAGE_LINODE_ACCESS_KEY', ''); + $linodeSecretKey = System::getEnv('_APP_STORAGE_LINODE_SECRET', ''); + $linodeRegion = System::getEnv('_APP_STORAGE_LINODE_REGION', ''); + $linodeBucket = System::getEnv('_APP_STORAGE_LINODE_BUCKET', ''); + $linodeAcl = 'private'; + return new Linode($root, $linodeAccessKey, $linodeSecretKey, $linodeBucket, $linodeRegion, $linodeAcl); + case Storage::DEVICE_WASABI: + $wasabiAccessKey = System::getEnv('_APP_STORAGE_WASABI_ACCESS_KEY', ''); + $wasabiSecretKey = System::getEnv('_APP_STORAGE_WASABI_SECRET', ''); + $wasabiRegion = System::getEnv('_APP_STORAGE_WASABI_REGION', ''); + $wasabiBucket = System::getEnv('_APP_STORAGE_WASABI_BUCKET', ''); + $wasabiAcl = 'private'; + return new Wasabi($root, $wasabiAccessKey, $wasabiSecretKey, $wasabiBucket, $wasabiRegion, $wasabiAcl); + } + } +} + +$log = new Dependency(); +$mode = new Dependency(); +$user = new Dependency(); +$plan = new Dependency(); +$pools = new Dependency(); +$geodb = new Dependency(); +$cache = new Dependency(); +$pools = new Dependency(); +$queue = new Dependency(); +$hooks = new Dependency(); +$logger = new Dependency(); +$locale = new Dependency(); +$schema = new Dependency(); +$github = new Dependency(); +$session = new Dependency(); +$console = new Dependency(); +$project = new Dependency(); +$clients = new Dependency(); +$servers = new Dependency(); +$register = new Dependency(); +$connections = new Dependency(); +$localeCodes = new Dependency(); +$getConsoleDB = new Dependency(); +$getProjectDB = new Dependency(); +$dbForProject = new Dependency(); +$dbForConsole = new Dependency(); +$queueForUsage = new Dependency(); +$queueForMails = new Dependency(); +$authorization = new Dependency(); +$authentication = new Dependency(); +$queueForBuilds = new Dependency(); +$deviceForLocal = new Dependency(); +$deviceForFiles = new Dependency(); +$queueForEvents = new Dependency(); +$queueForAudits = new Dependency(); +$promiseAdapter = new Dependency(); +$schemaVariable = new Dependency(); +$deviceForBuilds = new Dependency(); +$queueForDeletes = new Dependency(); +$requestTimestamp = new Dependency(); +$queueForDatabase = new Dependency(); +$queueForMessaging = new Dependency(); +$queueForFunctions = new Dependency(); +$queueForMigrations = new Dependency(); +$deviceForFunctions = new Dependency(); +$passwordsDictionary = new Dependency(); +$queueForCertificates = new Dependency(); + + +$plan + ->setName('plan') + ->setCallback(fn () => []); + +$mode + ->setName('mode') + ->inject('request') + ->setCallback(function (Request $request) { + /** + * Defines the mode for the request: + * - 'default' => Requests for Client and Server Side + * - 'admin' => Request from the Console on non-console projects + */ + return $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT)); + }); + +$user + ->setName('user') + ->inject('mode') + ->inject('project') + ->inject('console') + ->inject('request') + ->inject('response') + ->inject('dbForProject') + ->inject('dbForConsole') + ->inject('authorization') + ->inject('authentication') + ->setCallback(function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForConsole, Authorization $authorization, Authentication $authentication) { + $authorization->setDefaultStatus(true); + $authentication->setCookieName('a_session_' . $project->getId()); + + if (APP_MODE_ADMIN === $mode) { + $authentication->setCookieName('a_session_' . $console->getId()); + } + + $session = Auth::decodeSession( + $request->getCookie( + $authentication->getCookieName(), // Get sessions + $request->getCookie($authentication->getCookieName() . '_legacy', '') + ) + ); + + // Get session from header for SSR clients + if (empty($session['id']) && empty($session['secret'])) { + $sessionHeader = $request->getHeader('x-appwrite-session', ''); + + if (!empty($sessionHeader)) { + $session = Auth::decodeSession($sessionHeader); + } + } + + // Get fallback session from old clients (no SameSite support) or clients who block 3rd-party cookies + if ($response) { + $response->addHeader('X-Debug-Fallback', 'false'); + } + + if (empty($session['id']) && empty($session['secret'])) { + if ($response) { + $response->addHeader('X-Debug-Fallback', 'true'); + } + $fallback = $request->getHeader('x-fallback-cookies', ''); + $fallback = \json_decode($fallback, true); + $session = Auth::decodeSession(((isset($fallback[$authentication->getCookieName()])) ? $fallback[$authentication->getCookieName()] : '')); + } + + $authentication->setUnique($session['id'] ?? ''); + $authentication->setSecret($session['secret'] ?? ''); + + if (APP_MODE_ADMIN !== $mode) { + if ($project->isEmpty()) { + $user = new Document([]); + } else { + if ($project->getId() === 'console') { + $user = $dbForConsole->getDocument('users', $authentication->getUnique()); + } else { + $user = $dbForProject->getDocument('users', $authentication->getUnique()); + } + } + } else { + $user = $dbForConsole->getDocument('users', $authentication->getUnique()); + } + + if ( + $user->isEmpty() // Check a document has been found in the DB + || !Auth::sessionVerify($user->getAttribute('sessions', []), $authentication->getSecret()) + ) { // Validate user has valid login token + $user = new Document([]); + } + + if (APP_MODE_ADMIN === $mode) { + if ($user->find('teamId', $project->getAttribute('teamId'), 'memberships')) { + $authorization->setDefaultStatus(false); // Cancel security segmentation for admin users. + } else { + $user = new Document([]); + } + } + + $authJWT = $request->getHeader('x-appwrite-jwt', ''); + + if (!empty($authJWT) && !$project->isEmpty()) { // JWT authentication + $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0); + try { + $payload = $jwt->decode($authJWT); + } catch (JWTException $error) { + $request->removeHeader('x-appwrite-jwt'); + throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage()); + } + + $jwtUserId = $payload['userId'] ?? ''; + if (!empty($jwtUserId)) { + $user = $dbForProject->getDocument('users', $jwtUserId); + } + + $jwtSessionId = $payload['sessionId'] ?? ''; + if (!empty($jwtSessionId)) { + if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token + $user = new Document([]); + } + } + } + + // Adds logs to database queries + $dbForProject->setMetadata('user', $user->getId()); + $dbForConsole->setMetadata('user', $user->getId()); + + return $user; + }); + + +$session + ->setName('session') + ->inject('user') + ->inject('project') + ->inject('authorization') + ->inject('authentication') + ->setCallback(function (Document $user, Document $project, Authorization $authorization, Authentication $authentication) { + if ($user->isEmpty()) { + return; + } + + $sessions = $user->getAttribute('sessions', []); + $authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; + $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), $authentication->getSecret(), $authDuration); + + if (!$sessionId) { + return; + } + + foreach ($sessions as $session) { + if ($sessionId === $session->getId()) { + return $session; + } + } + + return; + }); + +$console + ->setName('console') + ->setCallback(function () { + return new Document([ + '$id' => ID::custom('console'), + '$internalId' => ID::custom('console'), + 'name' => 'Appwrite', + '$collection' => ID::custom('projects'), + 'description' => 'Appwrite core engine', + 'logo' => '', + 'teamId' => -1, + 'webhooks' => [], + 'keys' => [], + 'platforms' => [ + [ + '$collection' => ID::custom('platforms'), + 'name' => 'Localhost', + 'type' => Origin::CLIENT_TYPE_WEB, + 'hostname' => 'localhost', + ], // Current host is added on app init + ], + 'legalName' => '', + 'legalCountry' => '', + 'legalState' => '', + 'legalCity' => '', + 'legalAddress' => '', + 'legalTaxId' => '', + 'auths' => [ + 'mockNumbers' => [], + 'invites' => System::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled', + 'limit' => (System::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user + 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds + 'sessionAlerts' => System::getEnv('_APP_CONSOLE_SESSION_ALERTS', 'disabled') === 'enabled' + ], + 'authWhitelistEmails' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [], + 'authWhitelistIPs' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [], + 'oAuthProviders' => [ + 'githubEnabled' => true, + 'githubSecret' => System::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''), + 'githubAppid' => System::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '') + ], + ]); + }); + + +$project + ->setName('project') + ->inject('dbForConsole') + ->inject('request') + ->inject('console') + ->inject('authorization') + ->setCallback(function (Database $dbForConsole, Request $request, Document $console, Authorization $authorization) { + $projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', '')); + + if (empty($projectId) || $projectId === 'console') { + return $console; + } + + $project = $authorization->skip(fn () => $dbForConsole->getDocument('projects', $projectId)); + + return $project; + }); + +$pools + ->setName('pools') + ->inject('registry') + ->setCallback(function (Registry $registry) { + return $registry->get('pools'); + }); + +$dbForProject + ->setName('dbForProject') + ->inject('cache') + ->inject('pools') + ->inject('project') + ->inject('dbForConsole') + ->inject('authorization') + ->inject('connections') + ->setCallback(function (Cache $cache, array $pools, Document $project, Database $dbForConsole, Authorization $authorization, Connections $connections) { + if ($project->isEmpty() || $project->getId() === 'console') { + return $dbForConsole; + } + + 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')); + } + + $pool = $pools['pools-database-' . $dsn->getHost()]['pool']; + $connectionDsn = $pools['pools-database-' . $dsn->getHost()]['dsn']; + + $connection = $pool->get(); + $connections->add($connection, $pool); + $adapter = match ($connectionDsn->getScheme()) { + 'mariadb' => new MariaDB($connection), + 'mysql' => new MySQL($connection), + default => null + }; + + $adapter->setDatabase($connectionDsn->getPath()); + + $database = new Database($adapter, $cache); + + 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')); + } + + if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + $database + ->setSharedTables(true) + ->setTenant($project->getInternalId()) + ->setNamespace($dsn->getParam('namespace')); + } else { + $database + ->setSharedTables(false) + ->setTenant(null) + ->setNamespace('_' . $project->getInternalId()); + } + + $database->setAuthorization($authorization); + return $database; + }); + +$dbForConsole + ->setName('dbForConsole') + ->inject('getConsoleDB') + ->inject('connections') + ->setCallback(function (callable $getConsoleDB, Connections $connections): Database { + [$connection,$pool, $database] = $getConsoleDB(); + $connections->add($connection, $pool); + + return $database; + }); + +$cache + ->setName('cache') + ->inject('pools') + ->inject('connections') + ->setCallback(function (array $pools, Connections $connections) { + $adapters = []; + $databases = Config::getParam('pools-cache'); + + foreach ($databases as $database) { + $pool = $pools['pools-cache-' . $database]['pool']; + $dsn = $pools['pools-cache-' . $database]['dsn']; + + $connection = $pool->get(); + $connections->add($connection, $pool); + + $adapters[] = new CacheRedis($connection); + } + + return new Cache(new Sharding($adapters)); + }); + +$authorization + ->setName('authorization') + ->setCallback(function (): Authorization { + return new Authorization(); + }); + +$authentication + ->setName('authentication') + ->setCallback(function (): Authentication { + return new Authentication(); + }); + +$register + ->setName('registry') + ->setCallback(function () use (&$registry): Registry { + return $registry; + }); + +$pools + ->setName('pools') + ->inject('registry') + ->setCallback(function (Registry $registry) { + return $registry->get('pools'); + }); + +$logger + ->setName('logger') + ->inject('registry') + ->setCallback(function (Registry $registry) { + return $registry->get('logger'); + }); + +$log + ->setName('log') + ->setCallback(function () { + return new Log(); + }); + +$connections + ->setName('connections') + ->setCallback(function () { + return new Connections(); + }); + +$locale + ->setName('locale') + ->setCallback(fn () => new Locale(System::getEnv('_APP_LOCALE', 'en'))); + +$localeCodes + ->setName('localeCodes') + ->setCallback(fn () => array_map(fn ($locale) => $locale['code'], Config::getParam('locale-codes', []))); + +$queue + ->setName('queue') + ->inject('pools') + ->inject('connections') + ->setCallback(function (array $pools, Connections $connections) { + $pool = $pools['pools-queue-queue']['pool']; + $connection = $pool->get(); + $connections->add($connection, $pool); + + return new Queue\Connection\Redis($connection); + }); + +$queueForMessaging + ->setName('queueForMessaging') + ->inject('queue') + ->setCallback(function (Connection $queue) { + return new Messaging($queue); + }); + +$queueForMails + ->setName('queueForMails') + ->inject('queue') + ->setCallback(function (Connection $queue) { + return new Mail($queue); + }); + +$queueForBuilds + ->setName('queueForBuilds') + ->inject('queue') + ->setCallback(function (Connection $queue) { + return new Build($queue); + }); + +$queueForDatabase + ->setName('queueForDatabase') + ->inject('queue') + ->setCallback(function (Connection $queue) { + return new EventDatabase($queue); + }); + +$queueForDeletes + ->setName('queueForDeletes') + ->inject('queue') + ->setCallback(function (Connection $queue) { + return new Delete($queue); + }); + +$queueForEvents + ->setName('queueForEvents') + ->inject('queue') + ->setCallback(function (Connection $queue) { + return new Event($queue); + }); + +$queueForAudits + ->setName('queueForAudits') + ->inject('queue') + ->setCallback(function (Connection $queue) { + return new Audit($queue); + }); + +$queueForFunctions + ->setName('queueForFunctions') + ->inject('queue') + ->setCallback(function (Connection $queue) { + return new Func($queue); + }); + +$queueForUsage + ->setName('queueForUsage') + ->inject('queue') + ->setCallback(function (Connection $queue) { + return new Usage($queue); + }); + +$queueForCertificates + ->setName('queueForCertificates') + ->inject('queue') + ->setCallback(function (Connection $queue) { + return new Certificate($queue); + }); + +$queueForMigrations + ->setName('queueForMigrations') + ->inject('queue') + ->setCallback(function (Connection $queue) { + return new Migration($queue); + }); + +$deviceForLocal + ->setName('deviceForLocal') + ->setCallback(function () { + return new Local(); + }); + +$deviceForFiles + ->setName('deviceForFiles') + ->inject('project') + ->setCallback(function ($project) { + return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId()); + }); + +$deviceForFunctions + ->setName('deviceForFunctions') + ->inject('project') + ->setCallback(function ($project) { + return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId()); + }); + +$deviceForBuilds + ->setName('deviceForBuilds') + ->inject('project') + ->setCallback(function ($project) { + return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId()); + }); + +$clients + ->setName('clients') + ->inject('request') + ->inject('console') + ->inject('project') + ->setCallback(function (Request $request, Document $console, Document $project) { + $console->setAttribute('platforms', [ // Always allow current host + '$collection' => ID::custom('platforms'), + 'name' => 'Current Host', + 'type' => Origin::CLIENT_TYPE_WEB, + 'hostname' => $request->getHostname(), + ], Document::SET_TYPE_APPEND); + + $hostnames = explode(',', System::getEnv('_APP_CONSOLE_HOSTNAMES', '')); + $validator = new Hostname(); + foreach ($hostnames as $hostname) { + $hostname = trim($hostname); + if (!$validator->isValid($hostname)) { + continue; + } + $console->setAttribute('platforms', [ + '$collection' => ID::custom('platforms'), + 'type' => Origin::CLIENT_TYPE_WEB, + 'name' => $hostname, + 'hostname' => $hostname, + ], Document::SET_TYPE_APPEND); + } + + /** + * Get All verified client URLs for both console and current projects + * + Filter for duplicated entries + */ + $clientsConsole = \array_map( + fn ($node) => $node['hostname'], + \array_filter( + $console->getAttribute('platforms', []), + fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB) && isset($node['hostname']) && !empty($node['hostname'])) + ) + ); + + $clients = \array_unique( + \array_merge( + $clientsConsole, + \array_map( + fn ($node) => $node['hostname'], + \array_filter( + $project->getAttribute('platforms', []), + fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB || $node['type'] === Origin::CLIENT_TYPE_FLUTTER_WEB) && isset($node['hostname']) && !empty($node['hostname'])) + ) + ) + ) + ); + + return $clients; + }); + +$servers + ->setName('servers') + ->setCallback(function () { + $platforms = Config::getParam('platforms'); + $server = $platforms[APP_PLATFORM_SERVER]; + + $languages = array_map(function ($language) { + return strtolower($language['name']); + }, $server['sdks']); + + return $languages; + }); + +$geodb + ->setName('geodb') + ->inject('registry') + ->setCallback(function (Registry $register) { + return $register->get('geodb'); + }); + +$passwordsDictionary + ->setName('passwordsDictionary') + ->setCallback(function () { + $content = file_get_contents(__DIR__ . '/../assets/security/10k-common-passwords'); + $content = explode("\n", $content); + $content = array_flip($content); + return $content; + }); + +$hooks + ->setName('hooks') + ->inject('registry') + ->setCallback(function (Registry $registry) { + return $registry->get('hooks'); + }); + +$github + ->setName('gitHub') + ->inject('cache') + ->setCallback(function (Cache $cache) { + return new GitHub($cache); + }); + +$requestTimestamp + ->setName('requestTimestamp') + ->inject('request') + ->setCallback(function ($request) { + $timestampHeader = $request->getHeader('x-appwrite-timestamp'); + $requestTimestamp = null; + if (!empty($timestampHeader)) { + try { + $requestTimestamp = new \DateTime($timestampHeader); + } catch (\Throwable $e) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid X-Appwrite-Timestamp header value'); + } + } + return $requestTimestamp; + }); +$getConsoleDB + ->setName('getConsoleDB') + ->inject('pools') + ->inject('cache') + ->inject('authorization') + ->inject('connections') + ->setCallback(function (array $pools, Cache $cache, Authorization $authorization) { + return function () use ($pools, $cache, $authorization): array { + $pool = $pools['pools-console-console']['pool']; + $dsn = $pools['pools-console-console']['dsn']; + $connection = $pool->get(); + + $adapter = match ($dsn->getScheme()) { + 'mariadb' => new MariaDB($connection), + 'mysql' => new MySQL($connection), + default => null + }; + + $adapter->setDatabase($dsn->getPath()); + + $database = new Database($adapter, $cache); + $database->setAuthorization($authorization); + + $database + ->setNamespace('_console') + ->setMetadata('host', \gethostname()) + ->setMetadata('project', 'console') + ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); + + return [$connection, $pool, $database]; + }; + }); + +$getProjectDB + ->setName('getProjectDB') + ->inject('pools') + ->inject('dbForConsole') + ->inject('cache') + ->inject('authorization') + ->inject('connections') + ->setCallback(function (array $pools, Database $dbForConsole, Cache $cache, Authorization $authorization, Connections $connections) { + $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools + + return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases, $authorization, $connections): Database { + if ($project->isEmpty() || $project->getId() === 'console') { + return $dbForConsole; + } + + 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')); + } + + if (isset($databases[$dsn->getHost()])) { + $database = $databases[$dsn->getHost()]; + + if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + $database + ->setSharedTables(true) + ->setTenant($project->getInternalId()) + ->setNamespace($dsn->getParam('namespace')); + } else { + $database + ->setSharedTables(false) + ->setTenant(null) + ->setNamespace('_' . $project->getInternalId()); + } + + return $database; + } + + $pool = $pools['pools-database-' . $dsn->getHost()]['pool']; + $connectionDsn = $pools['pools-database-' . $dsn->getHost()]['dsn']; + + $connection = $pool->get(); + $connections->add($connection, $pool); + $adapter = match ($connectionDsn->getScheme()) { + 'mariadb' => new MariaDB($connection), + 'mysql' => new MySQL($connection), + default => null + }; + $adapter->setDatabase($connectionDsn->getPath()); + + $database = new Database($adapter, $cache); + $database->setAuthorization($authorization); + + $databases[$dsn->getHost()] = $database; + + if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + $database + ->setSharedTables(true) + ->setTenant($project->getInternalId()) + ->setNamespace($dsn->getParam('namespace')); + } else { + $database + ->setSharedTables(false) + ->setTenant(null) + ->setNamespace('_' . $project->getInternalId()); + } + + return $database; + }; + }); + +$promiseAdapter + ->setName('promiseAdapter') + ->setCallback(function () use ($registry) { + return $registry->get('promiseAdapter'); + }); + +$schemaVariable + ->setName('schemaVariable') + ->setCallback(fn () => new Schema()); + +$schema + ->setName('schema') + ->inject('http') + ->inject('context') + ->inject('request') + ->inject('response') + ->inject('dbForProject') + ->inject('authorization') + ->inject('schemaVariable') + ->setCallback(function (Http $http, Container $context, Request $request, Response $response, Database $dbForProject, Authorization $authorization, $schemaVariable) { + $complexity = function (int $complexity, array $args) { + $queries = Query::parseQueries($args['queries'] ?? []); + $query = Query::getByType($queries, [Query::TYPE_LIMIT])[0] ?? null; + $limit = $query ? $query->getValue() : APP_LIMIT_LIST_DEFAULT; + + return $complexity * $limit; + }; + + $attributes = function (int $limit, int $offset) use ($dbForProject, $authorization) { + $attrs = $authorization->skip(fn () => $dbForProject->find('attributes', [ + Query::limit($limit), + Query::offset($offset), + ])); + + return \array_map(function ($attr) { + return $attr->getArrayCopy(); + }, $attrs); + }; + + $urls = [ + 'list' => function (string $databaseId, string $collectionId, array $args) { + return "/v1/databases/$databaseId/collections/$collectionId/documents"; + }, + 'create' => function (string $databaseId, string $collectionId, array $args) { + return "/v1/databases/$databaseId/collections/$collectionId/documents"; + }, + 'read' => function (string $databaseId, string $collectionId, array $args) { + return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}"; + }, + 'update' => function (string $databaseId, string $collectionId, array $args) { + return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}"; + }, + 'delete' => function (string $databaseId, string $collectionId, array $args) { + return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}"; + }, + ]; + + $params = [ + 'list' => function (string $databaseId, string $collectionId, array $args) { + return ['queries' => $args['queries']]; + }, + 'create' => function (string $databaseId, string $collectionId, array $args) { + $id = $args['id'] ?? 'unique()'; + $permissions = $args['permissions'] ?? null; + + unset($args['id']); + unset($args['permissions']); + + return [ + 'databaseId' => $databaseId, + 'documentId' => $id, + 'collectionId' => $collectionId, + 'data' => $args, + 'permissions' => $permissions, + ]; + }, + 'update' => function (string $databaseId, string $collectionId, array $args) { + $documentId = $args['id']; + $permissions = $args['permissions'] ?? null; + + unset($args['id']); + unset($args['permissions']); + + return [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'documentId' => $documentId, + 'data' => $args, + 'permissions' => $permissions, + ]; + }, + ]; + + return $schemaVariable->build( + $http, + $request, + $response, + $context, + $complexity, + $attributes, + $urls, + $params, + ); + }); + +$container->set($log); +$container->set($mode); +$container->set($user); +$container->set($plan); +$container->set($cache); +$container->set($pools); +$container->set($queue); +$container->set($geodb); +$container->set($hooks); +$container->set($locale); +$container->set($schema); +$container->set($github); +$container->set($logger); +$container->set($session); +$container->set($console); +$container->set($project); +$container->set($clients); +$container->set($servers); +$container->set($register); +$container->set($connections); +$container->set($localeCodes); +$container->set($dbForProject); +$container->set($dbForConsole); +$container->set($getConsoleDB); +$container->set($getProjectDB); +$container->set($queueForUsage); +$container->set($queueForMails); +$container->set($authorization); +$container->set($authentication); +$container->set($schemaVariable); +$container->set($queueForBuilds); +$container->set($queueForEvents); +$container->set($queueForAudits); +$container->set($deviceForLocal); +$container->set($deviceForFiles); +$container->set($promiseAdapter); +$container->set($queueForDeletes); +$container->set($deviceForBuilds); +$container->set($queueForDatabase); +$container->set($requestTimestamp); +$container->set($queueForMessaging); +$container->set($queueForFunctions); +$container->set($queueForMigrations); +$container->set($deviceForFunctions); +$container->set($passwordsDictionary); +$container->set($queueForCertificates); diff --git a/app/realtime.php b/app/realtime.php index 39e00f7dda..ee6dd4fa81 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -5,136 +5,47 @@ use Appwrite\Extend\Exception; use Appwrite\Extend\Exception as AppwriteException; use Appwrite\Messaging\Adapter\Realtime; use Appwrite\Network\Validator\Origin; +use Appwrite\Utopia\Queue\Connections; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use Swoole\Http\Request as SwooleRequest; +use Swoole\Http\Response as SwooleHttpResponse; use Swoole\Http\Response as SwooleResponse; use Swoole\Runtime; use Swoole\Table; use Swoole\Timer; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapters\Database\TimeLimit; -use Utopia\App; -use Utopia\Cache\Adapter\Sharding; -use Utopia\Cache\Cache; use Utopia\CLI\Console; -use Utopia\Config\Config; -use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; -use Utopia\Database\Validator\Authorization; -use Utopia\DSN\DSN; +use Utopia\DI\Container; +use Utopia\DI\Dependency; +use Utopia\Http\Adapter\Swoole\Request as UtopiaRequest; +use Utopia\Http\Adapter\Swoole\Response as HttpResponse; +use Utopia\Http\Adapter\Swoole\Response as UtopiaResponse; +use Utopia\Http\Http; use Utopia\Logger\Log; +use Utopia\Pools\Connection; +use Utopia\Registry\Registry; use Utopia\System\System; use Utopia\WebSocket\Adapter; use Utopia\WebSocket\Server; /** - * @var \Utopia\Registry\Registry $register + * @var Registry $registry + * @var Container $container */ +global $registry, $container; + + require_once __DIR__ . '/init.php'; Runtime::enableCoroutine(SWOOLE_HOOK_ALL); -// Allows overriding -if (!function_exists("getConsoleDB")) { - function getConsoleDB(): Database - { - global $register; - - /** @var \Utopia\Pools\Group $pools */ - $pools = $register->get('pools'); - - $dbAdapter = $pools - ->get('console') - ->pop() - ->getResource() - ; - - $database = new Database($dbAdapter, getCache()); - - $database - ->setNamespace('_console') - ->setMetadata('host', \gethostname()) - ->setMetadata('project', '_console'); - - return $database; - } -} - -// Allows overriding -if (!function_exists("getProjectDB")) { - function getProjectDB(Document $project): Database - { - global $register; - - /** @var \Utopia\Pools\Group $pools */ - $pools = $register->get('pools'); - - if ($project->isEmpty() || $project->getId() === 'console') { - return getConsoleDB(); - } - - 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')); - } - - $adapter = $pools - ->get($dsn->getHost()) - ->pop() - ->getResource(); - - $database = new Database($adapter, getCache()); - - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { - $database - ->setSharedTables(true) - ->setTenant($project->getInternalId()) - ->setNamespace($dsn->getParam('namespace')); - } else { - $database - ->setSharedTables(false) - ->setTenant(null) - ->setNamespace('_' . $project->getInternalId()); - } - - $database - ->setMetadata('host', \gethostname()) - ->setMetadata('project', $project->getId()); - - return $database; - } -} - -// Allows overriding -if (!function_exists("getCache")) { - function getCache(): Cache - { - global $register; - - $pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */ - - $list = Config::getParam('pools-cache', []); - $adapters = []; - - foreach ($list as $value) { - $adapters[] = $pools - ->get($value) - ->pop() - ->getResource() - ; - } - - return new Cache(new Sharding($adapters)); - } -} - $realtime = new Realtime(); /** @@ -159,8 +70,8 @@ $adapter $server = new Server($adapter); -$logError = function (Throwable $error, string $action) use ($register) { - $logger = $register->get('logger'); +$logError = function (Throwable $error, string $action) use ($registry) { + $logger = $registry->get('logger'); if ($logger && !$error instanceof Exception) { $version = System::getEnv('_APP_VERSION', 'UNKNOWN'); @@ -200,16 +111,16 @@ $logError = function (Throwable $error, string $action) use ($register) { $server->error($logError); -$server->onStart(function () use ($stats, $register, $containerId, &$statsDocument, $logError) { +$server->onStart(function () use ($stats, $container, $containerId, &$statsDocument, $logError) { sleep(5); // wait for the initial database schema to be ready Console::success('Server started successfully'); - + $authorization = $container->get('authorization'); /** * Create document for this worker to share stats across Containers. */ - go(function () use ($register, $containerId, &$statsDocument) { + go(function () use ($container, $containerId, &$statsDocument) { $attempts = 0; - $database = getConsoleDB(); + $database = $container->get('dbForConsole'); do { try { @@ -223,14 +134,15 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume 'value' => '{}' ]); - $statsDocument = Authorization::skip(fn () => $database->createDocument('realtime', $document)); + $authorization = $container->get('authorization'); + $statsDocument = $authorization->skip(fn () => $database->createDocument('realtime', $document)); break; } catch (Throwable) { Console::warning("Collection not ready. Retrying connection ({$attempts})..."); sleep(DATABASE_RECONNECT_SLEEP); } } while (true); - $register->get('pools')->reclaim(); + ($container->get('connections'))->reclaim(); }); /** @@ -238,7 +150,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume */ // TODO: Remove this if check once it doesn't cause issues for cloud if (System::getEnv('_APP_EDITION', 'self-hosted') === 'self-hosted') { - Timer::tick(5000, function () use ($register, $stats, &$statsDocument, $logError) { + Timer::tick(5000, function () use ($container, $stats, &$statsDocument, $logError, $authorization) { $payload = []; foreach ($stats as $projectId => $value) { $payload[$projectId] = $stats->get($projectId, 'connectionsTotal'); @@ -248,40 +160,43 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume } try { - $database = getConsoleDB(); + $database = $container->get('dbForConsole'); $statsDocument ->setAttribute('timestamp', DateTime::now()) ->setAttribute('value', json_encode($payload)); - Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument)); + $authorization->skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument)); } catch (Throwable $th) { call_user_func($logError, $th, "updateWorkerDocument"); } finally { - $register->get('pools')->reclaim(); + ($container->get('connections'))->reclaim(); + $container->refresh('dbForConsole'); } }); } }); -$server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $realtime, $logError) { +$server->onWorkerStart(function (int $workerId) use ($server, $container, $stats, $realtime, $logError) { Console::success('Worker ' . $workerId . ' started successfully'); $attempts = 0; $start = time(); - Timer::tick(5000, function () use ($server, $register, $realtime, $stats, $logError) { + $authorization = $container->get('authorization'); + + Timer::tick(5000, function () use ($server, $container, $realtime, $stats, $logError, $authorization) { /** * Sending current connections to project channels on the console project every 5 seconds. */ // TODO: Remove this if check once it doesn't cause issues for cloud if (System::getEnv('_APP_EDITION', 'self-hosted') === 'self-hosted') { if ($realtime->hasSubscriber('console', Role::users()->toString(), 'project')) { - $database = getConsoleDB(); + $database = $container->get('dbForConsole'); $payload = []; - $list = Authorization::skip(fn () => $database->find('realtime', [ + $list = $authorization->skip(fn () => $database->find('realtime', [ Query::greaterThan('timestamp', DateTime::addSeconds(new \DateTime(), -15)), ])); @@ -321,8 +236,8 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, 'data' => $event['data'] ])); } - - $register->get('pools')->reclaim(); + ($container->get('connections'))->reclaim(); + $container->refresh('dbForConsole'); } } /** @@ -352,13 +267,26 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, while ($attempts < 300) { try { if ($attempts > 0) { - Console::error('Pub/sub connection lost (lasted ' . (time() - $start) . ' seconds, worker: ' . $workerId . '). - Attempting restart in 5 seconds (attempt #' . $attempts . ')'); + Console::error( + 'Pub/sub connection lost (lasted ' . (time() - $start) . ' seconds, worker: ' . $workerId . '). + Attempting restart in 5 seconds (attempt #' . $attempts . ')' + ); sleep(5); // 5 sec delay between connection attempts } + $start = time(); - $redis = $register->get('pools')->get('pubsub')->pop()->getResource(); /** @var Redis $redis */ + $pools = $container->get('pools'); + $pool = $pools['pools-pubsub-pubsub']['pool']; + + /** @var Connections $connections */ + $connections = $container->get('connections'); + $connection = $pool->get(); + $connections->add($connection, $pool); + + $redis = $connection; + + /** @var Redis $redis */ $redis->setOption(Redis::OPT_READ_TIMEOUT, -1); if ($redis->ping(true)) { @@ -368,7 +296,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, Console::error('Pub/sub failed (worker: ' . $workerId . ')'); } - $redis->subscribe(['realtime'], function (Redis $redis, string $channel, string $payload) use ($server, $workerId, $stats, $register, $realtime) { + $redis->subscribe(['realtime'], function (Redis $redis, string $channel, string $payload) use ($server, $workerId, $stats, $realtime, $authorization, $container) { $event = json_decode($payload, true); if ($event['permissionsChanged'] && isset($event['userId'])) { @@ -377,25 +305,24 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, if ($realtime->hasSubscriber($projectId, 'user:' . $userId)) { $connection = array_key_first(reset($realtime->subscriptions[$projectId]['user:' . $userId])); - $consoleDatabase = getConsoleDB(); - $project = Authorization::skip(fn () => $consoleDatabase->getDocument('projects', $projectId)); - $database = getProjectDB($project); + $dbForConsole = $container->get('dbForConsole'); - $user = $database->getDocument('users', $userId); + $project = $authorization->skip(fn () => $dbForConsole->getDocument('projects', $projectId)); + $dbForProject = $container->get('getProjectDB')($project); - $roles = Auth::getRoles($user); + $user = $dbForProject->getDocument('users', $userId); + + $roles = Auth::getRoles($user, $authorization); $channels = $realtime->connections[$connection]['channels']; $realtime->unsubscribe($connection); $realtime->subscribe($projectId, $connection, $roles, $channels); - - $register->get('pools')->reclaim(); } } $receivers = $realtime->getSubscribers($event); - if (App::isDevelopment() && !empty($receivers)) { + if (Http::isDevelopment() && !empty($receivers)) { Console::log("[Debug][Worker {$workerId}] Receivers: " . count($receivers)); Console::log("[Debug][Worker {$workerId}] Receivers Connection IDs: " . json_encode($receivers)); Console::log("[Debug][Worker {$workerId}] Event: " . $payload); @@ -421,30 +348,40 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, sleep(DATABASE_RECONNECT_SLEEP); continue; } finally { - $register->get('pools')->reclaim(); + ($container->get('connections'))->reclaim(); + $container->refresh('dbForConsole'); } } Console::error('Failed to restart pub/sub...'); }); -$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $register, $stats, &$realtime, $logError) { - $app = new App('UTC'); - $request = new Request($request); - $response = new Response(new SwooleResponse()); +$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $container, $stats, &$realtime, $logError) { + $authorization = $container->get('authorization'); + + $request = new Request(new UtopiaRequest($request)); + $response = new Response(new UtopiaResponse(new SwooleResponse())); + + $requestInjection = new Dependency(); + $responseInjection = new Dependency(); + + $requestInjection->setName('request')->setCallback(fn () => $request); + $responseInjection->setName('response')->setCallback(fn () => $response); + + $container->set($requestInjection); + $container->set($responseInjection); Console::info("Connection open (user: {$connection})"); - App::setResource('pools', fn () => $register->get('pools')); - App::setResource('request', fn () => $request); - App::setResource('response', fn () => $response); - try { + /** @var Document $project */ - $project = $app->getResource('project'); + $project = $container->refresh('project')->get('project'); + + $container->refresh('dbForProject'); /* - * Project Check + * Project Check */ if (empty($project->getId())) { throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing or unknown project ID'); @@ -453,15 +390,16 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, if ( array_key_exists('realtime', $project->getAttribute('apis', [])) && !$project->getAttribute('apis', [])['realtime'] - && !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles())) + && !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles())) ) { throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED); } - $dbForProject = getProjectDB($project); - $console = $app->getResource('console'); /** @var Document $console */ - $user = $app->getResource('user'); /** @var Document $user */ - + $dbForProject = $container->get('getProjectDB')($project); + /** @var Document $console */ + $console = $container->get('console'); + /** @var Document $user */ + $user = $container->refresh('user')->get('user'); /* * Abuse Check * @@ -490,7 +428,8 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription()); } - $roles = Auth::getRoles($user); + $authorization = $container->get('authorization'); + $roles = Auth::getRoles($user, $authorization); $channels = Realtime::convertChannels($request->getQuery('channels', []), $user->getId()); @@ -539,25 +478,33 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $server->send([$connection], json_encode($response)); $server->close($connection, $code); - if (App::isDevelopment()) { + if (Http::isDevelopment()) { Console::error('[Error] Connection Error'); Console::error('[Error] Code: ' . $response['data']['code']); Console::error('[Error] Message: ' . $response['data']['message']); } } finally { - $register->get('pools')->reclaim(); + $connections = $container->get('connections'); + $connections->reclaim(); } }); -$server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) { +$server->onWorkerStop(function (int $workerId) use ($container) { + $connections = $container->get('connections'); + $connections->reclaim(); +}); + +$server->onMessage(function (int $connection, string $message) use ($server, $container, $realtime, $containerId) { try { - $response = new Response(new SwooleResponse()); + $response = new Response(new HttpResponse(new SwooleHttpResponse())); $projectId = $realtime->connections[$connection]['projectId']; - $database = getConsoleDB(); + $database = $container->get('dbForConsole'); + $authorization = $container->get('authorization'); + $authentication = $container->get('authentication'); if ($projectId !== 'console') { - $project = Authorization::skip(fn () => $database->getDocument('projects', $projectId)); - $database = getProjectDB($project); + $project = $authorization->skip(fn () => $database->getDocument('projects', $projectId)); + $database = $container->get('getProjectDB')($project); } else { $project = null; } @@ -595,20 +542,21 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re } $session = Auth::decodeSession($message['data']['session']); - Auth::$unique = $session['id'] ?? ''; - Auth::$secret = $session['secret'] ?? ''; - $user = $database->getDocument('users', Auth::$unique); + $authentication->setUnique($session['id'] ?? ''); + $authentication->setSecret($session['secret'] ?? ''); + + $user = $database->getDocument('users', $authentication->getUnique()); if ( empty($user->getId()) // Check a document has been found in the DB - || !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) // Validate user has valid login token + || !Auth::sessionVerify($user->getAttribute('sessions', []), $authentication->getSecret()) // Validate user has valid login token ) { // cookie not valid throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.'); } - $roles = Auth::getRoles($user); + $roles = Auth::getRoles($user, $authorization); $channels = Realtime::convertChannels(array_flip($realtime->connections[$connection]['channels']), $user->getId()); $realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels); @@ -642,7 +590,8 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re $server->close($connection, $th->getCode()); } } finally { - $register->get('pools')->reclaim(); + ($container->get('connections'))->reclaim(); + $container->refresh('dbForConsole'); } }); diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml index ad35135a6f..4365bf39fc 100644 --- a/app/views/install/compose.phtml +++ b/app/views/install/compose.phtml @@ -11,7 +11,8 @@ $httpsPort = $this->getParam('httpsPort', ''); $version = $this->getParam('version', ''); $organization = $this->getParam('organization', ''); $image = $this->getParam('image', ''); -?>services: +?> +services: traefik: image: traefik:2.11 container_name: appwrite-traefik @@ -853,7 +854,7 @@ $image = $this->getParam('image', ''); - MYSQL_USER=${_APP_DB_USER} - MYSQL_PASSWORD=${_APP_DB_PASS} - MARIADB_AUTO_UPGRADE=1 - command: 'mysqld --innodb-flush-method=fsync' + command: 'mysqld --innodb-flush-method=fsync --max_connections=5000' redis: image: redis:7.2.4-alpine diff --git a/app/worker.php b/app/worker.php index 6078a0127c..55e14280f6 100644 --- a/app/worker.php +++ b/app/worker.php @@ -2,278 +2,107 @@ require_once __DIR__ . '/init.php'; -use Appwrite\Event\Audit; -use Appwrite\Event\Build; -use Appwrite\Event\Certificate; -use Appwrite\Event\Database as EventDatabase; -use Appwrite\Event\Delete; -use Appwrite\Event\Event; -use Appwrite\Event\Func; -use Appwrite\Event\Mail; -use Appwrite\Event\Messaging; -use Appwrite\Event\Migration; -use Appwrite\Event\Usage; use Appwrite\Event\UsageDump; use Appwrite\Platform\Appwrite; +use Appwrite\Utopia\Queue\Connections; use Swoole\Runtime; -use Utopia\App; -use Utopia\Cache\Adapter\Sharding; -use Utopia\Cache\Cache; use Utopia\CLI\Console; -use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -use Utopia\DSN\DSN; +use Utopia\DI\Dependency; use Utopia\Logger\Log; use Utopia\Logger\Logger; use Utopia\Platform\Service; -use Utopia\Pools\Group; use Utopia\Queue\Connection; use Utopia\Queue\Message; -use Utopia\Queue\Server; -use Utopia\Registry\Registry; +use Utopia\Queue\Worker; +use Utopia\Storage\Device\Local; use Utopia\System\System; -Authorization::disable(); +global $registry, $container; + Runtime::enableCoroutine(SWOOLE_HOOK_ALL); -Server::setResource('register', fn () => $register); +$project = new Dependency(); +$register = new Dependency(); +$dbForProject = new Dependency(); +$abuseRetention = new Dependency(); +$deviceForCache = new Dependency(); +$auditRetention = new Dependency(); +$queueForUsageDump = new Dependency(); +$executionRetention = new Dependency(); +$deviceForLocalFiles = new Dependency(); -Server::setResource('dbForConsole', function (Cache $cache, Registry $register) { - $pools = $register->get('pools'); - $database = $pools - ->get('console') - ->pop() - ->getResource(); +$register + ->setName('register') + ->setCallback(fn () => $registry); - $adapter = new Database($database, $cache); - $adapter->setNamespace('_console'); +$project + ->setName('project') + ->inject('message') + ->inject('dbForConsole') + ->setCallback(function (Message $message, Database $dbForConsole) { + $payload = $message->getPayload() ?? []; + $project = new Document($payload['project'] ?? []); - return $adapter; -}, ['cache', 'register']); - -Server::setResource('project', function (Message $message, Database $dbForConsole) { - $payload = $message->getPayload() ?? []; - $project = new Document($payload['project'] ?? []); - - if ($project->getId() === 'console') { - return $project; - } - - return $dbForConsole->getDocument('projects', $project->getId()); -}, ['message', 'dbForConsole']); - -Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForConsole) { - if ($project->isEmpty() || $project->getId() === 'console') { - return $dbForConsole; - } - - $pools = $register->get('pools'); - - 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')); - } - - $adapter = $pools - ->get($dsn->getHost()) - ->pop() - ->getResource(); - - $database = new Database($adapter, $cache); - - 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')); - } - - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { - $database - ->setSharedTables(true) - ->setTenant($project->getInternalId()) - ->setNamespace($dsn->getParam('namespace')); - } else { - $database - ->setSharedTables(false) - ->setTenant(null) - ->setNamespace('_' . $project->getInternalId()); - } - - return $database; -}, ['cache', 'register', 'message', 'project', 'dbForConsole']); - -Server::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) { - $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools - - return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases): Database { - if ($project->isEmpty() || $project->getId() === 'console') { - return $dbForConsole; + if ($project->getId() === 'console') { + return $project; } - 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')); - } + return $dbForConsole->getDocument('projects', $project->getId()); + }); - if (isset($databases[$dsn->getHost()])) { - $database = $databases[$dsn->getHost()]; +$abuseRetention + ->setName('abuseRetention') + ->setCallback(function () { + return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400)); + }); - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { - $database - ->setSharedTables(true) - ->setTenant($project->getInternalId()) - ->setNamespace($dsn->getParam('namespace')); - } else { - $database - ->setSharedTables(false) - ->setTenant(null) - ->setNamespace('_' . $project->getInternalId()); - } +$auditRetention + ->setName('auditRetention') + ->setCallback(function () { + return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600)); + }); - return $database; - } +$executionRetention + ->setName('executionRetention') + ->setCallback(function () { + return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600)); + }); - $dbAdapter = $pools - ->get($dsn->getHost()) - ->pop() - ->getResource(); +$queueForUsageDump + ->setName('queueForUsageDump') + ->inject('queue') + ->setCallback(function (Connection $queue) { + return new UsageDump($queue); + }); - $database = new Database($dbAdapter, $cache); +$deviceForCache + ->setName('deviceForCache') + ->inject('project') + ->setCallback(function (Document $project) { + return getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId()); + }); - $databases[$dsn->getHost()] = $database; +$deviceForLocalFiles + ->setName('deviceForLocalFiles') + ->inject('project') + ->setCallback(function (Document $project) { + return new Local(APP_STORAGE_UPLOADS . '/app-' . $project->getId()); + }); - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { - $database - ->setSharedTables(true) - ->setTenant($project->getInternalId()) - ->setNamespace($dsn->getParam('namespace')); - } else { - $database - ->setSharedTables(false) - ->setTenant(null) - ->setNamespace('_' . $project->getInternalId()); - } +$container->set($project); +$container->set($register); +$container->set($dbForProject); +$container->set($abuseRetention); +$container->set($auditRetention); +$container->set($deviceForCache); +$container->set($queueForUsageDump); +$container->set($executionRetention); +$container->set($deviceForLocalFiles); - return $database; - }; -}, ['pools', 'dbForConsole', 'cache']); - -Server::setResource('abuseRetention', function () { - return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400)); -}); - -Server::setResource('auditRetention', function () { - return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600)); -}); - -Server::setResource('executionRetention', function () { - return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600)); -}); - -Server::setResource('cache', function (Registry $register) { - $pools = $register->get('pools'); - $list = Config::getParam('pools-cache', []); - $adapters = []; - - foreach ($list as $value) { - $adapters[] = $pools - ->get($value) - ->pop() - ->getResource() - ; - } - - return new Cache(new Sharding($adapters)); -}, ['register']); - -Server::setResource('log', fn () => new Log()); - -Server::setResource('queueForUsage', function (Connection $queue) { - return new Usage($queue); -}, ['queue']); - -Server::setResource('queueForUsageDump', function (Connection $queue) { - return new UsageDump($queue); -}, ['queue']); - -Server::setResource('queue', function (Group $pools) { - return $pools->get('queue')->pop()->getResource(); -}, ['pools']); - -Server::setResource('queueForDatabase', function (Connection $queue) { - return new EventDatabase($queue); -}, ['queue']); - -Server::setResource('queueForMessaging', function (Connection $queue) { - return new Messaging($queue); -}, ['queue']); - -Server::setResource('queueForMails', function (Connection $queue) { - return new Mail($queue); -}, ['queue']); - -Server::setResource('queueForBuilds', function (Connection $queue) { - return new Build($queue); -}, ['queue']); - -Server::setResource('queueForDeletes', function (Connection $queue) { - return new Delete($queue); -}, ['queue']); - -Server::setResource('queueForEvents', function (Connection $queue) { - return new Event($queue); -}, ['queue']); - -Server::setResource('queueForAudits', function (Connection $queue) { - return new Audit($queue); -}, ['queue']); - -Server::setResource('queueForFunctions', function (Connection $queue) { - return new Func($queue); -}, ['queue']); - -Server::setResource('queueForCertificates', function (Connection $queue) { - return new Certificate($queue); -}, ['queue']); - -Server::setResource('queueForMigrations', function (Connection $queue) { - return new Migration($queue); -}, ['queue']); - -Server::setResource('logger', function (Registry $register) { - return $register->get('logger'); -}, ['register']); - -Server::setResource('pools', function (Registry $register) { - return $register->get('pools'); -}, ['register']); - -Server::setResource('deviceForFunctions', function (Document $project) { - return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId()); -}, ['project']); - -Server::setResource('deviceForFiles', function (Document $project) { - return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId()); -}, ['project']); - -Server::setResource('deviceForBuilds', function (Document $project) { - return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId()); -}, ['project']); - -Server::setResource('deviceForCache', function (Document $project) { - return getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId()); -}, ['project']); - - -$pools = $register->get('pools'); $platform = new Appwrite(); $args = $platform->getEnv('argv'); @@ -292,6 +121,13 @@ if (\str_starts_with($workerName, 'databases')) { } try { + $connection = new Connection\Redis( + System::getEnv('_APP_REDIS_HOST', 'redis'), + System::getEnv('_APP_REDIS_PORT', '6379'), + System::getEnv('_APP_REDIS_USER', ''), + System::getEnv('_APP_REDIS_PASS', '') + ); + /** * Any worker can be configured with the following env vars: * - _APP_WORKERS_NUM The total number of worker processes @@ -300,32 +136,35 @@ try { */ $platform->init(Service::TYPE_WORKER, [ 'workersNum' => System::getEnv('_APP_WORKERS_NUM', 1), - 'connection' => $pools->get('queue')->pop()->getResource(), + 'connection' => $connection, 'workerName' => strtolower($workerName) ?? null, 'queueName' => $queueName ]); } catch (\Throwable $e) { - Console::error($e->getMessage() . ', File: ' . $e->getFile() . ', Line: ' . $e->getLine()); + Console::error($e->getMessage() . ', File: ' . $e->getFile() . ', Line: ' . $e->getLine()); } -$worker = $platform->getWorker(); - -$worker - ->shutdown() - ->inject('pools') - ->action(function (Group $pools) { - $pools->reclaim(); +Worker::init() + ->inject('authorization') + ->action(function (Authorization $authorization) { + $authorization->disable(); }); -$worker - ->error() +Worker::shutdown() + ->inject('connections') + ->action(function (Connections $connections) { + $connections->reclaim(); + }); + +Worker::error() ->inject('error') ->inject('logger') ->inject('log') - ->inject('pools') + ->inject('connections') ->inject('project') - ->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project) use ($queueName) { - $pools->reclaim(); + ->inject('authorization') + ->action(function (Throwable $error, ?Logger $logger, Log $log, Connections $connections, Document $project, Authorization $authorization) use ($queueName) { + $connections->reclaim(); $version = System::getEnv('_APP_VERSION', 'UNKNOWN'); if ($logger) { @@ -341,7 +180,7 @@ $worker $log->addExtra('file', $error->getFile()); $log->addExtra('line', $error->getLine()); $log->addExtra('trace', $error->getTraceAsString()); - $log->addExtra('roles', Authorization::getRoles()); + $log->addExtra('roles', $authorization->getRoles()); $isProduction = System::getEnv('_APP_ENV', 'development') === 'production'; $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING); @@ -360,9 +199,7 @@ $worker Console::error('[Error] Line: ' . $error->getLine()); }); -$worker->workerStart() - ->action(function () use ($workerName) { - Console::info("Worker $workerName started"); - }); - -$worker->start(); +$platform + ->getWorker() + ->setContainer($container) + ->start(); diff --git a/composer.json b/composer.json index 26b8c3a7a3..e23806adc9 100644 --- a/composer.json +++ b/composer.json @@ -4,6 +4,7 @@ "description": "End to end backend server for frontend and mobile apps.", "type": "project", "license": "BSD-3-Clause", + "minimum-stability": "stable", "authors": [ { "name": "Eldad Fux", @@ -14,7 +15,8 @@ "test": "vendor/bin/phpunit", "lint": "vendor/bin/pint --test", "format": "vendor/bin/pint", - "bench": "vendor/bin/phpbench run --report=benchmark" + "bench": "vendor/bin/phpbench run --report=benchmark", + "check": "./vendor/bin/phpstan analyse -c phpstan.neon --memory-limit 1G app src tests" }, "autoload": { "psr-4": { @@ -45,33 +47,33 @@ "ext-sockets": "*", "appwrite/php-runtimes": "0.15.*", "appwrite/php-clamav": "2.0.*", - "utopia-php/abuse": "0.43.0", - "utopia-php/analytics": "0.10.*", - "utopia-php/audit": "0.43.0", + "utopia-php/abuse": "0.44.*", + "utopia-php/analytics": "0.13.*", + "utopia-php/audit": "0.44.*", "utopia-php/cache": "0.10.*", - "utopia-php/cli": "0.15.*", + "utopia-php/cli": "0.19.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.53.5-rc1", - "utopia-php/domains": "0.5.*", - "utopia-php/dsn": "0.2.1", - "utopia-php/framework": "0.33.*", + "utopia-php/database": "0.54.*", + "utopia-php/domains": "0.6.*", + "utopia-php/dsn": "0.2.*", + "utopia-php/framework": "1.0.*", "utopia-php/fetch": "0.2.*", "utopia-php/image": "0.6.*", "utopia-php/locale": "0.4.*", "utopia-php/logger": "0.6.*", "utopia-php/messaging": "0.12.*", - "utopia-php/migration": "0.5.*", - "utopia-php/orchestration": "0.9.*", - "utopia-php/platform": "0.7.*", + "utopia-php/migration": "0.4.*", + "utopia-php/orchestration": "0.15.*", + "utopia-php/platform": "0.8.*", + "utopia-php/view": "0.2.*", "utopia-php/pools": "0.5.*", "utopia-php/preloader": "0.2.*", - "utopia-php/queue": "0.7.*", + "utopia-php/queue": "0.8.*", "utopia-php/registry": "0.5.*", - "utopia-php/storage": "0.18.*", - "utopia-php/swoole": "0.8.*", + "utopia-php/storage": "0.19.*", "utopia-php/system": "0.8.*", - "utopia-php/vcs": "0.8.*", - "utopia-php/websocket": "0.1.*", + "utopia-php/vcs": "0.9.*", + "utopia-php/websocket": "0.2.*", "matomo/device-detector": "6.1.*", "dragonmantank/cron-expression": "3.3.2", "phpmailer/phpmailer": "6.9.1", @@ -88,7 +90,8 @@ "swoole/ide-helper": "5.1.2", "textalk/websocket": "1.5.7", "laravel/pint": "^1.14", - "phpbench/phpbench": "^1.2" + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "1.8.*" }, "provide": { "ext-phpiredis": "*" @@ -97,11 +100,5 @@ "platform": { "php": "8.3" } - }, - "repositories": { - "utopia-php/database": { - "type": "vcs", - "url": "https://github.com/utopia-php/database" - } } } diff --git a/composer.lock b/composer.lock index b3a94545b4..cff13d28a1 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "7066d9ca32e7a1a60614effdc4701970", + "content-hash": "6017f815da50b7d4dabad66386e013e3", "packages": [ { "name": "adhocore/jwt", @@ -1429,16 +1429,16 @@ }, { "name": "utopia-php/abuse", - "version": "0.43.0", + "version": "0.44.0", "source": { "type": "git", "url": "https://github.com/utopia-php/abuse.git", - "reference": "6346a3b4c5177a43160035a7289e30fdfb0790d6" + "reference": "cd37bba23778f3a0dc54b4ba6ffb36d4d91ed8f1" }, "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/cd37bba23778f3a0dc54b4ba6ffb36d4d91ed8f1", + "reference": "cd37bba23778f3a0dc54b4ba6ffb36d4d91ed8f1", "shasum": "" }, "require": { @@ -1446,7 +1446,7 @@ "ext-pdo": "*", "ext-redis": "*", "php": ">=8.0", - "utopia-php/database": "0.53.*" + "utopia-php/database": "0.54.*" }, "require-dev": { "laravel/pint": "1.5.*", @@ -1474,27 +1474,28 @@ ], "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.44.0" }, - "time": "2024-08-30T05:17:23+00:00" + "time": "2024-09-05T16:09:32+00:00" }, { "name": "utopia-php/analytics", - "version": "0.10.2", + "version": "0.13.0", "source": { "type": "git", "url": "https://github.com/utopia-php/analytics.git", - "reference": "14c805114736f44c26d6d24b176a2f8b93d86a1f" + "reference": "3dace02af5d4190623f88fb6e02f5559a99f14c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/analytics/zipball/14c805114736f44c26d6d24b176a2f8b93d86a1f", - "reference": "14c805114736f44c26d6d24b176a2f8b93d86a1f", + "url": "https://api.github.com/repos/utopia-php/analytics/zipball/3dace02af5d4190623f88fb6e02f5559a99f14c4", + "reference": "3dace02af5d4190623f88fb6e02f5559a99f14c4", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/cli": "^0.15.0" + "utopia-php/cli": "0.19.*", + "utopia-php/system": "0.8.*" }, "require-dev": { "laravel/pint": "dev-main", @@ -1520,27 +1521,27 @@ ], "support": { "issues": "https://github.com/utopia-php/analytics/issues", - "source": "https://github.com/utopia-php/analytics/tree/0.10.2" + "source": "https://github.com/utopia-php/analytics/tree/0.13.0" }, - "time": "2023-03-22T12:01:09+00:00" + "time": "2024-09-05T16:19:26+00:00" }, { "name": "utopia-php/audit", - "version": "0.43.0", + "version": "0.44.0", "source": { "type": "git", "url": "https://github.com/utopia-php/audit.git", - "reference": "cef22b5dc6a6d28fcd522f41c7bf7ded4a4dfd3e" + "reference": "69eee24e4d6cb8fdae31235d80b9a46b18092139" }, "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/69eee24e4d6cb8fdae31235d80b9a46b18092139", + "reference": "69eee24e4d6cb8fdae31235d80b9a46b18092139", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/database": "0.53.*" + "utopia-php/database": "0.54.*" }, "require-dev": { "laravel/pint": "1.5.*", @@ -1567,9 +1568,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.44.0" }, - "time": "2024-08-30T05:17:36+00:00" + "time": "2024-09-05T16:12:41+00:00" }, { "name": "utopia-php/cache", @@ -1623,27 +1624,29 @@ }, { "name": "utopia-php/cli", - "version": "0.15.0", + "version": "0.19.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cli.git", - "reference": "ccb7c8125ffe0254fef8f25744bfa376eb7bd0ea" + "reference": "f8af1d6087f498bc1f0191750a118d357ded9948" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cli/zipball/ccb7c8125ffe0254fef8f25744bfa376eb7bd0ea", - "reference": "ccb7c8125ffe0254fef8f25744bfa376eb7bd0ea", + "url": "https://api.github.com/repos/utopia-php/cli/zipball/f8af1d6087f498bc1f0191750a118d357ded9948", + "reference": "f8af1d6087f498bc1f0191750a118d357ded9948", "shasum": "" }, "require": { "php": ">=7.4", - "utopia-php/framework": "0.*.*" + "utopia-php/di": "0.1.*", + "utopia-php/framework": "1.0.*" }, "require-dev": { "laravel/pint": "1.2.*", + "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.3", "squizlabs/php_codesniffer": "^3.6", - "vimeo/psalm": "4.0.1" + "swoole/ide-helper": "4.8.8" }, "type": "library", "autoload": { @@ -1666,9 +1669,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cli/issues", - "source": "https://github.com/utopia-php/cli/tree/0.15.0" + "source": "https://github.com/utopia-php/cli/tree/0.19.0" }, - "time": "2023-03-01T05:55:14+00:00" + "time": "2024-09-05T15:46:56+00:00" }, { "name": "utopia-php/config", @@ -1723,16 +1726,16 @@ }, { "name": "utopia-php/database", - "version": "0.53.5-rc1", + "version": "0.54.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "689ba22063bf46def385da8695ba7a921e81e38d" + "reference": "c32d6eab5992c927cbf6fb4aad51d76fc5f64946" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/689ba22063bf46def385da8695ba7a921e81e38d", - "reference": "689ba22063bf46def385da8695ba7a921e81e38d", + "url": "https://api.github.com/repos/utopia-php/database/zipball/c32d6eab5992c927cbf6fb4aad51d76fc5f64946", + "reference": "c32d6eab5992c927cbf6fb4aad51d76fc5f64946", "shasum": "" }, "require": { @@ -1740,7 +1743,7 @@ "ext-pdo": "*", "php": ">=8.0", "utopia-php/cache": "0.10.*", - "utopia-php/framework": "0.33.*", + "utopia-php/framework": "1.0.*", "utopia-php/mongo": "0.3.*" }, "require-dev": { @@ -1751,7 +1754,7 @@ "phpunit/phpunit": "9.6.*", "rregeer/phpunit-coverage-check": "0.3.*", "swoole/ide-helper": "5.1.3", - "utopia-php/cli": "0.14.*" + "utopia-php/cli": "0.19.*" }, "type": "library", "autoload": { @@ -1759,38 +1762,7 @@ "Utopia\\Database\\": "src/Database" } }, - "autoload-dev": { - "psr-4": { - "Tests\\E2E\\": "tests/e2e", - "Tests\\Unit\\": "tests/unit" - } - }, - "scripts": { - "build": [ - "Composer\\Config::disableProcessTimeout", - "docker compose build" - ], - "start": [ - "Composer\\Config::disableProcessTimeout", - "docker compose up -d" - ], - "test": [ - "Composer\\Config::disableProcessTimeout", - "docker compose exec tests vendor/bin/phpunit --configuration phpunit.xml" - ], - "lint": [ - "./vendor/bin/pint --test" - ], - "format": [ - "./vendor/bin/pint" - ], - "check": [ - "./vendor/bin/phpstan analyse --level 7 src tests --memory-limit 512M" - ], - "coverage": [ - "./vendor/bin/coverage-check ./tmp/clover.xml 90" - ] - }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -1803,31 +1775,80 @@ "utopia" ], "support": { - "source": "https://github.com/utopia-php/database/tree/0.53.5-rc1", - "issues": "https://github.com/utopia-php/database/issues" + "issues": "https://github.com/utopia-php/database/issues", + "source": "https://github.com/utopia-php/database/tree/0.54.1" }, - "time": "2024-09-24T08:43:10+00:00" + "time": "2024-09-10T10:08:37+00:00" }, { - "name": "utopia-php/domains", - "version": "0.5.0", + "name": "utopia-php/di", + "version": "0.1.0", "source": { "type": "git", - "url": "https://github.com/utopia-php/domains.git", - "reference": "bf07f60326f8389f378ddf6fcde86217e5cfe18c" + "url": "https://github.com/utopia-php/di.git", + "reference": "22490c95f7ac3898ed1c33f1b1b5dd577305ee31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/domains/zipball/bf07f60326f8389f378ddf6fcde86217e5cfe18c", - "reference": "bf07f60326f8389f378ddf6fcde86217e5cfe18c", + "url": "https://api.github.com/repos/utopia-php/di/zipball/22490c95f7ac3898ed1c33f1b1b5dd577305ee31", + "reference": "22490c95f7ac3898ed1c33f1b1b5dd577305ee31", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "laravel/pint": "^1.2", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.25", + "swoole/ide-helper": "4.8.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\": "src/", + "Tests\\E2E\\": "tests/e2e" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A simple and lite library for managing dependency injections", + "keywords": [ + "framework", + "http", + "php", + "upf" + ], + "support": { + "issues": "https://github.com/utopia-php/di/issues", + "source": "https://github.com/utopia-php/di/tree/0.1.0" + }, + "time": "2024-08-08T14:35:19+00:00" + }, + { + "name": "utopia-php/domains", + "version": "0.6.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/domains.git", + "reference": "5c70b0f524deeb1fccc3962ad1da650ae2c94cda" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/domains/zipball/5c70b0f524deeb1fccc3962ad1da650ae2c94cda", + "reference": "5c70b0f524deeb1fccc3962ad1da650ae2c94cda", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/framework": "0.*.*" + "utopia-php/framework": "1.0.*" }, "require-dev": { "laravel/pint": "1.2.*", + "phpstan/phpstan": "1.9.x-dev", "phpunit/phpunit": "^9.3" }, "type": "library", @@ -1864,9 +1885,9 @@ ], "support": { "issues": "https://github.com/utopia-php/domains/issues", - "source": "https://github.com/utopia-php/domains/tree/0.5.0" + "source": "https://github.com/utopia-php/domains/tree/0.6.0" }, - "time": "2024-01-03T22:04:27+00:00" + "time": "2024-09-05T16:21:11+00:00" }, { "name": "utopia-php/dsn", @@ -1956,26 +1977,30 @@ }, { "name": "utopia-php/framework", - "version": "0.33.8", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "a7f577540a25cb90896fef2b64767bf8d700f3c5" + "reference": "fc63ec61c720190a5ea5bb484c615145850951e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/a7f577540a25cb90896fef2b64767bf8d700f3c5", - "reference": "a7f577540a25cb90896fef2b64767bf8d700f3c5", + "url": "https://api.github.com/repos/utopia-php/http/zipball/fc63ec61c720190a5ea5bb484c615145850951e6", + "reference": "fc63ec61c720190a5ea5bb484c615145850951e6", "shasum": "" }, "require": { - "php": ">=8.0" + "ext-swoole": "*", + "php": ">=8.0", + "utopia-php/servers": "0.1.*" }, "require-dev": { + "ext-xdebug": "*", "laravel/pint": "^1.2", "phpbench/phpbench": "^1.2", "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.5.25" + "phpunit/phpunit": "^9.5.25", + "swoole/ide-helper": "4.8.3" }, "type": "library", "autoload": { @@ -1987,17 +2012,18 @@ "license": [ "MIT" ], - "description": "A simple, light and advanced PHP framework", + "description": "A simple, light and advanced PHP HTTP framework", "keywords": [ "framework", + "http", "php", "upf" ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.8" + "source": "https://github.com/utopia-php/http/tree/1.0.2" }, - "time": "2024-08-15T14:10:09+00:00" + "time": "2024-09-10T09:04:19+00:00" }, { "name": "utopia-php/image", @@ -2100,16 +2126,16 @@ }, { "name": "utopia-php/logger", - "version": "0.6.1", + "version": "0.6.0", "source": { "type": "git", "url": "https://github.com/utopia-php/logger.git", - "reference": "7e8ff512c6f04577aba1df67c7b9628971946f9c" + "reference": "a2d1daeeb8f61fdec6d851950d9a021a3d05c9f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/logger/zipball/7e8ff512c6f04577aba1df67c7b9628971946f9c", - "reference": "7e8ff512c6f04577aba1df67c7b9628971946f9c", + "url": "https://api.github.com/repos/utopia-php/logger/zipball/a2d1daeeb8f61fdec6d851950d9a021a3d05c9f9", + "reference": "a2d1daeeb8f61fdec6d851950d9a021a3d05c9f9", "shasum": "" }, "require": { @@ -2148,9 +2174,9 @@ ], "support": { "issues": "https://github.com/utopia-php/logger/issues", - "source": "https://github.com/utopia-php/logger/tree/0.6.1" + "source": "https://github.com/utopia-php/logger/tree/0.6.0" }, - "time": "2024-09-20T14:02:12+00:00" + "time": "2024-05-23T13:37:54+00:00" }, { "name": "utopia-php/messaging", @@ -2205,16 +2231,16 @@ }, { "name": "utopia-php/migration", - "version": "0.5.2", + "version": "0.4.4", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "f18d44d4459f78c292dac9edde856fd156fe497a" + "reference": "a8a5d392bebf082faf289f4dfe09d9fd76844c33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/f18d44d4459f78c292dac9edde856fd156fe497a", - "reference": "f18d44d4459f78c292dac9edde856fd156fe497a", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/a8a5d392bebf082faf289f4dfe09d9fd76844c33", + "reference": "a8a5d392bebf082faf289f4dfe09d9fd76844c33", "shasum": "" }, "require": { @@ -2224,7 +2250,6 @@ "require-dev": { "laravel/pint": "1.*", "phpunit/phpunit": "9.*", - "utopia-php/cli": "^0.18.0", "vlucas/phpdotenv": "5.*" }, "type": "library", @@ -2247,9 +2272,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/0.5.2" + "source": "https://github.com/utopia-php/migration/tree/0.4.4" }, - "time": "2024-07-22T09:27:07+00:00" + "time": "2024-05-17T05:25:31+00:00" }, { "name": "utopia-php/mongo", @@ -2313,26 +2338,26 @@ }, { "name": "utopia-php/orchestration", - "version": "0.9.1", + "version": "0.15.0", "source": { "type": "git", "url": "https://github.com/utopia-php/orchestration.git", - "reference": "55f43513b3f940a3f4f9c2cde7682d0c2581beb0" + "reference": "cd55650ba5f13118c3580048e6dd86b604f9a5b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/orchestration/zipball/55f43513b3f940a3f4f9c2cde7682d0c2581beb0", - "reference": "55f43513b3f940a3f4f9c2cde7682d0c2581beb0", + "url": "https://api.github.com/repos/utopia-php/orchestration/zipball/cd55650ba5f13118c3580048e6dd86b604f9a5b3", + "reference": "cd55650ba5f13118c3580048e6dd86b604f9a5b3", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/cli": "0.15.*" + "utopia-php/cli": "0.19.*" }, "require-dev": { "laravel/pint": "^1.2", - "phpunit/phpunit": "^9.3", - "vimeo/psalm": "4.0.1" + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.3" }, "type": "library", "autoload": { @@ -2357,31 +2382,32 @@ ], "support": { "issues": "https://github.com/utopia-php/orchestration/issues", - "source": "https://github.com/utopia-php/orchestration/tree/0.9.1" + "source": "https://github.com/utopia-php/orchestration/tree/0.15.0" }, - "time": "2023-03-17T15:05:06+00:00" + "time": "2024-09-05T16:28:02+00:00" }, { "name": "utopia-php/platform", - "version": "0.7.0", + "version": "0.8.1", "source": { "type": "git", "url": "https://github.com/utopia-php/platform.git", - "reference": "beeea0f2c9bce14a6869fc5c87a1047cdecb5c52" + "reference": "95d57f38a4001c7189a66885c485ac635d305234" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/platform/zipball/beeea0f2c9bce14a6869fc5c87a1047cdecb5c52", - "reference": "beeea0f2c9bce14a6869fc5c87a1047cdecb5c52", + "url": "https://api.github.com/repos/utopia-php/platform/zipball/95d57f38a4001c7189a66885c485ac635d305234", + "reference": "95d57f38a4001c7189a66885c485ac635d305234", "shasum": "" }, "require": { "ext-json": "*", "ext-redis": "*", "php": ">=8.0", - "utopia-php/cli": "0.15.*", - "utopia-php/framework": "0.33.*", - "utopia-php/queue": "0.7.*" + "utopia-php/cli": "0.19.*", + "utopia-php/framework": "1.0.*", + "utopia-php/queue": "0.8.*", + "utopia-php/servers": "0.1.*" }, "require-dev": { "laravel/pint": "1.2.*", @@ -2407,9 +2433,9 @@ ], "support": { "issues": "https://github.com/utopia-php/platform/issues", - "source": "https://github.com/utopia-php/platform/tree/0.7.0" + "source": "https://github.com/utopia-php/platform/tree/0.8.1" }, - "time": "2024-05-08T17:00:55+00:00" + "time": "2024-09-06T02:33:27+00:00" }, { "name": "utopia-php/pools", @@ -2517,22 +2543,23 @@ }, { "name": "utopia-php/queue", - "version": "0.7.0", + "version": "0.8.0", "source": { "type": "git", "url": "https://github.com/utopia-php/queue.git", - "reference": "917565256eb94bcab7246f7a746b1a486813761b" + "reference": "a518b271f8c158d6e66e36972f767189111033c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/queue/zipball/917565256eb94bcab7246f7a746b1a486813761b", - "reference": "917565256eb94bcab7246f7a746b1a486813761b", + "url": "https://api.github.com/repos/utopia-php/queue/zipball/a518b271f8c158d6e66e36972f767189111033c2", + "reference": "a518b271f8c158d6e66e36972f767189111033c2", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/cli": "0.15.*", - "utopia-php/framework": "0.*.*" + "utopia-php/cli": "0.19.*", + "utopia-php/di": "0.1.*", + "utopia-php/servers": "0.1.*" }, "require-dev": { "laravel/pint": "^0.2.3", @@ -2542,6 +2569,7 @@ "workerman/workerman": "^4.0" }, "suggest": { + "ext-redis": "Needed to support Redis connections", "ext-swoole": "Needed to support Swoole.", "workerman/workerman": "Needed to support Workerman." }, @@ -2572,9 +2600,9 @@ ], "support": { "issues": "https://github.com/utopia-php/queue/issues", - "source": "https://github.com/utopia-php/queue/tree/0.7.0" + "source": "https://github.com/utopia-php/queue/tree/0.8.0" }, - "time": "2024-01-17T19:00:43+00:00" + "time": "2024-09-05T16:33:01+00:00" }, { "name": "utopia-php/registry", @@ -2629,17 +2657,70 @@ "time": "2021-03-10T10:45:22+00:00" }, { - "name": "utopia-php/storage", - "version": "0.18.5", + "name": "utopia-php/servers", + "version": "0.1.1", "source": { "type": "git", - "url": "https://github.com/utopia-php/storage.git", - "reference": "7d355c5e3ccc8ecebc0266f8ddd30088a43be919" + "url": "https://github.com/utopia-php/servers.git", + "reference": "fd5c8d32778f265256c1936372a071b944f5ba8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/storage/zipball/7d355c5e3ccc8ecebc0266f8ddd30088a43be919", - "reference": "7d355c5e3ccc8ecebc0266f8ddd30088a43be919", + "url": "https://api.github.com/repos/utopia-php/servers/zipball/fd5c8d32778f265256c1936372a071b944f5ba8a", + "reference": "fd5c8d32778f265256c1936372a071b944f5ba8a", + "shasum": "" + }, + "require": { + "php": ">=8.0", + "utopia-php/di": "0.1.*" + }, + "require-dev": { + "laravel/pint": "^0.2.3", + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^9.5.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Servers\\": "src/Servers" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Team Appwrite", + "email": "team@appwrite.io" + } + ], + "description": "A base library for building Utopia style servers.", + "keywords": [ + "framework", + "php", + "servers", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/servers/issues", + "source": "https://github.com/utopia-php/servers/tree/0.1.1" + }, + "time": "2024-09-06T02:25:56+00:00" + }, + { + "name": "utopia-php/storage", + "version": "0.19.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/storage.git", + "reference": "5013b894a776874d6010753fc9349df2225c69af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/storage/zipball/5013b894a776874d6010753fc9349df2225c69af", + "reference": "5013b894a776874d6010753fc9349df2225c69af", "shasum": "" }, "require": { @@ -2651,8 +2732,8 @@ "ext-zlib": "*", "ext-zstd": "*", "php": ">=8.0", - "utopia-php/framework": "0.*.*", - "utopia-php/system": "0.*.*" + "utopia-php/framework": "1.0.*", + "utopia-php/system": "0.8.*" }, "require-dev": { "laravel/pint": "1.2.*", @@ -2679,60 +2760,9 @@ ], "support": { "issues": "https://github.com/utopia-php/storage/issues", - "source": "https://github.com/utopia-php/storage/tree/0.18.5" + "source": "https://github.com/utopia-php/storage/tree/0.19.0" }, - "time": "2024-09-04T08:57:27+00:00" - }, - { - "name": "utopia-php/swoole", - "version": "0.8.2", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/swoole.git", - "reference": "5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/swoole/zipball/5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4", - "reference": "5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4", - "shasum": "" - }, - "require": { - "ext-swoole": "*", - "php": ">=8.0", - "utopia-php/framework": "0.33.*" - }, - "require-dev": { - "laravel/pint": "1.2.*", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.3", - "swoole/ide-helper": "5.0.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\Swoole\\": "src/Swoole" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "An extension for Utopia Framework to work with PHP Swoole as a PHP FPM alternative", - "keywords": [ - "framework", - "http", - "php", - "server", - "swoole", - "upf", - "utopia" - ], - "support": { - "issues": "https://github.com/utopia-php/swoole/issues", - "source": "https://github.com/utopia-php/swoole/tree/0.8.2" - }, - "time": "2024-02-01T14:54:12+00:00" + "time": "2024-09-05T17:00:24+00:00" }, { "name": "utopia-php/system", @@ -2792,23 +2822,24 @@ }, { "name": "utopia-php/vcs", - "version": "0.8.2", + "version": "0.9.0", "source": { "type": "git", "url": "https://github.com/utopia-php/vcs.git", - "reference": "eb9b7eade1a46a4f660e0d5a6304f7fa26ec9d18" + "reference": "673abe2fef0750a841a4fa8fa6f99d4a602c68e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/vcs/zipball/eb9b7eade1a46a4f660e0d5a6304f7fa26ec9d18", - "reference": "eb9b7eade1a46a4f660e0d5a6304f7fa26ec9d18", + "url": "https://api.github.com/repos/utopia-php/vcs/zipball/673abe2fef0750a841a4fa8fa6f99d4a602c68e7", + "reference": "673abe2fef0750a841a4fa8fa6f99d4a602c68e7", "shasum": "" }, "require": { "adhocore/jwt": "^1.1", "php": ">=8.0", - "utopia-php/cache": "^0.10.0", - "utopia-php/framework": "0.*.*" + "utopia-php/cache": "0.10.*", + "utopia-php/framework": "1.0.*", + "utopia-php/system": "0.8.*" }, "require-dev": { "laravel/pint": "1.2.*", @@ -2835,32 +2866,76 @@ ], "support": { "issues": "https://github.com/utopia-php/vcs/issues", - "source": "https://github.com/utopia-php/vcs/tree/0.8.2" + "source": "https://github.com/utopia-php/vcs/tree/0.9.0" }, - "time": "2024-08-13T14:36:30+00:00" + "time": "2024-09-05T16:44:48+00:00" }, { - "name": "utopia-php/websocket", - "version": "0.1.0", + "name": "utopia-php/view", + "version": "0.2.0", "source": { "type": "git", - "url": "https://github.com/utopia-php/websocket.git", - "reference": "51fcb86171400d8aa40d76c54593481fd273dab5" + "url": "https://github.com/utopia-php/view.git", + "reference": "6ee55e83bc014c39ed6b69390f6d399116f65e88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/websocket/zipball/51fcb86171400d8aa40d76c54593481fd273dab5", - "reference": "51fcb86171400d8aa40d76c54593481fd273dab5", + "url": "https://api.github.com/repos/utopia-php/view/zipball/6ee55e83bc014c39ed6b69390f6d399116f65e88", + "reference": "6ee55e83bc014c39ed6b69390f6d399116f65e88", "shasum": "" }, "require": { "php": ">=8.0" }, "require-dev": { + "laravel/pint": "^1.2", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.25" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\View\\": "src/View" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A simple, light and advanced PHP rendering engine", + "keywords": [ + "php", + "view" + ], + "support": { + "issues": "https://github.com/utopia-php/view/issues", + "source": "https://github.com/utopia-php/view/tree/0.2.0" + }, + "time": "2024-04-01T17:21:29+00:00" + }, + { + "name": "utopia-php/websocket", + "version": "0.2.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/websocket.git", + "reference": "e9d0919b321744a61f12563f5791c47ba9f57810" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/websocket/zipball/e9d0919b321744a61f12563f5791c47ba9f57810", + "reference": "e9d0919b321744a61f12563f5791c47ba9f57810", + "shasum": "" + }, + "require": { + "php": ">=8.0" + }, + "require-dev": { + "laravel/pint": "^1.15", + "phpstan/phpstan": "^1.8", "phpunit/phpunit": "^9.5.5", - "swoole/ide-helper": "4.6.6", + "swoole/ide-helper": "5.1.2", "textalk/websocket": "1.5.2", - "vimeo/psalm": "^4.8.1", "workerman/workerman": "^4.0" }, "type": "library", @@ -2873,16 +2948,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Eldad Fux", - "email": "eldad@appwrite.io" - }, - { - "name": "Torsten Dittmann", - "email": "torsten@appwrite.io" - } - ], "description": "A simple abstraction for WebSocket servers.", "keywords": [ "framework", @@ -2893,9 +2958,9 @@ ], "support": { "issues": "https://github.com/utopia-php/websocket/issues", - "source": "https://github.com/utopia-php/websocket/tree/0.1.0" + "source": "https://github.com/utopia-php/websocket/tree/0.2.0" }, - "time": "2021-12-20T10:50:09+00:00" + "time": "2024-04-09T08:28:11+00:00" }, { "name": "webmozart/assert", @@ -4216,16 +4281,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.31.0", + "version": "1.30.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "249f15fb843bf240cf058372dad29e100cee6c17" + "reference": "51b95ec8670af41009e2b2b56873bad96682413e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/249f15fb843bf240cf058372dad29e100cee6c17", - "reference": "249f15fb843bf240cf058372dad29e100cee6c17", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/51b95ec8670af41009e2b2b56873bad96682413e", + "reference": "51b95ec8670af41009e2b2b56873bad96682413e", "shasum": "" }, "require": { @@ -4257,9 +4322,68 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.31.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.30.1" }, - "time": "2024-09-22T11:32:18+00:00" + "time": "2024-09-07T20:13:05+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.8.11", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "46e223dd68a620da18855c23046ddb00940b4014" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/46e223dd68a620da18855c23046ddb00940b4014", + "reference": "46e223dd68a620da18855c23046ddb00940b4014", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.8.11" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-10-24T15:45:13+00:00" }, { "name": "phpunit/php-code-coverage", @@ -5896,16 +6020,16 @@ }, { "name": "symfony/console", - "version": "v7.1.5", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee" + "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0fa539d12b3ccf068a722bbbffa07ca7079af9ee", - "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee", + "url": "https://api.github.com/repos/symfony/console/zipball/1eed7af6961d763e7832e874d7f9b21c3ea9c111", + "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111", "shasum": "" }, "require": { @@ -5969,7 +6093,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.5" + "source": "https://github.com/symfony/console/tree/v7.1.4" }, "funding": [ { @@ -5985,7 +6109,7 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:28:38+00:00" + "time": "2024-08-15T22:48:53+00:00" }, { "name": "symfony/deprecation-contracts", @@ -6056,16 +6180,16 @@ }, { "name": "symfony/filesystem", - "version": "v7.1.5", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "61fe0566189bf32e8cfee78335d8776f64a66f5a" + "reference": "92a91985250c251de9b947a14bb2c9390b1a562c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/61fe0566189bf32e8cfee78335d8776f64a66f5a", - "reference": "61fe0566189bf32e8cfee78335d8776f64a66f5a", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/92a91985250c251de9b947a14bb2c9390b1a562c", + "reference": "92a91985250c251de9b947a14bb2c9390b1a562c", "shasum": "" }, "require": { @@ -6102,7 +6226,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.1.5" + "source": "https://github.com/symfony/filesystem/tree/v7.1.2" }, "funding": [ { @@ -6118,7 +6242,7 @@ "type": "tidelift" } ], - "time": "2024-09-17T09:16:35+00:00" + "time": "2024-06-28T10:03:55+00:00" }, { "name": "symfony/finder", @@ -6567,16 +6691,16 @@ }, { "name": "symfony/process", - "version": "v7.1.5", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "5c03ee6369281177f07f7c68252a280beccba847" + "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/5c03ee6369281177f07f7c68252a280beccba847", - "reference": "5c03ee6369281177f07f7c68252a280beccba847", + "url": "https://api.github.com/repos/symfony/process/zipball/7f2f542c668ad6c313dc4a5e9c3321f733197eca", + "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca", "shasum": "" }, "require": { @@ -6608,7 +6732,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.1.5" + "source": "https://github.com/symfony/process/tree/v7.1.3" }, "funding": [ { @@ -6624,7 +6748,7 @@ "type": "tidelift" } ], - "time": "2024-09-19T21:48:23+00:00" + "time": "2024-07-26T12:44:47+00:00" }, { "name": "symfony/service-contracts", @@ -6711,16 +6835,16 @@ }, { "name": "symfony/string", - "version": "v7.1.5", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306" + "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/d66f9c343fa894ec2037cc928381df90a7ad4306", - "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306", + "url": "https://api.github.com/repos/symfony/string/zipball/6cd670a6d968eaeb1c77c2e76091c45c56bc367b", + "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b", "shasum": "" }, "require": { @@ -6778,7 +6902,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.5" + "source": "https://github.com/symfony/string/tree/v7.1.4" }, "funding": [ { @@ -6794,7 +6918,7 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:28:38+00:00" + "time": "2024-08-12T09:59:40+00:00" }, { "name": "textalk/websocket", @@ -7026,9 +7150,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "utopia-php/database": 5 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/docker-compose.yml b/docker-compose.yml index 6ecb0ecff8..c4bdffa1d0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -682,6 +682,7 @@ services: entrypoint: maintenance <<: *x-logging container_name: appwrite-task-maintenance + restart: unless-stopped image: appwrite-dev networks: - appwrite @@ -782,6 +783,7 @@ services: entrypoint: schedule-functions <<: *x-logging container_name: appwrite-task-scheduler-functions + restart: unless-stopped image: appwrite-dev networks: - appwrite @@ -810,6 +812,7 @@ services: entrypoint: schedule-executions <<: *x-logging container_name: appwrite-task-scheduler-executions + restart: unless-stopped image: appwrite-dev networks: - appwrite @@ -837,6 +840,7 @@ services: entrypoint: schedule-messages <<: *x-logging container_name: appwrite-task-scheduler-messages + restart: unless-stopped image: appwrite-dev networks: - appwrite @@ -956,7 +960,7 @@ services: - MYSQL_USER=${_APP_DB_USER} - MYSQL_PASSWORD=${_APP_DB_PASS} - MARIADB_AUTO_UPGRADE=1 - command: "mysqld --innodb-flush-method=fsync" + command: 'mysqld --innodb-flush-method=fsync --max_connections=5000' redis: image: redis:7.2.4-alpine diff --git a/docs/tutorials/add-route.md b/docs/tutorials/add-route.md index ac6fd40bdb..0baa51b5c0 100644 --- a/docs/tutorials/add-route.md +++ b/docs/tutorials/add-route.md @@ -8,7 +8,7 @@ Setting an alias allows the route to be also accessible from the alias URL. The first parameter specifies the alias URL, the second parameter specifies default values for route parameters. ```php -App::post('/v1/storage/buckets/:bucketId/files') +Http::post('/v1/storage/buckets/:bucketId/files') ->alias('/v1/storage/files', ['bucketId' => 'default']) ``` @@ -17,7 +17,7 @@ App::post('/v1/storage/buckets/:bucketId/files') Used as an abstract description of the route. ```php -App::post('/v1/storage/buckets/:bucketId/files') +Http::post('/v1/storage/buckets/:bucketId/files') ->desc('Create File') ``` @@ -26,14 +26,14 @@ App::post('/v1/storage/buckets/:bucketId/files') Groups array is used to group one or more routes with one or more hooks functionality. ```php -App::post('/v1/storage/buckets/:bucketId/files') +Http::post('/v1/storage/buckets/:bucketId/files') ->groups(['api']) ``` In the above example groups() is used to define the current route as part of the routes that shares a common init middleware hook. ```php -App::init() +Http::init() ->groups(['api']) ->action( some code..... @@ -52,7 +52,7 @@ Appwrite uses different labels to achieve different things, for example: - scope - Defines the route permissions scope. ```php -App::post('/v1/storage/buckets/:bucketId/files') +Http::post('/v1/storage/buckets/:bucketId/files') ->label('scope', 'files.write') ``` @@ -66,7 +66,7 @@ App::post('/v1/storage/buckets/:bucketId/files') - audits.resource - Signals the extraction part of the resource. ```php -App::post('/v1/account/create') +Http::post('/v1/account/create') ->label('audits.event', 'account.create') ->label('audits.resource', 'user/{response.$id}') ->label('audits.userId', '{response.$id}') @@ -84,7 +84,7 @@ App::post('/v1/account/create') * sdk.offline.response.key - JSON property name that has the ID. Defaults to $id ```php -App::post('/v1/account/jwt') +Http::post('/v1/account/jwt') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION]) ->label('sdk.namespace', 'account') ->label('sdk.method', 'createJWT') @@ -100,7 +100,7 @@ App::post('/v1/account/jwt') - cache.resource - Identifies the cached resource. ```php -App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') +Http::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') ->label('cache', true) ->label('cache.resource', 'file/{request.fileId}') ``` @@ -115,7 +115,7 @@ When using the example below, we configure the abuse mechanism to allow this key constructed from the combination of the ip, http method, url, userId to hit the route maximum 60 times in 1 hour (60 seconds \* 60 minutes). ```php -App::post('/v1/storage/buckets/:bucketId/files') +Http::post('/v1/storage/buckets/:bucketId/files') ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') ->label('abuse-limit', 60) ->label('abuse-time', 3600) @@ -127,7 +127,7 @@ App::post('/v1/storage/buckets/:bucketId/files') Placeholders marked as `[]` are parsed and replaced with their real values. ```php -App::post('/v1/storage/buckets/:bucketId/files') +Http::post('/v1/storage/buckets/:bucketId/files') ->label('event', 'buckets.[bucketId].files.[fileId].create') ``` @@ -145,7 +145,7 @@ As the name implies, `param()` is used to define a request parameter. - An array of injections ```php -App::get('/v1/account/logs') +Http::get('/v1/account/logs') ->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) ``` @@ -154,14 +154,14 @@ App::get('/v1/account/logs') inject is used to inject dependencies pre-bounded to the app. ```php -App::post('/v1/storage/buckets/:bucketId/files') +Http::post('/v1/storage/buckets/:bucketId/files') ->inject('user') ``` -In the example above, the user object is injected into the route pre-bounded using `App::setResource()`. +In the example above, the user object is injected into the route pre-bounded using `Http::setResource()`. ```php -App::setResource('user', function() { +Http::setResource('user', function() { some code... }); ``` @@ -170,7 +170,7 @@ some code... Action populates the actual route code and has to be very clear and understandable. A good route stays simple and doesn't contain complex logic. An action is where we describe our business needs in code, and combine different libraries to work together and tell our story. ```php -App::post('/v1/account/sessions/anonymous') +Http::post('/v1/account/sessions/anonymous') ->action(function (Request $request) { some code... }); diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000000..25771ef17c --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,11 @@ +parameters: + level: 0 + scanDirectories: + - vendor/swoole/ide-helper + excludePaths: + - tests/resources + ignoreErrors: + - '#Parameter \$geodb of anonymous function has invalid type MaxMind\\Db\\Reader\.#' + - '#Parameter \$geodb of function router\(\) has invalid type MaxMind\\Db\\Reader\.#' + - '#Instantiated class MaxMind\\Db\\Reader not found.#' + - '#Function scrypt not found\.#' diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index 1e8109622e..0135020765 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -91,37 +91,6 @@ class Auth */ public const MFA_RECENT_DURATION = 1800; // 30 mins - /** - * @var string - */ - public static $cookieName = 'a_session'; - - /** - * User Unique ID. - * - * @var string - */ - public static $unique = ''; - - /** - * User Secret Key. - * - * @var string - */ - public static $secret = ''; - - /** - * Set Cookie Name. - * - * @param $string - * - * @return string - */ - public static function setCookieName($string) - { - return self::$cookieName = $string; - } - /** * Encode Session. * @@ -439,13 +408,14 @@ class Auth * Returns all roles for a user. * * @param Document $user + * @param Authorization $auth * @return array */ - public static function getRoles(Document $user): array + public static function getRoles(Document $user, Authorization $auth): array { $roles = []; - if (!self::isPrivilegedUser(Authorization::getRoles()) && !self::isAppUser(Authorization::getRoles())) { + if (!self::isPrivilegedUser($auth->getRoles()) && !self::isAppUser($auth->getRoles())) { if ($user->getId()) { $roles[] = Role::user($user->getId())->toString(); $roles[] = Role::users()->toString(); diff --git a/src/Appwrite/Auth/Authentication.php b/src/Appwrite/Auth/Authentication.php new file mode 100644 index 0000000000..ef372309da --- /dev/null +++ b/src/Appwrite/Auth/Authentication.php @@ -0,0 +1,57 @@ +cookieName = $string; + } + + public function getCookieName(): string + { + return $this->cookieName; + } + + public function getUnique(): string + { + return $this->unique; + } + + public function setUnique(string $unique): void + { + $this->unique = $unique; + } + + public function getSecret(): string + { + return $this->secret; + } + + public function setSecret(string $secret): void + { + $this->secret = $secret; + } + +} diff --git a/src/Appwrite/Auth/Validator/MockNumber.php b/src/Appwrite/Auth/Validator/MockNumber.php index ac5ba89fc5..8f0f14c9da 100644 --- a/src/Appwrite/Auth/Validator/MockNumber.php +++ b/src/Appwrite/Auth/Validator/MockNumber.php @@ -2,8 +2,8 @@ namespace Appwrite\Auth\Validator; -use Utopia\Validator; -use Utopia\Validator\Text; +use Utopia\Http\Validator; +use Utopia\Http\Validator\Text; /** * MockNumber. @@ -52,6 +52,7 @@ class MockNumber extends Validator return false; } + $this->message = ''; return true; } diff --git a/src/Appwrite/Auth/Validator/Password.php b/src/Appwrite/Auth/Validator/Password.php index bfe5577889..913701f7a3 100644 --- a/src/Appwrite/Auth/Validator/Password.php +++ b/src/Appwrite/Auth/Validator/Password.php @@ -2,7 +2,7 @@ namespace Appwrite\Auth\Validator; -use Utopia\Validator; +use Utopia\Http\Validator; /** * Password. diff --git a/src/Appwrite/Auth/Validator/Phone.php b/src/Appwrite/Auth/Validator/Phone.php index 26aa687278..d5f6df60c8 100644 --- a/src/Appwrite/Auth/Validator/Phone.php +++ b/src/Appwrite/Auth/Validator/Phone.php @@ -2,7 +2,7 @@ namespace Appwrite\Auth\Validator; -use Utopia\Validator; +use Utopia\Http\Validator; /** * Phone. diff --git a/src/Appwrite/Event/Func.php b/src/Appwrite/Event/Func.php index 0cbaf17b60..2fbdb7e785 100644 --- a/src/Appwrite/Event/Func.php +++ b/src/Appwrite/Event/Func.php @@ -175,9 +175,9 @@ class Func extends Event * * @return string */ - public function getData(): string + public function getBody(): string { - return $this->data; + return $this->body; } /** diff --git a/src/Appwrite/Event/Validator/Event.php b/src/Appwrite/Event/Validator/Event.php index a3605e4df5..2d46bd7727 100644 --- a/src/Appwrite/Event/Validator/Event.php +++ b/src/Appwrite/Event/Validator/Event.php @@ -3,7 +3,7 @@ namespace Appwrite\Event\Validator; use Utopia\Config\Config; -use Utopia\Validator; +use Utopia\Http\Validator; class Event extends Validator { diff --git a/src/Appwrite/Functions/Validator/Headers.php b/src/Appwrite/Functions/Validator/Headers.php index 04003d535b..4c30a98045 100644 --- a/src/Appwrite/Functions/Validator/Headers.php +++ b/src/Appwrite/Functions/Validator/Headers.php @@ -2,7 +2,7 @@ namespace Appwrite\Functions\Validator; -use Utopia\Validator; +use Utopia\Http\Validator; /** * Headers. diff --git a/src/Appwrite/Functions/Validator/Payload.php b/src/Appwrite/Functions/Validator/Payload.php index acb461eabd..3b2ff4b918 100644 --- a/src/Appwrite/Functions/Validator/Payload.php +++ b/src/Appwrite/Functions/Validator/Payload.php @@ -2,7 +2,7 @@ namespace Appwrite\Functions\Validator; -use Utopia\Validator\Text; +use Utopia\Http\Validator\Text; class Payload extends Text { diff --git a/src/Appwrite/Functions/Validator/RuntimeSpecification.php b/src/Appwrite/Functions/Validator/RuntimeSpecification.php index 22311838f6..fa6efe90a4 100644 --- a/src/Appwrite/Functions/Validator/RuntimeSpecification.php +++ b/src/Appwrite/Functions/Validator/RuntimeSpecification.php @@ -2,7 +2,7 @@ namespace Appwrite\Functions\Validator; -use Utopia\Validator; +use Utopia\Http\Validator; class RuntimeSpecification extends Validator { diff --git a/src/Appwrite/GraphQL/Resolvers.php b/src/Appwrite/GraphQL/Resolvers.php index 8bc72af2f8..49d7c421f7 100644 --- a/src/Appwrite/GraphQL/Resolvers.php +++ b/src/Appwrite/GraphQL/Resolvers.php @@ -6,9 +6,12 @@ use Appwrite\GraphQL\Exception as GQLException; use Appwrite\Promises\Swoole; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; -use Utopia\App; +use Utopia\DI\Container; use Utopia\Exception; -use Utopia\Route; +use Utopia\Http\Http; +use Utopia\Http\Request as UtopiaHttpRequest; +use Utopia\Http\Response as UtopiaHttpResponse; +use Utopia\Http\Route; use Utopia\System\System; class Resolvers @@ -16,24 +19,21 @@ class Resolvers /** * Create a resolver for a given API {@see Route}. * - * @param App $utopia + * @param Http $http * @param ?Route $route * @return callable */ - public static function api( - App $utopia, + public function api( + Http $http, ?Route $route, + UtopiaHttpRequest $request, + UtopiaHttpResponse $response, + Container $container, ): callable { - return static fn ($type, $args, $context, $info) => new Swoole( - function (callable $resolve, callable $reject) use ($utopia, $route, $args, $context, $info) { - /** @var App $utopia */ - /** @var Response $response */ - /** @var Request $request */ - - $utopia = $utopia->getResource('utopia:graphql', true); - $request = $utopia->getResource('request', true); - $response = $utopia->getResource('response', true); + $resolver = $this; + return fn ($type, $args, $context, $info) => new Swoole( + function (callable $resolve, callable $reject) use ($http, $route, $args, $context, $container, $info, $request, $response, $resolver) { $path = $route->getPath(); foreach ($args as $key => $value) { if (\str_contains($path, '/:' . $key)) { @@ -46,14 +46,13 @@ class Resolvers switch ($route->getMethod()) { case 'GET': - $request->setQueryString($args); + $request->setQuery($args); break; default: $request->setPayload($args); break; } - - self::resolve($utopia, $request, $response, $resolve, $reject); + $resolver->resolve($http, $request, $response, $container, $resolve, $reject); } ); } @@ -61,20 +60,20 @@ class Resolvers /** * Create a resolver for a document in a specified database and collection with a specific method type. * - * @param App $utopia + * @param Http $http * @param string $databaseId * @param string $collectionId * @param string $methodType * @return callable */ - public static function document( - App $utopia, + public function document( + Http $http, string $databaseId, string $collectionId, string $methodType, ): callable { return [self::class, 'document' . \ucfirst($methodType)]( - $utopia, + $http, $databaseId, $collectionId ); @@ -83,28 +82,28 @@ class Resolvers /** * Create a resolver for getting a document in a specified database and collection. * - * @param App $utopia + * @param Http $http * @param string $databaseId * @param string $collectionId * @param callable $url * @return callable */ - public static function documentGet( - App $utopia, + public function documentGet( + Http $http, string $databaseId, string $collectionId, callable $url, + UtopiaHttpRequest $request, + UtopiaHttpResponse $response, + Container $container, ): callable { - return static fn ($type, $args, $context, $info) => new Swoole( - function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $type, $args) { - $utopia = $utopia->getResource('utopia:graphql', true); - $request = $utopia->getResource('request', true); - $response = $utopia->getResource('response', true); - + $resolver = $this; + return fn ($type, $args, $context, $info) => new Swoole( + function (callable $resolve, callable $reject) use ($http, $databaseId, $collectionId, $url, $type, $args, $container, $request, $response, $resolver) { $request->setMethod('GET'); $request->setURI($url($databaseId, $collectionId, $args)); - self::resolve($utopia, $request, $response, $resolve, $reject); + $resolver->resolve($http, $request, $response, $container, $resolve, $reject); } ); } @@ -112,35 +111,35 @@ class Resolvers /** * Create a resolver for listing documents in a specified database and collection. * - * @param App $utopia + * @param Http $http * @param string $databaseId * @param string $collectionId * @param callable $url * @param callable $params * @return callable */ - public static function documentList( - App $utopia, + public function documentList( + Http $http, string $databaseId, string $collectionId, callable $url, callable $params, + UtopiaHttpRequest $request, + UtopiaHttpResponse $response, + Container $container, ): callable { - return static fn ($type, $args, $context, $info) => new Swoole( - function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $type, $args) { - $utopia = $utopia->getResource('utopia:graphql', true); - $request = $utopia->getResource('request', true); - $response = $utopia->getResource('response', true); - + $resolver = $this; + return fn ($type, $args, $context, $info) => new Swoole( + function (callable $resolve, callable $reject) use ($http, $databaseId, $collectionId, $url, $params, $type, $args, $container, $request, $response, $resolver) { $request->setMethod('GET'); $request->setURI($url($databaseId, $collectionId, $args)); - $request->setQueryString($params($databaseId, $collectionId, $args)); + $request->setQuery($params($databaseId, $collectionId, $args)); $beforeResolve = function ($payload) { return $payload['documents']; }; - self::resolve($utopia, $request, $response, $resolve, $reject, $beforeResolve); + $resolver->resolve($http, $request, $response, $container, $resolve, $reject, $beforeResolve); } ); } @@ -148,31 +147,31 @@ class Resolvers /** * Create a resolver for creating a document in a specified database and collection. * - * @param App $utopia + * @param Http $http * @param string $databaseId * @param string $collectionId * @param callable $url * @param callable $params * @return callable */ - public static function documentCreate( - App $utopia, + public function documentCreate( + Http $http, string $databaseId, string $collectionId, callable $url, callable $params, + UtopiaHttpRequest $request, + UtopiaHttpResponse $response, + Container $container, ): callable { - return static fn ($type, $args, $context, $info) => new Swoole( - function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $type, $args) { - $utopia = $utopia->getResource('utopia:graphql', true); - $request = $utopia->getResource('request', true); - $response = $utopia->getResource('response', true); - + $resolver = $this; + return fn ($type, $args, $context, $info) => new Swoole( + function (callable $resolve, callable $reject) use ($http, $databaseId, $collectionId, $url, $params, $type, $args, $container, $request, $response, $resolver) { $request->setMethod('POST'); $request->setURI($url($databaseId, $collectionId, $args)); $request->setPayload($params($databaseId, $collectionId, $args)); - self::resolve($utopia, $request, $response, $resolve, $reject); + $resolver->resolve($http, $request, $response, $container, $resolve, $reject); } ); } @@ -180,31 +179,31 @@ class Resolvers /** * Create a resolver for updating a document in a specified database and collection. * - * @param App $utopia + * @param Http $http * @param string $databaseId * @param string $collectionId * @param callable $url * @param callable $params * @return callable */ - public static function documentUpdate( - App $utopia, + public function documentUpdate( + Http $http, string $databaseId, string $collectionId, callable $url, callable $params, + UtopiaHttpRequest $request, + UtopiaHttpResponse $response, + Container $container, ): callable { - return static fn ($type, $args, $context, $info) => new Swoole( - function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $type, $args) { - $utopia = $utopia->getResource('utopia:graphql', true); - $request = $utopia->getResource('request', true); - $response = $utopia->getResource('response', true); - + $resolver = $this; + return fn ($type, $args, $context, $info) => new Swoole( + function (callable $resolve, callable $reject) use ($http, $databaseId, $collectionId, $url, $params, $type, $args, $container, $request, $response, $resolver) { $request->setMethod('PATCH'); $request->setURI($url($databaseId, $collectionId, $args)); $request->setPayload($params($databaseId, $collectionId, $args)); - self::resolve($utopia, $request, $response, $resolve, $reject); + $resolver->resolve($http, $request, $response, $container, $resolve, $reject); } ); } @@ -212,34 +211,34 @@ class Resolvers /** * Create a resolver for deleting a document in a specified database and collection. * - * @param App $utopia + * @param Http $http * @param string $databaseId * @param string $collectionId * @param callable $url * @return callable */ - public static function documentDelete( - App $utopia, + public function documentDelete( + Http $http, string $databaseId, string $collectionId, callable $url, + UtopiaHttpRequest $request, + UtopiaHttpResponse $response, + Container $container, ): callable { - return static fn ($type, $args, $context, $info) => new Swoole( - function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $type, $args) { - $utopia = $utopia->getResource('utopia:graphql', true); - $request = $utopia->getResource('request', true); - $response = $utopia->getResource('response', true); - + $resolver = $this; + return fn ($type, $args, $context, $info) => new Swoole( + function (callable $resolve, callable $reject) use ($http, $databaseId, $collectionId, $url, $type, $args, $container, $request, $response, $resolver) { $request->setMethod('DELETE'); $request->setURI($url($databaseId, $collectionId, $args)); - self::resolve($utopia, $request, $response, $resolve, $reject); + $resolver->resolve($http, $request, $response, $container, $resolve, $reject); } ); } /** - * @param App $utopia + * @param Http $http * @param Request $request * @param Response $response * @param callable $resolve @@ -249,10 +248,11 @@ class Resolvers * @return void * @throws Exception */ - private static function resolve( - App $utopia, + private function resolve( + Http $http, Request $request, Response $response, + Container $context, callable $resolve, callable $reject, ?callable $beforeResolve = null, @@ -263,14 +263,16 @@ class Resolvers $request->removeHeader('content-type'); } - $request = clone $request; - $utopia->setResource('request', static fn () => $request); $response->setContentType(Response::CONTENT_TYPE_NULL); try { - $route = $utopia->match($request, fresh: true); + $context + ->refresh('cache') + ->refresh('dbForProject') + ->refresh('dbForConsole') + ->refresh('getProjectDb'); - $utopia->execute($route, $request, $response); + $http->run(clone $context); } catch (\Throwable $e) { if ($beforeReject) { $e = $beforeReject($e); @@ -285,10 +287,12 @@ class Resolvers if ($beforeReject) { $payload = $beforeReject($payload); } - $reject(new GQLException( - message: $payload['message'], - code: $response->getStatusCode() - )); + $reject( + new GQLException( + message: $payload['message'], + code: $response->getStatusCode() + ) + ); return; } diff --git a/src/Appwrite/GraphQL/Schema.php b/src/Appwrite/GraphQL/Schema.php index 833ea9d032..2b05f08aee 100644 --- a/src/Appwrite/GraphQL/Schema.php +++ b/src/Appwrite/GraphQL/Schema.php @@ -3,54 +3,63 @@ namespace Appwrite\GraphQL; use Appwrite\GraphQL\Types\Mapper; +use Appwrite\Utopia\Response\Models; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Schema as GQLSchema; -use Utopia\App; +use Utopia\DI\Container; use Utopia\Exception; -use Utopia\Route; +use Utopia\Http\Adapter\Swoole\Response as UtopiaSwooleResponse; +use Utopia\Http\Http; +use Utopia\Http\Request; +use Utopia\Http\Response as UtopiaHttpResponse; +use Utopia\Http\Route; class Schema { - protected static ?GQLSchema $schema = null; - protected static array $dirty = []; + protected ?GQLSchema $schema = null; + protected array $dirty = []; /** * - * @param App $utopia - * @param callable $complexity Function to calculate complexity - * @param callable $attributes Function to get attributes - * @param array $urls Array of functions to get urls for specific method types - * @param array $params Array of functions to build parameters for specific method types + * @param Http $http + * @param callable $complexity Function to calculate complexity + * @param callable $attributes Function to get attributes + * @param array $urls Array of functions to get urls for specific method types + * @param array $params Array of functions to build parameters for specific method types * @return GQLSchema * @throws Exception */ - public static function build( - App $utopia, + public function build( + Http $http, + Request $request, + UtopiaHttpResponse $response, + Container $container, callable $complexity, callable $attributes, array $urls, array $params, ): GQLSchema { - App::setResource('utopia:graphql', static function () use ($utopia) { - return $utopia; - }); - - if (!empty(self::$schema)) { - return self::$schema; + if (!empty($this->schema)) { + return $this->schema; } - $api = static::api( - $utopia, + $api = $this->api( + $http, + $request, + $response, + $container, $complexity ); - //$collections = static::collections( - // $utopia, - // $complexity, - // $attributes, - // $urls, - // $params, - //); + // $collections = $this->collections( + // $http, + // $complexity, + // $request, + // $response, + // $attributes, + // $urls, + // $params, + // ); $queries = \array_merge_recursive( $api['query'], @@ -64,7 +73,7 @@ class Schema \ksort($queries); \ksort($mutations); - return static::$schema = new GQLSchema([ + return $this->schema = new GQLSchema([ 'query' => new ObjectType([ 'name' => 'Query', 'fields' => $queries @@ -80,21 +89,23 @@ class Schema * This function iterates all API routes and builds a GraphQL * schema defining types and resolvers for all response models. * - * @param App $utopia + * @param Http $http + * @param Request $request + * @param UtopiaSwooleResponse $response * @param callable $complexity * @return array - * @throws Exception + * @throws \Exception */ - protected static function api(App $utopia, callable $complexity): array + protected function api(Http $http, Request $request, UtopiaHttpResponse $response, Container $container, callable $complexity): array { - Mapper::init($utopia - ->getResource('response') - ->getModels()); + Mapper::init(Models::getModels()); + + $mapper = new Mapper(); $queries = []; $mutations = []; - foreach ($utopia->getRoutes() as $routes) { + foreach ($http->getRoutes() as $routes) { foreach ($routes as $route) { /** @var Route $route */ @@ -106,7 +117,7 @@ class Schema continue; } - foreach (Mapper::route($utopia, $route, $complexity) as $field) { + foreach ($mapper->route($http, $route, $request, $response, $container, $complexity) as $field) { switch ($route->getMethod()) { case 'GET': $queries[$name] = $field; @@ -134,7 +145,7 @@ class Schema * Iterates all of a projects attributes and builds GraphQL * queries and mutations for the collections they make up. * - * @param App $utopia + * @param Http $http * @param callable $complexity * @param callable $attributes * @param array $urls @@ -143,7 +154,7 @@ class Schema * @throws \Exception */ protected static function collections( - App $utopia, + Http $http, callable $complexity, callable $attributes, array $urls, @@ -194,36 +205,36 @@ class Schema $queryFields[$collectionId . 'Get'] = [ 'type' => $objectType, 'args' => Mapper::args('id'), - 'resolve' => Resolvers::documentGet( - $utopia, + /*'resolve' => Resolvers::documentGet( + $http, $databaseId, $collectionId, $urls['get'], - ) + )*/ ]; $queryFields[$collectionId . 'List'] = [ 'type' => Type::listOf($objectType), 'args' => Mapper::args('list'), - 'resolve' => Resolvers::documentList( - $utopia, + /*'resolve' => Resolvers::documentList( + $http, $databaseId, $collectionId, $urls['list'], $params['list'], - ), + ),*/ 'complexity' => $complexity, ]; $mutationFields[$collectionId . 'Create'] = [ 'type' => $objectType, 'args' => $attributes, - 'resolve' => Resolvers::documentCreate( - $utopia, + /*'resolve' => Resolvers::documentCreate( + $http, $databaseId, $collectionId, $urls['create'], $params['create'], - ) + )*/ ]; $mutationFields[$collectionId . 'Update'] = [ 'type' => $objectType, @@ -234,23 +245,23 @@ class Schema $attributes ) ), - 'resolve' => Resolvers::documentUpdate( - $utopia, + /*'resolve' => Resolvers::documentUpdate( + $http, $databaseId, $collectionId, $urls['update'], $params['update'], - ) + )*/ ]; $mutationFields[$collectionId . 'Delete'] = [ 'type' => Mapper::model('none'), 'args' => Mapper::args('id'), - 'resolve' => Resolvers::documentDelete( - $utopia, + /*'resolve' => Resolvers::documentDelete( + $http, $databaseId, $collectionId, $urls['delete'], - ) + )*/ ]; } $offset += $limit; @@ -262,8 +273,8 @@ class Schema ]; } - public static function setDirty(string $projectId): void + public function setDirty(string $projectId): void { - self::$dirty[$projectId] = true; + $this->dirty[$projectId] = true; } } diff --git a/src/Appwrite/GraphQL/Types/Mapper.php b/src/Appwrite/GraphQL/Types/Mapper.php index 36b246b28b..a15c6aa475 100644 --- a/src/Appwrite/GraphQL/Types/Mapper.php +++ b/src/Appwrite/GraphQL/Types/Mapper.php @@ -8,10 +8,13 @@ use Exception; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; -use Utopia\App; -use Utopia\Route; -use Utopia\Validator; -use Utopia\Validator\Nullable; +use Utopia\DI\Container; +use Utopia\Http\Adapter\Swoole\Response as UtopiaSwooleResponse; +use Utopia\Http\Http; +use Utopia\Http\Request; +use Utopia\Http\Route; +use Utopia\Http\Validator; +use Utopia\Http\Validator\Nullable; class Mapper { @@ -74,12 +77,15 @@ class Mapper return self::$args[$key] ?? []; } - public static function route( - App $utopia, + public function route( + Http $http, Route $route, + Request $request, + UtopiaSwooleResponse $response, + Container $container, callable $complexity ): iterable { - foreach (self::$blacklist as $blacklist) { + foreach (static::$blacklist as $blacklist) { if (\str_starts_with($route->getPath(), $blacklist)) { return; } @@ -101,7 +107,7 @@ class Mapper $list = true; } $parameterType = Mapper::param( - $utopia, + $container, $parameter['validator'], !$parameter['optional'], $parameter['injections'] @@ -116,7 +122,7 @@ class Mapper 'type' => $type, 'description' => $description, 'args' => $params, - 'resolve' => Resolvers::api($utopia, $route) + 'resolve' => (new Resolvers())->api($http, $route, $request, $response, $container) ]; if ($list) { @@ -205,7 +211,7 @@ class Mapper /** * Map a {@see Route} parameter to a GraphQL Type * - * @param App $utopia + * @param Container $container * @param Validator|callable $validator * @param bool $required * @param array $injections @@ -213,13 +219,13 @@ class Mapper * @throws Exception */ public static function param( - App $utopia, + Container $container, Validator|callable $validator, bool $required, array $injections ): Type { $validator = \is_callable($validator) - ? \call_user_func_array($validator, $utopia->getResources($injections)) + ? \call_user_func_array($validator, array_map(fn ($injection) => $container->get($injection), $injections)) : $validator; $isNullable = $validator instanceof Nullable; @@ -232,20 +238,20 @@ class Mapper case 'Appwrite\Network\Validator\CNAME': case 'Appwrite\Task\Validator\Cron': case 'Appwrite\Utopia\Database\Validator\CustomId': - case 'Utopia\Validator\Domain': + case 'Utopia\Http\Validator\Domain': case 'Appwrite\Network\Validator\Email': case 'Appwrite\Event\Validator\Event': case 'Appwrite\Event\Validator\FunctionEvent': - case 'Utopia\Validator\HexColor': - case 'Utopia\Validator\Host': - case 'Utopia\Validator\IP': + case 'Utopia\Http\Validator\HexColor': + case 'Utopia\Http\Validator\Host': + case 'Utopia\Http\Validator\IP': case 'Utopia\Database\Validator\Key': - case 'Utopia\Validator\Origin': + case 'Utopia\Http\Validator\Origin': case 'Appwrite\Auth\Validator\Password': - case 'Utopia\Validator\Text': + case 'Utopia\Http\Validator\Text': case 'Utopia\Database\Validator\UID': - case 'Utopia\Validator\URL': - case 'Utopia\Validator\WhiteList': + case 'Utopia\Http\Validator\URL': + case 'Utopia\Http\Validator\WhiteList': default: $type = Type::string(); break; @@ -273,29 +279,29 @@ class Mapper case 'Appwrite\Utopia\Database\Validator\Queries\Variables': $type = Type::listOf(Type::string()); break; - case 'Utopia\Validator\Boolean': + case 'Utopia\Http\Validator\Boolean': $type = Type::boolean(); break; - case 'Utopia\Validator\ArrayList': + case 'Utopia\Http\Validator\ArrayList': $type = Type::listOf(self::param( - $utopia, + $container, $validator->getValidator(), $required, $injections )); break; - case 'Utopia\Validator\Integer': - case 'Utopia\Validator\Numeric': - case 'Utopia\Validator\Range': + case 'Utopia\Http\Validator\Integer': + case 'Utopia\Http\Validator\Numeric': + case 'Utopia\Http\Validator\Range': $type = Type::int(); break; - case 'Utopia\Validator\FloatValidator': + case 'Utopia\Http\Validator\FloatValidator': $type = Type::float(); break; - case 'Utopia\Validator\Assoc': + case 'Utopia\Http\Validator\Assoc': $type = Types::assoc(); break; - case 'Utopia\Validator\JSON': + case 'Utopia\Http\Validator\JSON': $type = Types::json(); break; case 'Utopia\Storage\Validator\File': diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php index cee1b2d263..5b215ec04f 100644 --- a/src/Appwrite/Migration/Migration.php +++ b/src/Appwrite/Migration/Migration.php @@ -98,10 +98,10 @@ abstract class Migration */ protected array $collections; - public function __construct() + public function __construct(Authorization $auth) { - Authorization::disable(); - Authorization::setDefaultStatus(false); + $auth->disable(); + $auth->setDefaultStatus(false); $this->collections = Config::getParam('collections', []); @@ -176,25 +176,23 @@ abstract class Migration Console::log('Migrating Collection ' . $collection['$id'] . ':'); foreach ($this->documentsIterator($collection['$id']) as $document) { - go(function (Document $document, callable $callback) { - if (empty($document->getId()) || empty($document->getCollection())) { - return; - } + if (empty($document->getId()) || empty($document->getCollection())) { + return; + } - $old = $document->getArrayCopy(); - $new = call_user_func($callback, $document); + $old = $document->getArrayCopy(); + $new = call_user_func($callback, $document); - if (is_null($new) || $new->getArrayCopy() == $old) { - return; - } + if (is_null($new) || $new->getArrayCopy() == $old) { + return; + } - try { - $this->projectDB->updateDocument($document->getCollection(), $document->getId(), $document); - } catch (\Throwable $th) { - Console::error('Failed to update document: ' . $th->getMessage()); - return; - } - }, $document, $callback); + try { + $this->projectDB->updateDocument($document->getCollection(), $document->getId(), $document); + } catch (\Throwable $th) { + Console::error('Failed to update document: ' . $th->getMessage()); + return; + } } } } diff --git a/src/Appwrite/Network/Validator/CNAME.php b/src/Appwrite/Network/Validator/CNAME.php index e1ae061c84..e9e2b586a5 100644 --- a/src/Appwrite/Network/Validator/CNAME.php +++ b/src/Appwrite/Network/Validator/CNAME.php @@ -2,7 +2,7 @@ namespace Appwrite\Network\Validator; -use Utopia\Validator; +use Utopia\Http\Validator; class CNAME extends Validator { diff --git a/src/Appwrite/Network/Validator/Email.php b/src/Appwrite/Network/Validator/Email.php index 3209a4aada..bae0ff0bbf 100644 --- a/src/Appwrite/Network/Validator/Email.php +++ b/src/Appwrite/Network/Validator/Email.php @@ -2,14 +2,14 @@ namespace Appwrite\Network\Validator; -use Utopia\Validator; +use Utopia\Http\Validator; /** * Email * * Validate that an variable is a valid email address * - * @package Utopia\Validator + * @package Utopia\Http\Validator */ class Email extends Validator { diff --git a/src/Appwrite/Network/Validator/Origin.php b/src/Appwrite/Network/Validator/Origin.php index d41e9af2ad..573a59b844 100644 --- a/src/Appwrite/Network/Validator/Origin.php +++ b/src/Appwrite/Network/Validator/Origin.php @@ -2,8 +2,8 @@ namespace Appwrite\Network\Validator; -use Utopia\Validator; -use Utopia\Validator\Hostname; +use Utopia\Http\Validator; +use Utopia\Http\Validator\Hostname; class Origin extends Validator { diff --git a/src/Appwrite/Platform/Tasks/Doctor.php b/src/Appwrite/Platform/Tasks/Doctor.php index 82d1ca2d59..3bf9e0d33b 100644 --- a/src/Appwrite/Platform/Tasks/Doctor.php +++ b/src/Appwrite/Platform/Tasks/Doctor.php @@ -3,13 +3,17 @@ namespace Appwrite\Platform\Tasks; use Appwrite\ClamAV\Network; -use Utopia\App; +use Appwrite\Utopia\Queue\Connections; use Utopia\CLI\Console; use Utopia\Config\Config; +use Utopia\Database\Adapter\MariaDB; +use Utopia\Database\Adapter\MySQL; use Utopia\Domains\Domain; use Utopia\DSN\DSN; +use Utopia\Http\Http; use Utopia\Logger\Logger; use Utopia\Platform\Action; +use Utopia\Queue\Connection\Redis; use Utopia\Registry\Registry; use Utopia\Storage\Device\Local; use Utopia\Storage\Storage; @@ -27,10 +31,11 @@ class Doctor extends Action $this ->desc('Validate server health') ->inject('register') - ->callback(fn (Registry $register) => $this->action($register)); + ->inject('connections') + ->callback(fn (Registry $register, Connections $connections) => $this->action($register, $connections)); } - public function action(Registry $register): void + public function action(Registry $register, Connections $connections): void { Console::log(" __ ____ ____ _ _ ____ __ ____ ____ __ __ / _\ ( _ \( _ \/ )( \( _ \( )(_ _)( __) ( )/ \ @@ -125,22 +130,41 @@ class Doctor extends Action //throw $th; } - $pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */ + /** @var array $pools */ + $pools = $register->get('pools'); $configs = [ - 'Console.DB' => Config::getParam('pools-console'), - 'Projects.DB' => Config::getParam('pools-database'), + 'Console.DB' => [ + 'prefix' => 'console', + 'databases' => Config::getParam('pools-console') + ], + 'Database.DB' => [ + 'prefix' => 'database', + 'databases' => Config::getParam('pools-database') + ], ]; + foreach ($configs as $key => $config) { - foreach ($config as $database) { + foreach ($config['databases'] as $database) { try { - $adapter = $pools->get($database)->pop()->getResource(); + $pool = $pools['pools-' . $config['prefix'] . '-' . $database]['pool']; + $dsn = $pools['pools-' . $config['prefix'] . '-' . $database]['dsn']; + + $connection = $pool->get(); + $connections->add($connection, $pool); + $adapter = match ($dsn->getScheme()) { + 'mariadb' => new MariaDB($connection), + 'mysql' => new MySQL($connection), + default => null + }; + $adapter->setDatabase($dsn->getPath()); + if ($adapter->ping()) { - Console::success('🟢 ' . str_pad("{$key}({$database})", 50, '.') . 'connected'); + Console::success('🟢 ' . str_pad("$key({$database})", 50, '.') . 'connected'); } else { - Console::error('🔴 ' . str_pad("{$key}({$database})", 47, '.') . 'disconnected'); + Console::error('🔴 ' . str_pad("$key({$database})", 47, '.') . 'disconnected'); } } catch (\Throwable $th) { Console::error('🔴 ' . str_pad("{$key}.({$database})", 47, '.') . 'disconnected'); @@ -148,25 +172,39 @@ class Doctor extends Action } } - $pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */ + /** @var array $pools */ + $pools = $register->get('pools'); $configs = [ - 'Cache' => Config::getParam('pools-cache'), - 'Queue' => Config::getParam('pools-queue'), - 'PubSub' => Config::getParam('pools-pubsub'), + 'Cache' => [ + 'prefix' => 'cache', + 'databases' => Config::getParam('pools-cache') + ], + 'Queue' => [ + 'prefix' => 'queue', + 'databases' => Config::getParam('pools-queue') + ], + 'PubSub' => [ + 'prefix' => 'pubsub', + 'databases' => Config::getParam('pools-pubsub') + ], ]; - foreach ($configs as $key => $config) { - foreach ($config as $pool) { + foreach ($config['databases'] as $database) { try { - $adapter = $pools->get($pool)->pop()->getResource(); + $pool = $pools['pools-' . $config['prefix'] . '-' . $database]['pool']; + $dsn = $pools['pools-' . $config['prefix'] . '-' . $database]['dsn']; + $connection = $pool->get(); + $connections->add($connection, $pool); + + $adapter = new Redis($dsn->getHost(), $dsn->getPort()); if ($adapter->ping()) { - Console::success('🟢 ' . str_pad("{$key}({$pool})", 50, '.') . 'connected'); + Console::success('🟢 ' . str_pad("{$key}({$database})", 50, '.') . 'connected'); } else { - Console::error('🔴 ' . str_pad("{$key}({$pool})", 47, '.') . 'disconnected'); + Console::error('🔴 ' . str_pad("{$key}({$database})", 47, '.') . 'disconnected'); } } catch (\Throwable $th) { - Console::error('🔴 ' . str_pad("{$key}({$pool})", 47, '.') . 'disconnected'); + Console::error('🔴 ' . str_pad("{$key}({$database})", 47, '.') . 'disconnected'); } } } @@ -258,7 +296,7 @@ class Doctor extends Action } try { - if (App::isProduction()) { + if (Http::isProduction()) { Console::log(''); $version = \json_decode(@\file_get_contents(System::getEnv('_APP_HOME', 'http://localhost') . '/version'), true); diff --git a/src/Appwrite/Platform/Tasks/Install.php b/src/Appwrite/Platform/Tasks/Install.php index 4abd267684..a1a73e6385 100644 --- a/src/Appwrite/Platform/Tasks/Install.php +++ b/src/Appwrite/Platform/Tasks/Install.php @@ -8,9 +8,9 @@ use Appwrite\Docker\Env; use Appwrite\Utopia\View; use Utopia\CLI\Console; use Utopia\Config\Config; +use Utopia\Http\Validator\Boolean; +use Utopia\Http\Validator\Text; use Utopia\Platform\Action; -use Utopia\Validator\Boolean; -use Utopia\Validator\Text; class Install extends Action { @@ -213,8 +213,7 @@ class Install extends Action } $env = ''; - $stdout = ''; - $stderr = ''; + $output = ''; foreach ($input as $key => $value) { if ($value) { @@ -225,13 +224,13 @@ class Install extends Action $exit = 0; if (!$noStart) { Console::log("Running \"docker compose up -d --remove-orphans --renew-anon-volumes\""); - $exit = Console::execute("$env docker compose --project-directory $this->path up -d --remove-orphans --renew-anon-volumes", '', $stdout, $stderr); + $exit = Console::execute("$env docker compose --project-directory $this->path up -d --remove-orphans --renew-anon-volumes", '', $output); } if ($exit !== 0) { $message = 'Failed to install Appwrite dockers'; Console::error($message); - Console::error($stderr); + Console::error($output); Console::exit($exit); } else { $message = 'Appwrite installed successfully'; diff --git a/src/Appwrite/Platform/Tasks/Migrate.php b/src/Appwrite/Platform/Tasks/Migrate.php index dcba59bb1d..55be0b81c2 100644 --- a/src/Appwrite/Platform/Tasks/Migrate.php +++ b/src/Appwrite/Platform/Tasks/Migrate.php @@ -10,10 +10,10 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; +use Utopia\Http\Validator\Text; use Utopia\Platform\Action; use Utopia\Registry\Registry; use Utopia\System\System; -use Utopia\Validator\Text; class Migrate extends Action { @@ -30,12 +30,15 @@ class Migrate extends Action ->desc('Migrate Appwrite to new version') /** @TODO APP_VERSION_STABLE needs to be defined */ ->param('version', APP_VERSION_STABLE, new Text(8), 'Version to migrate to.', true) + ->inject('cache') ->inject('dbForConsole') ->inject('getProjectDB') ->inject('register') - ->callback(function ($version, $dbForConsole, $getProjectDB, Registry $register) { - \Co\run(function () use ($version, $dbForConsole, $getProjectDB, $register) { - $this->action($version, $dbForConsole, $getProjectDB, $register); + ->inject('authorization') + ->inject('console') + ->callback(function ($version, $dbForConsole, $getProjectDB, Registry $register, Authorization $authorization, Document $console) { + \Co\run(function () use ($version, $dbForConsole, $getProjectDB, $register, $authorization, $console) { + $this->action($version, $dbForConsole, $getProjectDB, $register, $authorization, $console); }); }); } @@ -58,13 +61,12 @@ class Migrate extends Action } } - public function action(string $version, Database $dbForConsole, callable $getProjectDB, Registry $register) + public function action(string $version, Database $dbForConsole, callable $getProjectDB, Registry $register, Authorization $auth, Document $console) { - Authorization::disable(); + $auth->disable(); if (!array_key_exists($version, Migration::$versions)) { Console::error("Version {$version} not found."); Console::exit(1); - return; } @@ -77,12 +79,8 @@ class Migrate extends Action 10 ); - $app = new App('UTC'); - Console::success('Starting Data Migration to version ' . $version); - $console = $app->getResource('console'); - $limit = 30; $sum = 30; $offset = 0; @@ -101,7 +99,7 @@ class Migrate extends Action $class = 'Appwrite\\Migration\\Version\\' . Migration::$versions[$version]; /** @var Migration $migration */ - $migration = new $class(); + $migration = new $class($auth, ); while (!empty($projects)) { foreach ($projects as $project) { diff --git a/src/Appwrite/Platform/Tasks/QueueCount.php b/src/Appwrite/Platform/Tasks/QueueCount.php index b02165c1d2..63f829073a 100644 --- a/src/Appwrite/Platform/Tasks/QueueCount.php +++ b/src/Appwrite/Platform/Tasks/QueueCount.php @@ -3,11 +3,11 @@ namespace Appwrite\Platform\Tasks; use Utopia\CLI\Console; +use Utopia\Http\Validator\Text; +use Utopia\Http\Validator\WhiteList; use Utopia\Platform\Action; use Utopia\Queue\Client; use Utopia\Queue\Connection; -use Utopia\Validator\Text; -use Utopia\Validator\WhiteList; class QueueCount extends Action { diff --git a/src/Appwrite/Platform/Tasks/QueueRetry.php b/src/Appwrite/Platform/Tasks/QueueRetry.php index b6139dc177..63f6c8e11e 100644 --- a/src/Appwrite/Platform/Tasks/QueueRetry.php +++ b/src/Appwrite/Platform/Tasks/QueueRetry.php @@ -3,11 +3,11 @@ namespace Appwrite\Platform\Tasks; use Utopia\CLI\Console; +use Utopia\Http\Validator\Text; +use Utopia\Http\Validator\Wildcard; use Utopia\Platform\Action; use Utopia\Queue\Client; use Utopia\Queue\Connection; -use Utopia\Validator\Text; -use Utopia\Validator\Wildcard; class QueueRetry extends Action { diff --git a/src/Appwrite/Platform/Tasks/SSL.php b/src/Appwrite/Platform/Tasks/SSL.php index 5af0cb6cd8..ad4098d9ee 100644 --- a/src/Appwrite/Platform/Tasks/SSL.php +++ b/src/Appwrite/Platform/Tasks/SSL.php @@ -5,10 +5,10 @@ namespace Appwrite\Platform\Tasks; use Appwrite\Event\Certificate; use Utopia\CLI\Console; use Utopia\Database\Document; +use Utopia\Http\Validator\Boolean; +use Utopia\Http\Validator\Hostname; use Utopia\Platform\Action; use Utopia\System\System; -use Utopia\Validator\Boolean; -use Utopia\Validator\Hostname; class SSL extends Action { diff --git a/src/Appwrite/Platform/Tasks/ScheduleBase.php b/src/Appwrite/Platform/Tasks/ScheduleBase.php index 79a05dcd13..8dc037897d 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleBase.php +++ b/src/Appwrite/Platform/Tasks/ScheduleBase.php @@ -2,44 +2,44 @@ namespace Appwrite\Platform\Tasks; +use Appwrite\Utopia\Queue\Connections; use Swoole\Timer; use Utopia\CLI\Console; -use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Exception; use Utopia\Database\Query; use Utopia\Platform\Action; -use Utopia\Pools\Group; use Utopia\System\System; -use function Swoole\Coroutine\run; - abstract class ScheduleBase extends Action { protected const UPDATE_TIMER = 10; //seconds protected const ENQUEUE_TIMER = 60; //seconds protected array $schedules = []; + protected Connections $connections; abstract public static function getName(): string; + abstract public static function getSupportedResource(): string; abstract protected function enqueueResources( - Group $pools, - Database $dbForConsole + array $pools, + callable $getConsoleDB ); public function __construct() { + $this->connections = new Connections(); $type = static::getSupportedResource(); $this ->desc("Execute {$type}s scheduled in Appwrite") ->inject('pools') - ->inject('dbForConsole') + ->inject('getConsoleDB') ->inject('getProjectDB') - ->callback(fn (Group $pools, Database $dbForConsole, callable $getProjectDB) => $this->action($pools, $dbForConsole, $getProjectDB)); + ->callback(fn (array $pools, callable $getConsoleDB, callable $getProjectDB) => $this->action($pools, $getConsoleDB, $getProjectDB)); } /** @@ -47,11 +47,12 @@ abstract class ScheduleBase extends Action * 2. Create timer that sync all changes from 'schedules' collection to local copy. Only reading changes thanks to 'resourceUpdatedAt' attribute * 3. Create timer that prepares coroutines for soon-to-execute schedules. When it's ready, coroutine sleeps until exact time before sending request to worker. */ - public function action(Group $pools, Database $dbForConsole, callable $getProjectDB): void + public function action(array $pools, callable $getConsoleDB, callable $getProjectDB): void { Console::title(\ucfirst(static::getSupportedResource()) . ' scheduler V1'); Console::success(APP_NAME . ' ' . \ucfirst(static::getSupportedResource()) . ' scheduler v1 has started'); + [$_, $_, $dbForConsole] = $getConsoleDB(); /** * Extract only necessary attributes to lower memory used. * @@ -127,76 +128,74 @@ abstract class ScheduleBase extends Action $latestDocument = \end($results); } - $pools->reclaim(); Console::success("{$total} resources were loaded in " . (\microtime(true) - $loadStart) . " seconds"); Console::success("Starting timers at " . DateTime::now()); - run(function () use ($dbForConsole, &$lastSyncUpdate, $getSchedule, $pools) { - /** - * The timer synchronize $schedules copy with database collection. - */ - Timer::tick(static::UPDATE_TIMER * 1000, function () use ($dbForConsole, &$lastSyncUpdate, $getSchedule, $pools) { - $time = DateTime::now(); - $timerStart = \microtime(true); - $limit = 1000; - $sum = $limit; - $total = 0; - $latestDocument = null; + Timer::tick(static::UPDATE_TIMER * 1000, function () use ($getConsoleDB, &$lastSyncUpdate, $getSchedule, $pools) { + [$connection,$pool, $dbForConsole] = $getConsoleDB(); + $connections = new Connections(); + $connections->add($connection, $pool); - Console::log("Sync tick: Running at $time"); + $time = DateTime::now(); + $timerStart = \microtime(true); - while ($sum === $limit) { - $paginationQueries = [Query::limit($limit)]; + $limit = 1000; + $sum = $limit; + $total = 0; + $latestDocument = null; - if ($latestDocument) { - $paginationQueries[] = Query::cursorAfter($latestDocument); - } + Console::log("Sync tick: Running at $time"); - $results = $dbForConsole->find('schedules', \array_merge($paginationQueries, [ - Query::equal('region', [System::getEnv('_APP_REGION', 'default')]), - Query::equal('resourceType', [static::getSupportedResource()]), - Query::greaterThanEqual('resourceUpdatedAt', $lastSyncUpdate), - ])); + while ($sum === $limit) { + $paginationQueries = [Query::limit($limit)]; - $sum = count($results); - $total = $total + $sum; - - foreach ($results as $document) { - $localDocument = $this->schedules[$document->getInternalId()] ?? null; - - // Check if resource has been updated since last sync - $org = $localDocument !== null ? \strtotime($localDocument['resourceUpdatedAt']) : null; - $new = \strtotime($document['resourceUpdatedAt']); - - if (!$document['active']) { - Console::info("Removing: {$document['resourceId']}"); - unset($this->schedules[$document->getInternalId()]); - } elseif ($new !== $org) { - Console::info("Updating: {$document['resourceId']}"); - $this->schedules[$document->getInternalId()] = $getSchedule($document); - } - } - - $latestDocument = \end($results); + if ($latestDocument) { + $paginationQueries[] = Query::cursorAfter($latestDocument); } - $lastSyncUpdate = $time; - $timerEnd = \microtime(true); + $results = $dbForConsole->find('schedules', \array_merge($paginationQueries, [ + Query::equal('region', [System::getEnv('_APP_REGION', 'default')]), + Query::equal('resourceType', [static::getSupportedResource()]), + Query::greaterThanEqual('resourceUpdatedAt', $lastSyncUpdate), + ])); - $pools->reclaim(); + $sum = count($results); + $total = $total + $sum; - Console::log("Sync tick: {$total} schedules were updated in " . ($timerEnd - $timerStart) . " seconds"); - }); + foreach ($results as $document) { + $localDocument = $this->schedules[$document->getInternalId()] ?? null; - Timer::tick( - static::ENQUEUE_TIMER * 1000, - fn () => $this->enqueueResources($pools, $dbForConsole) - ); + // Check if resource has been updated since last sync + $org = $localDocument !== null ? \strtotime($localDocument['resourceUpdatedAt']) : null; + $new = \strtotime($document['resourceUpdatedAt']); - $this->enqueueResources($pools, $dbForConsole); + if (!$document['active']) { + Console::info("Removing: {$document['resourceId']}"); + unset($this->schedules[$document->getInternalId()]); + } elseif ($new !== $org) { + Console::info("Updating: {$document['resourceId']}"); + $this->schedules[$document->getInternalId()] = $getSchedule($document); + } + } + + $latestDocument = \end($results); + } + + $lastSyncUpdate = $time; + $timerEnd = \microtime(true); + + $connections->reclaim(); + Console::log("Sync tick: {$total} schedules were updated in " . ($timerEnd - $timerStart) . " seconds"); }); + + Timer::tick( + static::ENQUEUE_TIMER * 1000, + fn () => $this->enqueueResources($pools, $getConsoleDB) + ); + + $this->enqueueResources($pools, $getConsoleDB); } } diff --git a/src/Appwrite/Platform/Tasks/ScheduleExecutions.php b/src/Appwrite/Platform/Tasks/ScheduleExecutions.php index 682d796585..b67a892b2e 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleExecutions.php +++ b/src/Appwrite/Platform/Tasks/ScheduleExecutions.php @@ -4,8 +4,7 @@ namespace Appwrite\Platform\Tasks; use Appwrite\Event\Func; use Swoole\Coroutine as Co; -use Utopia\Database\Database; -use Utopia\Pools\Group; +use Utopia\Queue\Connection\Redis; class ScheduleExecutions extends ScheduleBase { @@ -22,11 +21,16 @@ class ScheduleExecutions extends ScheduleBase return 'execution'; } - protected function enqueueResources(Group $pools, Database $dbForConsole): void + protected function enqueueResources(array $pools, callable $getConsoleDB): void { - $queue = $pools->get('queue')->pop(); - $connection = $queue->getResource(); - $queueForFunctions = new Func($connection); + [$connection,$pool, $dbForConsole] = $getConsoleDB(); + $this->connections->add($connection, $pool); + + $queuePool = $pools['pools-queue-queue']['pool']; + $queueConnection = $queuePool->get(); + $this->connections->add($queueConnection, $queuePool); + + $queueForFunctions = new Func(new Redis($queueConnection)); $intervalEnd = (new \DateTime())->modify('+' . self::ENQUEUE_TIMER . ' seconds'); foreach ($this->schedules as $schedule) { @@ -52,7 +56,7 @@ class ScheduleExecutions extends ScheduleBase $delay = $scheduledAt->getTimestamp() - (new \DateTime())->getTimestamp(); - \go(function () use ($queueForFunctions, $schedule, $delay, $data) { + \go(function () use ($queueForFunctions, $schedule, $delay, $data, $dbForConsole) { Co::sleep($delay); $queueForFunctions->setType('schedule') @@ -67,16 +71,16 @@ class ScheduleExecutions extends ScheduleBase ->setProject($schedule['project']) ->setUserId($data['userId'] ?? '') ->trigger(); - }); - $dbForConsole->deleteDocument( - 'schedules', - $schedule['$id'], - ); + $dbForConsole->deleteDocument( + 'schedules', + $schedule['$id'], + ); + }); unset($this->schedules[$schedule['$internalId']]); } - $queue->reclaim(); + $this->connections->reclaim(); } } diff --git a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php index e2c278714f..450551400e 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php +++ b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php @@ -5,9 +5,8 @@ namespace Appwrite\Platform\Tasks; use Appwrite\Event\Func; use Cron\CronExpression; use Utopia\CLI\Console; -use Utopia\Database\Database; use Utopia\Database\DateTime; -use Utopia\Pools\Group; +use Utopia\Queue\Connection\Redis; class ScheduleFunctions extends ScheduleBase { @@ -26,7 +25,7 @@ class ScheduleFunctions extends ScheduleBase return 'function'; } - protected function enqueueResources(Group $pools, Database $dbForConsole): void + protected function enqueueResources(array $pools, callable $getConsoleDB): void { $timerStart = \microtime(true); $time = DateTime::now(); @@ -68,8 +67,11 @@ class ScheduleFunctions extends ScheduleBase \go(function () use ($delay, $scheduleKeys, $pools) { \sleep($delay); // in seconds - $queue = $pools->get('queue')->pop(); - $connection = $queue->getResource(); + $pool = $pools['pools-queue-queue']['pool']; + $connection = $pool->get(); + $this->connections->add($connection, $pool); + + $queueConnection = new Redis($connection); foreach ($scheduleKeys as $scheduleKey) { // Ensure schedule was not deleted @@ -79,7 +81,7 @@ class ScheduleFunctions extends ScheduleBase $schedule = $this->schedules[$scheduleKey]; - $queueForFunctions = new Func($connection); + $queueForFunctions = new Func($queueConnection); $queueForFunctions ->setType('schedule') @@ -90,7 +92,8 @@ class ScheduleFunctions extends ScheduleBase ->trigger(); } - $queue->reclaim(); + $this->connections->reclaim(); + // $queue->reclaim(); // TODO: Do in try/catch/finally, or add to connectons resource }); } diff --git a/src/Appwrite/Platform/Tasks/ScheduleMessages.php b/src/Appwrite/Platform/Tasks/ScheduleMessages.php index 167f1282ed..72e1a5f786 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleMessages.php +++ b/src/Appwrite/Platform/Tasks/ScheduleMessages.php @@ -3,8 +3,7 @@ namespace Appwrite\Platform\Tasks; use Appwrite\Event\Messaging; -use Utopia\Database\Database; -use Utopia\Pools\Group; +use Utopia\Queue\Connection\Redis; class ScheduleMessages extends ScheduleBase { @@ -21,8 +20,11 @@ class ScheduleMessages extends ScheduleBase return 'message'; } - protected function enqueueResources(Group $pools, Database $dbForConsole): void + protected function enqueueResources(array $pools, callable $getConsoleDB): void { + [$connection,$pool, $dbForConsole] = $getConsoleDB(); + $this->connections->add($connection, $pool); + foreach ($this->schedules as $schedule) { if (!$schedule['active']) { continue; @@ -35,10 +37,15 @@ class ScheduleMessages extends ScheduleBase continue; } - \go(function () use ($schedule, $pools, $dbForConsole) { - $queue = $pools->get('queue')->pop(); - $connection = $queue->getResource(); - $queueForMessaging = new Messaging($connection); + \go(function () use ($now, $schedule, $pools, $dbForConsole) { + $pool = $pools['pools-queue-queue']['pool']; + $dsn = $pools['pools-queue-queue']['dsn']; + $connection = $pool->get(); + $this->connections->add($connection, $pool); + + $queueConnection = new Redis($dsn->getHost(), $dsn->getPort()); + + $queueForMessaging = new Messaging($queueConnection); $queueForMessaging ->setType(MESSAGE_SEND_TYPE_EXTERNAL) @@ -51,8 +58,7 @@ class ScheduleMessages extends ScheduleBase $schedule['$id'], ); - $queue->reclaim(); - + $this->connections->reclaim(); unset($this->schedules[$schedule['$internalId']]); }); } diff --git a/src/Appwrite/Platform/Tasks/Specs.php b/src/Appwrite/Platform/Tasks/Specs.php index e171f2f405..6e69798d98 100644 --- a/src/Appwrite/Platform/Tasks/Specs.php +++ b/src/Appwrite/Platform/Tasks/Specs.php @@ -6,21 +6,26 @@ use Appwrite\Specification\Format\OpenAPI3; use Appwrite\Specification\Format\Swagger2; use Appwrite\Specification\Specification; use Appwrite\Utopia\Response; +use Appwrite\Utopia\Response\Models; use Exception; -use Swoole\Http\Response as HttpResponse; -use Utopia\App; +use Swoole\Http\Request as SwooleHttpRequest; +use Swoole\Http\Response as SwooleHttpResponse; use Utopia\Cache\Adapter\None; use Utopia\Cache\Cache; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Database\Adapter\MySQL; use Utopia\Database\Database; +use Utopia\DI\Container; +use Utopia\DI\Dependency; +use Utopia\Http\Adapter\FPM\Server; +use Utopia\Http\Adapter\Swoole\Request; +use Utopia\Http\Adapter\Swoole\Response as HttpResponse; +use Utopia\Http\Http; +use Utopia\Http\Validator\Text; +use Utopia\Http\Validator\WhiteList; use Utopia\Platform\Action; -use Utopia\Registry\Registry; -use Utopia\Request; use Utopia\System\System; -use Utopia\Validator\Text; -use Utopia\Validator\WhiteList; class Specs extends Action { @@ -35,21 +40,32 @@ class Specs extends Action ->desc('Generate Appwrite API specifications') ->param('version', 'latest', new Text(16), 'Spec version', true) ->param('mode', 'normal', new WhiteList(['normal', 'mocks']), 'Spec Mode', true) - ->inject('register') - ->callback(fn (string $version, string $mode, Registry $register) => $this->action($version, $mode, $register)); + ->inject('context') + ->callback(fn (string $version, string $mode, Container $context) => $this->action($version, $mode, $context)); } - public function action(string $version, string $mode, Registry $register): void + public function action(string $version, string $mode, Container $container): void { - $appRoutes = App::getRoutes(); - $response = new Response(new HttpResponse()); + $appRoutes = Http::getRoutes(); + $response = new Response(new HttpResponse(new SwooleHttpResponse())); $mocks = ($mode === 'mocks'); + $requestDependency = new Dependency(); + $responseDependency = new Dependency(); + $dbForConsole = new Dependency(); + $dbForProject = new Dependency(); + // Mock dependencies - App::setResource('request', fn () => new Request()); - App::setResource('response', fn () => $response); - App::setResource('dbForConsole', fn () => new Database(new MySQL(''), new Cache(new None()))); - App::setResource('dbForProject', fn () => new Database(new MySQL(''), new Cache(new None()))); + $requestDependency->setName('request')->setCallback(fn () => new Request(new SwooleHttpRequest())); + $responseDependency->setName('response')->setCallback(fn () => $response); + $dbForConsole->setName('dbForConsole')->setCallback(fn () => new Database(new MySQL(''), new Cache(new None()))); + $dbForProject->setName('dbForProject')->setCallback(fn () => new Database(new MySQL(''), new Cache(new None()))); + + $container + ->set($requestDependency) + ->set($responseDependency) + ->set($dbForProject) + ->set($dbForConsole); $platforms = [ 'client' => APP_PLATFORM_CLIENT, @@ -241,7 +257,7 @@ class Specs extends Action ]; } - $models = $response->getModels(); + $models = Models::getModels(); foreach ($models as $key => $value) { if ($platform !== APP_PLATFORM_CONSOLE && !$value->isPublic()) { @@ -249,7 +265,7 @@ class Specs extends Action } } - $arguments = [new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0]; + $arguments = [new Http(new Server(), $container, 'UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0]; foreach (['swagger2', 'open-api3'] as $format) { $formatInstance = match ($format) { 'swagger2' => new Swagger2(...$arguments), diff --git a/src/Appwrite/Platform/Tasks/Upgrade.php b/src/Appwrite/Platform/Tasks/Upgrade.php index 341ce42fc4..608b924c06 100644 --- a/src/Appwrite/Platform/Tasks/Upgrade.php +++ b/src/Appwrite/Platform/Tasks/Upgrade.php @@ -3,8 +3,8 @@ namespace Appwrite\Platform\Tasks; use Utopia\CLI\Console; -use Utopia\Validator\Boolean; -use Utopia\Validator\Text; +use Utopia\Http\Validator\Boolean; +use Utopia\Http\Validator\Text; class Upgrade extends Install { diff --git a/src/Appwrite/Platform/Workers/Audits.php b/src/Appwrite/Platform/Workers/Audits.php index 86ca59d3fd..d21f1c267d 100644 --- a/src/Appwrite/Platform/Workers/Audits.php +++ b/src/Appwrite/Platform/Workers/Audits.php @@ -9,6 +9,7 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Authorization; use Utopia\Database\Exception\Structure; +use Utopia\Database\Validator\Authorization as ValidatorAuthorization; use Utopia\Platform\Action; use Utopia\Queue\Message; @@ -28,7 +29,8 @@ class Audits extends Action ->desc('Audits worker') ->inject('message') ->inject('dbForProject') - ->callback(fn ($message, $dbForProject) => $this->action($message, $dbForProject)); + ->inject('authorization') + ->callback(fn ($message, $dbForProject, ValidatorAuthorization $authorization) => $this->action($message, $dbForProject, $authorization)); } @@ -41,7 +43,7 @@ class Audits extends Action * @throws Authorization * @throws Structure */ - public function action(Message $message, Database $dbForProject): void + public function action(Message $message, Database $dbForProject, ValidatorAuthorization $auth): void { $payload = $message->getPayload() ?? []; diff --git a/src/Appwrite/Platform/Workers/Builds.php b/src/Appwrite/Platform/Workers/Builds.php index 5dd2f7f886..9274e7430c 100644 --- a/src/Appwrite/Platform/Workers/Builds.php +++ b/src/Appwrite/Platform/Workers/Builds.php @@ -54,7 +54,8 @@ class Builds extends Action ->inject('dbForProject') ->inject('deviceForFunctions') ->inject('log') - ->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)); + ->inject('authorization') + ->callback(fn ($message, Database $dbForConsole, Event $queueForEvents, Func $queueForFunctions, Usage $usage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, Log $log, Authorization $authorization) => $this->action($message, $dbForConsole, $queueForEvents, $queueForFunctions, $usage, $cache, $dbForProject, $deviceForFunctions, $log, $authorization)); } /** @@ -67,10 +68,11 @@ class Builds extends Action * @param Database $dbForProject * @param Device $deviceForFunctions * @param Log $log + * @param Authorization $auth * @return void * @throws \Utopia\Database\Exception */ - public function action(Message $message, Database $dbForConsole, Event $queueForEvents, Func $queueForFunctions, Usage $queueForUsage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, Log $log): void + public function action(Message $message, Database $dbForConsole, Event $queueForEvents, Func $queueForFunctions, Usage $queueForUsage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, Log $log, Authorization $auth): void { $payload = $message->getPayload() ?? []; @@ -92,7 +94,7 @@ class Builds extends Action case BUILD_TYPE_RETRY: Console::info('Creating build for deployment: ' . $deployment->getId()); $github = new GitHub($cache); - $this->buildDeployment($deviceForFunctions, $queueForFunctions, $queueForEvents, $queueForUsage, $dbForConsole, $dbForProject, $github, $project, $resource, $deployment, $template, $log); + $this->buildDeployment($deviceForFunctions, $queueForFunctions, $queueForEvents, $queueForUsage, $dbForConsole, $dbForProject, $github, $project, $resource, $deployment, $template, $log, $auth); break; default: @@ -113,11 +115,12 @@ class Builds extends Action * @param Document $deployment * @param Document $template * @param Log $log + * @param Authorization $auth * @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 $function, 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, Authorization $auth): void { $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST')); @@ -221,20 +224,18 @@ class Builds extends Action $templateRootDirectory = \ltrim($templateRootDirectory, '/'); if (!empty($templateRepositoryName) && !empty($templateOwnerName) && !empty($templateVersion)) { - $stdout = ''; - $stderr = ''; - + $output = ''; // Clone template repo $tmpTemplateDirectory = '/tmp/builds/' . $buildId . '-template'; $gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateVersion, GitHub::CLONE_TYPE_TAG, $tmpTemplateDirectory, $templateRootDirectory); - $exit = Console::execute($gitCloneCommandForTemplate, '', $stdout, $stderr); + $exit = Console::execute($gitCloneCommandForTemplate, '', $output); if ($exit !== 0) { - throw new \Exception('Unable to clone code repository: ' . $stderr); + throw new \Exception('Unable to clone code repository: ' . $output); } // Ensure directories - Console::execute('mkdir -p ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory), '', $stdout, $stderr); + Console::execute('mkdir -p ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory), '', $output); $tmpPathFile = $tmpTemplateDirectory . '/code.tar.gz'; @@ -245,7 +246,7 @@ class Builds extends Action } $tarParamDirectory = \escapeshellarg($tmpTemplateDirectory . (empty($templateRootDirectory) ? '' : '/' . $templateRootDirectory)); - Console::execute('tar --exclude code.tar.gz -czf ' . \escapeshellarg($tmpPathFile) . ' -C ' . \escapeshellcmd($tarParamDirectory) . ' .', '', $stdout, $stderr); // TODO: Replace escapeshellcmd with escapeshellarg if we find a way that doesnt break syntax + Console::execute('tar --exclude code.tar.gz -czf ' . \escapeshellarg($tmpPathFile) . ' -C ' . \escapeshellcmd($tarParamDirectory) . ' .', '', $output); // TODO: Replace escapeshellcmd with escapeshellarg if we find a way that doesnt break syntax $source = $deviceForFunctions->getPath($deployment->getId() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION)); $result = $localDevice->transfer($tmpPathFile, $source, $deviceForFunctions); @@ -254,7 +255,7 @@ class Builds extends Action throw new \Exception("Unable to move file"); } - Console::execute('rm -rf ' . \escapeshellarg($tmpTemplateDirectory), '', $stdout, $stderr); + Console::execute('rm -rf ' . \escapeshellarg($tmpTemplateDirectory), '', $output); $directorySize = $deviceForFunctions->getFileSize($source); $build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttribute('source', $source)); @@ -276,6 +277,7 @@ class Builds extends Action $branchName = $deployment->getAttribute('providerBranch'); $commitHash = $deployment->getAttribute('providerCommitHash', ''); + $output = ''; $cloneVersion = $branchName; $cloneType = GitHub::CLONE_TYPE_BRANCH; @@ -283,22 +285,18 @@ class Builds extends Action $cloneVersion = $commitHash; $cloneType = GitHub::CLONE_TYPE_COMMIT; } - $gitCloneCommand = $github->generateCloneCommand($cloneOwner, $cloneRepository, $cloneVersion, $cloneType, $tmpDirectory, $rootDirectory); - $stdout = ''; - $stderr = ''; - - Console::execute('mkdir -p ' . \escapeshellarg('/tmp/builds/' . $buildId), '', $stdout, $stderr); + Console::execute('mkdir -p ' . \escapeshellarg('/tmp/builds/' . $buildId), '', $output); if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') { Console::info('Build has been canceled'); return; } - $exit = Console::execute($gitCloneCommand, '', $stdout, $stderr); + $exit = Console::execute($gitCloneCommand, '', $output); if ($exit !== 0) { - throw new \Exception('Unable to clone code repository: ' . $stderr); + throw new \Exception('Unable to clone code repository: ' . $output); } // Local refactoring for function folder with spaces @@ -306,10 +304,10 @@ class Builds extends Action $rootDirectoryWithoutSpaces = str_replace(' ', '', $rootDirectory); $from = $tmpDirectory . '/' . $rootDirectory; $to = $tmpDirectory . '/' . $rootDirectoryWithoutSpaces; - $exit = Console::execute('mv "' . \escapeshellarg($from) . '" "' . \escapeshellarg($to) . '"', '', $stdout, $stderr); + $exit = Console::execute('mv "' . \escapeshellarg($from) . '" "' . \escapeshellarg($to) . '"', '', $output); if ($exit !== 0) { - throw new \Exception('Unable to move function with spaces' . $stderr); + throw new \Exception('Unable to move function with spaces' . $output); } $rootDirectory = $rootDirectoryWithoutSpaces; } @@ -330,33 +328,33 @@ class Builds extends Action $tmpTemplateDirectory = '/tmp/builds/' . $buildId . '/template'; $gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateVersion, GitHub::CLONE_TYPE_TAG, $tmpTemplateDirectory, $templateRootDirectory); - $exit = Console::execute($gitCloneCommandForTemplate, '', $stdout, $stderr); + $exit = Console::execute($gitCloneCommandForTemplate, '', $output); if ($exit !== 0) { - throw new \Exception('Unable to clone code repository: ' . $stderr); + throw new \Exception('Unable to clone code repository: ' . $output); } // Ensure directories - Console::execute('mkdir -p ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory), '', $stdout, $stderr); - Console::execute('mkdir -p ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $stdout, $stderr); + Console::execute('mkdir -p ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory), '', $output); + Console::execute('mkdir -p ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $output); // Merge template into user repo - Console::execute('rsync -av --exclude \'.git\' ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory . '/') . ' ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $stdout, $stderr); + Console::execute('rsync -av --exclude \'.git\' ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory . '/') . ' ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $output); // 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($function->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), '', $output); if ($exit !== 0) { - throw new \Exception('Unable to push code repository: ' . $stderr); + throw new \Exception('Unable to push code repository: ' . $output); } - $exit = Console::execute('cd ' . \escapeshellarg($tmpDirectory) . ' && git rev-parse HEAD', '', $stdout, $stderr); + $exit = Console::execute('cd ' . \escapeshellarg($tmpDirectory) . ' && git rev-parse HEAD', '', $output); if ($exit !== 0) { - throw new \Exception('Unable to get vcs commit SHA: ' . $stderr); + throw new \Exception('Unable to get vcs commit SHA: ' . $output); } - $providerCommitHash = \trim($stdout); + $providerCommitHash = \trim($output); $authorUrl = "https://github.com/$cloneOwner"; $deployment->setAttribute('providerCommitHash', $providerCommitHash ?? ''); @@ -399,7 +397,7 @@ class Builds extends Action } $tarParamDirectory = '/tmp/builds/' . $buildId . '/code' . (empty($rootDirectory) ? '' : '/' . $rootDirectory); - Console::execute('tar --exclude code.tar.gz -czf ' . \escapeshellarg($tmpPathFile) . ' -C ' . \escapeshellcmd($tarParamDirectory) . ' .', '', $stdout, $stderr); // TODO: Replace escapeshellcmd with escapeshellarg if we find a way that doesnt break syntax + Console::execute('tar --exclude code.tar.gz -czf ' . \escapeshellarg($tmpPathFile) . ' -C ' . \escapeshellcmd($tarParamDirectory) . ' .', '', $output); // TODO: Replace escapeshellcmd with escapeshellarg if we find a way that doesnt break syntax $source = $deviceForFunctions->getPath($deployment->getId() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION)); $result = $localDevice->transfer($tmpPathFile, $source, $deviceForFunctions); @@ -408,7 +406,7 @@ class Builds extends Action throw new \Exception("Unable to move file"); } - Console::execute('rm -rf ' . \escapeshellarg($tmpPath), '', $stdout, $stderr); + Console::execute('rm -rf ' . \escapeshellarg($tmpPath), '', $output); $build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttribute('source', $source)); @@ -663,7 +661,7 @@ class Builds extends Action ->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)); + $auth->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'); diff --git a/src/Appwrite/Platform/Workers/Certificates.php b/src/Appwrite/Platform/Workers/Certificates.php index 58dc1dd28a..4a8d928ba2 100644 --- a/src/Appwrite/Platform/Workers/Certificates.php +++ b/src/Appwrite/Platform/Workers/Certificates.php @@ -11,7 +11,6 @@ use Appwrite\Template\Template; use Appwrite\Utopia\Response\Model\Rule; use Exception; use Throwable; -use Utopia\App; use Utopia\CLI\Console; use Utopia\Database\Database; use Utopia\Database\DateTime; @@ -22,6 +21,7 @@ use Utopia\Database\Exception\Structure; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Domains\Domain; +use Utopia\Http\Http; use Utopia\Locale\Locale; use Utopia\Logger\Log; use Utopia\Platform\Action; @@ -330,30 +330,26 @@ class Certificates extends Action * * @param string $folder Folder into which certificates should be generated * @param string $domain Domain to generate certificate for - * @return array Named array with keys 'stdout' and 'stderr', both string + * @return string output * @throws Exception */ - private function issueCertificate(string $folder, string $domain, string $email): array + private function issueCertificate(string $folder, string $domain, string $email): string { - $stdout = ''; - $stderr = ''; + $output = ''; - $staging = (App::isProduction()) ? '' : ' --dry-run'; + $staging = (Http::isProduction()) ? '' : ' --dry-run'; $exit = Console::execute("certbot certonly -v --webroot --noninteractive --agree-tos{$staging}" . " --email " . $email . " --cert-name " . $folder . " -w " . APP_STORAGE_CERTIFICATES - . " -d {$domain}", '', $stdout, $stderr); + . " -d {$domain}", '', $output); // Unexpected error, usually 5XX, API limits, ... if ($exit !== 0) { - throw new Exception('Failed to issue a certificate with message: ' . $stderr); + throw new Exception('Failed to issue a certificate with message: ' . $output); } - return [ - 'stdout' => $stdout, - 'stderr' => $stderr - ]; + return $output; } /** @@ -381,7 +377,7 @@ class Certificates extends Action * @return void * @throws Exception */ - private function applyCertificateFiles(string $folder, string $domain, array $letsEncryptData): void + private function applyCertificateFiles(string $folder, string $domain, string $letsEncryptData): void { // Prepare folder in storage for domain @@ -394,19 +390,19 @@ class Certificates extends Action // Move generated files if (!@\rename('/etc/letsencrypt/live/' . $folder . '/cert.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem')) { - throw new Exception('Failed to rename certificate cert.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']); + throw new Exception('Failed to rename certificate cert.pem. Let\'s Encrypt log: ' . $letsEncryptData); } if (!@\rename('/etc/letsencrypt/live/' . $folder . '/chain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/chain.pem')) { - throw new Exception('Failed to rename certificate chain.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']); + throw new Exception('Failed to rename certificate chain.pem. Let\'s Encrypt log: ' . $letsEncryptData); } if (!@\rename('/etc/letsencrypt/live/' . $folder . '/fullchain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/fullchain.pem')) { - throw new Exception('Failed to rename certificate fullchain.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']); + throw new Exception('Failed to rename certificate fullchain.pem. Let\'s Encrypt log: ' . $letsEncryptData); } if (!@\rename('/etc/letsencrypt/live/' . $folder . '/privkey.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/privkey.pem')) { - throw new Exception('Failed to rename certificate privkey.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']); + throw new Exception('Failed to rename certificate privkey.pem. Let\'s Encrypt log: ' . $letsEncryptData); } $config = \implode(PHP_EOL, [ diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 4f8953a270..478b8bb80d 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -22,6 +22,7 @@ use Utopia\Database\Exception\Conflict; use Utopia\Database\Exception\Restricted; use Utopia\Database\Exception\Structure; use Utopia\Database\Query; +use Utopia\Database\Validator\Authorization as ValidatorAuthorization; use Utopia\DSN\DSN; use Utopia\Logger\Log; use Utopia\Platform\Action; @@ -54,14 +55,15 @@ class Deletes extends Action ->inject('executionRetention') ->inject('auditRetention') ->inject('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)); + ->inject('authorization') + ->callback(fn ($message, $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, string $abuseRetention, string $executionRetention, string $auditRetention, Log $log, ValidatorAuthorization $authorization) => $this->action($message, $dbForConsole, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $abuseRetention, $executionRetention, $auditRetention, $log, $authorization)); } /** * @throws Exception * @throws Throwable */ - 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 + 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, ValidatorAuthorization $auth): void { $payload = $message->getPayload() ?? []; @@ -117,7 +119,7 @@ class Deletes extends Action break; case DELETE_TYPE_AUDIT: if (!$project->isEmpty()) { - $this->deleteAuditLogs($project, $getProjectDB, $auditRetention); + $this->deleteAuditLogs($project, $getProjectDB, $auditRetention, $auth); } if (!$document->isEmpty()) { @@ -125,7 +127,7 @@ class Deletes extends Action } break; case DELETE_TYPE_ABUSE: - $this->deleteAbuseLogs($project, $getProjectDB, $abuseRetention); + $this->deleteAbuseLogs($project, $getProjectDB, $abuseRetention, $auth); break; case DELETE_TYPE_REALTIME: $this->deleteRealtimeUsage($dbForConsole, $datetime); @@ -693,7 +695,7 @@ class Deletes extends Action * @return void * @throws Exception */ - private function deleteAbuseLogs(Document $project, callable $getProjectDB, string $abuseRetention): void + private function deleteAbuseLogs(Document $project, callable $getProjectDB, string $abuseRetention, ValidatorAuthorization $auth): void { $projectId = $project->getId(); $dbForProject = $getProjectDB($project); @@ -714,7 +716,7 @@ class Deletes extends Action * @return void * @throws Exception */ - private function deleteAuditLogs(Document $project, callable $getProjectDB, string $auditRetention): void + private function deleteAuditLogs(Document $project, callable $getProjectDB, string $auditRetention, ValidatorAuthorization $auth): void { $projectId = $project->getId(); $dbForProject = $getProjectDB($project); diff --git a/src/Appwrite/Platform/Workers/Mails.php b/src/Appwrite/Platform/Workers/Mails.php index e48f96ea90..51bdb56cf0 100644 --- a/src/Appwrite/Platform/Workers/Mails.php +++ b/src/Appwrite/Platform/Workers/Mails.php @@ -84,13 +84,13 @@ class Mails extends Action $bodyTemplate = __DIR__ . '/../../../../app/config/locale/templates/email-base.tpl'; } $bodyTemplate = Template::fromFile($bodyTemplate); - $bodyTemplate->setParam('{{body}}', $body, escapeHtml: false); + $bodyTemplate->setParam('{{body}}', $body, escape: false); foreach ($variables as $key => $value) { // TODO: hotfix for redirect param - $bodyTemplate->setParam('{{' . $key . '}}', $value, escapeHtml: $key !== 'redirect'); + $bodyTemplate->setParam('{{' . $key . '}}', $value, escape: $key !== 'redirect'); } foreach ($this->richTextParams as $key => $value) { - $bodyTemplate->setParam('{{' . $key . '}}', $value, escapeHtml: false); + $bodyTemplate->setParam('{{' . $key . '}}', $value, escape: false); } $body = $bodyTemplate->render(); diff --git a/src/Appwrite/Promises/Promise.php b/src/Appwrite/Promises/Promise.php index a6b1aa79d5..f12590dfed 100644 --- a/src/Appwrite/Promises/Promise.php +++ b/src/Appwrite/Promises/Promise.php @@ -12,7 +12,7 @@ abstract class Promise private mixed $result; - public function __construct(?callable $executor = null) + final public function __construct(?callable $executor = null) { if (\is_null($executor)) { return; diff --git a/src/Appwrite/Promises/Swoole.php b/src/Appwrite/Promises/Swoole.php index c258ef6a5e..8cddd567f3 100644 --- a/src/Appwrite/Promises/Swoole.php +++ b/src/Appwrite/Promises/Swoole.php @@ -6,23 +6,16 @@ use Swoole\Coroutine\Channel; class Swoole extends Promise { - public function __construct(?callable $executor = null) - { - parent::__construct($executor); - } - protected function execute( callable $executor, callable $resolve, callable $reject ): void { - \go(function () use ($executor, $resolve, $reject) { - try { - $executor($resolve, $reject); - } catch (\Throwable $exception) { - $reject($exception); - } - }); + try { + $executor($resolve, $reject); + } catch (\Throwable $exception) { + $reject($exception); + } } /** diff --git a/src/Appwrite/Specification/Format.php b/src/Appwrite/Specification/Format.php index 30ce6470e1..e2048971be 100644 --- a/src/Appwrite/Specification/Format.php +++ b/src/Appwrite/Specification/Format.php @@ -3,13 +3,13 @@ namespace Appwrite\Specification; use Appwrite\Utopia\Response\Model; -use Utopia\App; use Utopia\Config\Config; -use Utopia\Route; +use Utopia\Http\Http; +use Utopia\Http\Route; abstract class Format { - protected App $app; + protected Http $http; /** * @var Route[] @@ -50,9 +50,9 @@ abstract class Format ] ]; - public function __construct(App $app, array $services, array $routes, array $models, array $keys, int $authCount) + public function __construct(Http $http, array $services, array $routes, array $models, array $keys, int $authCount) { - $this->app = $app; + $this->http = $http; $this->services = $services; $this->routes = $routes; $this->models = $models; diff --git a/src/Appwrite/Specification/Format/OpenAPI3.php b/src/Appwrite/Specification/Format/OpenAPI3.php index 3074d59b7c..c9186ee0ac 100644 --- a/src/Appwrite/Specification/Format/OpenAPI3.php +++ b/src/Appwrite/Specification/Format/OpenAPI3.php @@ -7,11 +7,11 @@ use Appwrite\Template\Template; use Appwrite\Utopia\Response\Model; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; -use Utopia\Validator; -use Utopia\Validator\ArrayList; -use Utopia\Validator\Nullable; -use Utopia\Validator\Range; -use Utopia\Validator\WhiteList; +use Utopia\Http\Validator; +use Utopia\Http\Validator\ArrayList; +use Utopia\Http\Validator\Nullable; +use Utopia\Http\Validator\Range; +use Utopia\Http\Validator\WhiteList; class OpenAPI3 extends Format { @@ -238,8 +238,11 @@ class OpenAPI3 extends Format } if ($route->getLabel('sdk.response.code', 500) === 204) { - $temp['responses'][(string)$route->getLabel('sdk.response.code', '500')]['description'] = 'No content'; - unset($temp['responses'][(string)$route->getLabel('sdk.response.code', '500')]['schema']); + $labelCode = (string)$route->getLabel('sdk.response.code', '500'); + $temp['responses'][$labelCode]['description'] = 'No content'; + if (isset($temp['responses'][$labelCode]['schema'])) { + unset($temp['responses'][$labelCode]['schema']); + } } if ((!empty($scope))) { // && 'public' != $scope @@ -269,10 +272,10 @@ class OpenAPI3 extends Format $bodyRequired = []; foreach ($route->getParams() as $name => $param) { // Set params - /** - * @var \Utopia\Validator $validator - */ - $validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $this->app->getResources($param['injections'])) : $param['validator']; + $injections = array_map(fn ($injection) => $this->http->getContainer()->get($injection), $param['injections'] ?? []); + + /** @var Validator $validator */ + $validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $injections) : $param['validator']; $node = [ 'name' => $name, @@ -289,11 +292,11 @@ class OpenAPI3 extends Format switch ((!empty($validator)) ? \get_class($validator) : '') { case 'Utopia\Database\Validator\UID': - case 'Utopia\Validator\Text': + case 'Utopia\Http\Validator\Text': $node['schema']['type'] = $validator->getType(); $node['schema']['x-example'] = '<' . \strtoupper(Template::fromCamelCaseToSnake($node['name'])) . '>'; break; - case 'Utopia\Validator\Boolean': + case 'Utopia\Http\Validator\Boolean': $node['schema']['type'] = $validator->getType(); $node['schema']['x-example'] = false; break; @@ -314,15 +317,15 @@ class OpenAPI3 extends Format $node['schema']['format'] = 'email'; $node['schema']['x-example'] = 'email@example.com'; break; - case 'Utopia\Validator\Host': - case 'Utopia\Validator\URL': + case 'Utopia\Http\Validator\Host': + case 'Utopia\Http\Validator\URL': $node['schema']['type'] = $validator->getType(); $node['schema']['format'] = 'url'; $node['schema']['x-example'] = 'https://example.com'; break; - case 'Utopia\Validator\JSON': - case 'Utopia\Validator\Mock': - case 'Utopia\Validator\Assoc': + case 'Utopia\Http\Validator\JSON': + case 'Utopia\Http\Validator\Mock': + case 'Utopia\Http\Validator\Assoc': $param['default'] = (empty($param['default'])) ? new \stdClass() : $param['default']; $node['schema']['type'] = 'object'; $node['schema']['x-example'] = '{}'; @@ -332,7 +335,7 @@ class OpenAPI3 extends Format $node['schema']['type'] = $validator->getType(); $node['schema']['format'] = 'binary'; break; - case 'Utopia\Validator\ArrayList': + case 'Utopia\Http\Validator\ArrayList': /** @var ArrayList $validator */ $node['schema']['type'] = 'array'; $node['schema']['items'] = [ @@ -394,25 +397,25 @@ class OpenAPI3 extends Format $node['schema']['format'] = 'phone'; $node['schema']['x-example'] = '+12065550100'; // In the US, 555 is reserved like example.com break; - case 'Utopia\Validator\Range': + case 'Utopia\Http\Validator\Range': /** @var Range $validator */ $node['schema']['type'] = $validator->getType() === Validator::TYPE_FLOAT ? 'number' : $validator->getType(); $node['schema']['format'] = $validator->getType() == Validator::TYPE_INTEGER ? 'int32' : 'float'; $node['schema']['x-example'] = $validator->getMin(); break; - case 'Utopia\Validator\Numeric': - case 'Utopia\Validator\Integer': + case 'Utopia\Http\Validator\Numeric': + case 'Utopia\Http\Validator\Integer': $node['schema']['type'] = $validator->getType(); $node['schema']['format'] = 'int32'; break; - case 'Utopia\Validator\FloatValidator': + case 'Utopia\Http\Validator\FloatValidator': $node['schema']['type'] = 'number'; $node['schema']['format'] = 'float'; break; - case 'Utopia\Validator\Length': + case 'Utopia\Http\Validator\Length': $node['schema']['type'] = $validator->getType(); break; - case 'Utopia\Validator\WhiteList': + case 'Utopia\Http\Validator\WhiteList': /** @var WhiteList $validator */ $node['schema']['type'] = $validator->getType(); $node['schema']['x-example'] = $validator->getList()[0]; diff --git a/src/Appwrite/Specification/Format/Swagger2.php b/src/Appwrite/Specification/Format/Swagger2.php index 2eab7807b3..81e0f2c41d 100644 --- a/src/Appwrite/Specification/Format/Swagger2.php +++ b/src/Appwrite/Specification/Format/Swagger2.php @@ -7,10 +7,10 @@ use Appwrite\Template\Template; use Appwrite\Utopia\Response\Model; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; -use Utopia\Validator; -use Utopia\Validator\ArrayList; -use Utopia\Validator\Nullable; -use Utopia\Validator\Range; +use Utopia\Http\Validator; +use Utopia\Http\Validator\ArrayList; +use Utopia\Http\Validator\Nullable; +use Utopia\Http\Validator\Range; class Swagger2 extends Format { @@ -270,8 +270,10 @@ class Swagger2 extends Format ); foreach ($parameters as $name => $param) { // Set params + $injections = array_map(fn ($injection) => $this->http->getContainer()->get($injection), $param['injections'] ?? []); + /** @var Validator $validator */ - $validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $this->app->getResources($param['injections'])) : $param['validator']; + $validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $injections) : $param['validator']; $node = [ 'name' => $name, @@ -287,18 +289,18 @@ class Swagger2 extends Format } $validatorClass = (!empty($validator)) ? \get_class($validator) : ''; - if ($validatorClass === 'Utopia\Validator\AnyOf') { + if ($validatorClass === 'Utopia\Http\Validator\AnyOf') { $validator = $param['validator']->getValidators()[0]; $validatorClass = \get_class($validator); } switch ($validatorClass) { - case 'Utopia\Validator\Text': + case 'Utopia\Http\Validator\Text': case 'Utopia\Database\Validator\UID': $node['type'] = $validator->getType(); $node['x-example'] = '<' . \strtoupper(Template::fromCamelCaseToSnake($node['name'])) . '>'; break; - case 'Utopia\Validator\Boolean': + case 'Utopia\Http\Validator\Boolean': $node['type'] = $validator->getType(); $node['x-example'] = false; break; @@ -319,13 +321,13 @@ class Swagger2 extends Format $node['format'] = 'email'; $node['x-example'] = 'email@example.com'; break; - case 'Utopia\Validator\Host': - case 'Utopia\Validator\URL': + case 'Utopia\Http\Validator\Host': + case 'Utopia\Http\Validator\URL': $node['type'] = $validator->getType(); $node['format'] = 'url'; $node['x-example'] = 'https://example.com'; break; - case 'Utopia\Validator\ArrayList': + case 'Utopia\Http\Validator\ArrayList': /** @var ArrayList $validator */ $node['type'] = 'array'; $node['collectionFormat'] = 'multi'; @@ -333,9 +335,9 @@ class Swagger2 extends Format 'type' => $validator->getValidator()->getType(), ]; break; - case 'Utopia\Validator\JSON': - case 'Utopia\Validator\Mock': - case 'Utopia\Validator\Assoc': + case 'Utopia\Http\Validator\JSON': + case 'Utopia\Http\Validator\Mock': + case 'Utopia\Http\Validator\Assoc': $node['type'] = 'object'; $node['default'] = (empty($param['default'])) ? new \stdClass() : $param['default']; $node['x-example'] = '{}'; @@ -406,26 +408,26 @@ class Swagger2 extends Format $node['format'] = 'phone'; $node['x-example'] = '+12065550100'; break; - case 'Utopia\Validator\Range': + case 'Utopia\Http\Validator\Range': /** @var Range $validator */ $node['type'] = $validator->getType() === Validator::TYPE_FLOAT ? 'number' : $validator->getType(); $node['format'] = $validator->getType() == Validator::TYPE_INTEGER ? 'int32' : 'float'; $node['x-example'] = $validator->getMin(); break; - case 'Utopia\Validator\Numeric': - case 'Utopia\Validator\Integer': + case 'Utopia\Http\Validator\Numeric': + case 'Utopia\Http\Validator\Integer': $node['type'] = $validator->getType(); $node['format'] = 'int32'; break; - case 'Utopia\Validator\FloatValidator': + case 'Utopia\Http\Validator\FloatValidator': $node['type'] = 'number'; $node['format'] = 'float'; break; - case 'Utopia\Validator\Length': + case 'Utopia\Http\Validator\Length': $node['type'] = $validator->getType(); break; - case 'Utopia\Validator\WhiteList': - /** @var \Utopia\Validator\WhiteList $validator */ + case 'Utopia\Http\Validator\WhiteList': + /** @var \Utopia\Http\Validator\WhiteList $validator */ $node['type'] = $validator->getType(); $node['x-example'] = $validator->getList()[0]; diff --git a/src/Appwrite/Task/Validator/Cron.php b/src/Appwrite/Task/Validator/Cron.php index 03bd1c5220..afa19c50a6 100644 --- a/src/Appwrite/Task/Validator/Cron.php +++ b/src/Appwrite/Task/Validator/Cron.php @@ -3,7 +3,7 @@ namespace Appwrite\Task\Validator; use Cron\CronExpression; -use Utopia\Validator; +use Utopia\Http\Validator; class Cron extends Validator { diff --git a/src/Appwrite/Utopia/Database/Validator/CompoundUID.php b/src/Appwrite/Utopia/Database/Validator/CompoundUID.php index 3f23500952..b851d8ba85 100644 --- a/src/Appwrite/Utopia/Database/Validator/CompoundUID.php +++ b/src/Appwrite/Utopia/Database/Validator/CompoundUID.php @@ -3,7 +3,7 @@ namespace Appwrite\Utopia\Database\Validator; use Utopia\Database\Validator\UID; -use Utopia\Validator; +use Utopia\Http\Validator; class CompoundUID extends Validator { diff --git a/src/Appwrite/Utopia/Database/Validator/ProjectId.php b/src/Appwrite/Utopia/Database/Validator/ProjectId.php index 46b0cdf53e..28abe176fe 100644 --- a/src/Appwrite/Utopia/Database/Validator/ProjectId.php +++ b/src/Appwrite/Utopia/Database/Validator/ProjectId.php @@ -2,7 +2,7 @@ namespace Appwrite\Utopia\Database\Validator; -use Utopia\Validator; +use Utopia\Http\Validator; class ProjectId extends Validator { diff --git a/src/Appwrite/Utopia/Queue/Connections.php b/src/Appwrite/Utopia/Queue/Connections.php new file mode 100644 index 0000000000..e873373566 --- /dev/null +++ b/src/Appwrite/Utopia/Queue/Connections.php @@ -0,0 +1,36 @@ +connections[] = ['connection' => $connection, 'pool' => $pool]; + return $this; + } + + /** + * @return self + */ + public function reclaim(): self + { + foreach ($this->connections as $id => $resource) { + $pool = $resource['pool']; + $connection = $resource['connection']; + $pool->put($connection); + unset($this->connections[$id]); + } + + return $this; + } +} diff --git a/src/Appwrite/Utopia/Request.php b/src/Appwrite/Utopia/Request.php index 3f0a196d5e..6be9701baa 100644 --- a/src/Appwrite/Utopia/Request.php +++ b/src/Appwrite/Utopia/Request.php @@ -3,11 +3,10 @@ namespace Appwrite\Utopia; use Appwrite\Utopia\Request\Filter; -use Swoole\Http\Request as SwooleRequest; -use Utopia\Route; -use Utopia\Swoole\Request as UtopiaRequest; +use Utopia\Http\Adapter\Swoole\Request as HttpRequest; +use Utopia\Http\Route; -class Request extends UtopiaRequest +class Request extends HttpRequest { /** * @var array @@ -15,9 +14,12 @@ class Request extends UtopiaRequest private array $filters = []; private static ?Route $route = null; - public function __construct(SwooleRequest $request) + /** + * Request constructor. + */ + public function __construct(HttpRequest $request) { - parent::__construct($request); + parent::__construct($request->swoole); } /** @@ -113,6 +115,16 @@ class Request extends UtopiaRequest return self::$route !== null; } + + public function removeHeader(string $key): static + { + if (isset($this->headers[$key])) { + unset($this->headers[$key]); + } + + return parent::removeHeader($key); + } + /** * Get headers * @@ -122,10 +134,14 @@ class Request extends UtopiaRequest */ public function getHeaders(): array { - $headers = $this->generateHeaders(); + if ($this->headers !== null) { + return $this->headers; + } + + $this->headers = $this->generateHeaders(); if (empty($this->swoole->cookie)) { - return $headers; + return $this->headers; } $cookieHeaders = []; @@ -134,10 +150,10 @@ class Request extends UtopiaRequest } if (!empty($cookieHeaders)) { - $headers['cookie'] = \implode('; ', $cookieHeaders); + $this->headers['cookie'] = \implode('; ', $cookieHeaders); } - return $headers; + return $this->headers; } /** diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index 6cc2639f51..376c327b37 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -4,122 +4,20 @@ namespace Appwrite\Utopia; use Appwrite\Utopia\Fetch\BodyMultipart; use Appwrite\Utopia\Response\Filter; -use Appwrite\Utopia\Response\Model; -use Appwrite\Utopia\Response\Model\Account; -use Appwrite\Utopia\Response\Model\AlgoArgon2; -use Appwrite\Utopia\Response\Model\AlgoBcrypt; -use Appwrite\Utopia\Response\Model\AlgoMd5; -use Appwrite\Utopia\Response\Model\AlgoPhpass; -use Appwrite\Utopia\Response\Model\AlgoScrypt; -use Appwrite\Utopia\Response\Model\AlgoScryptModified; -use Appwrite\Utopia\Response\Model\AlgoSha; -use Appwrite\Utopia\Response\Model\Any; -use Appwrite\Utopia\Response\Model\Attribute; -use Appwrite\Utopia\Response\Model\AttributeBoolean; -use Appwrite\Utopia\Response\Model\AttributeDatetime; -use Appwrite\Utopia\Response\Model\AttributeEmail; -use Appwrite\Utopia\Response\Model\AttributeEnum; -use Appwrite\Utopia\Response\Model\AttributeFloat; -use Appwrite\Utopia\Response\Model\AttributeInteger; -use Appwrite\Utopia\Response\Model\AttributeIP; -use Appwrite\Utopia\Response\Model\AttributeList; -use Appwrite\Utopia\Response\Model\AttributeRelationship; -use Appwrite\Utopia\Response\Model\AttributeString; -use Appwrite\Utopia\Response\Model\AttributeURL; -use Appwrite\Utopia\Response\Model\AuthProvider; -use Appwrite\Utopia\Response\Model\BaseList; -use Appwrite\Utopia\Response\Model\Branch; -use Appwrite\Utopia\Response\Model\Bucket; -use Appwrite\Utopia\Response\Model\Build; -use Appwrite\Utopia\Response\Model\Collection; -use Appwrite\Utopia\Response\Model\ConsoleVariables; -use Appwrite\Utopia\Response\Model\Continent; -use Appwrite\Utopia\Response\Model\Country; -use Appwrite\Utopia\Response\Model\Currency; -use Appwrite\Utopia\Response\Model\Database; -use Appwrite\Utopia\Response\Model\Deployment; -use Appwrite\Utopia\Response\Model\Detection; -use Appwrite\Utopia\Response\Model\Document as ModelDocument; -use Appwrite\Utopia\Response\Model\Error; -use Appwrite\Utopia\Response\Model\ErrorDev; -use Appwrite\Utopia\Response\Model\Execution; -use Appwrite\Utopia\Response\Model\File; -use Appwrite\Utopia\Response\Model\Func; -use Appwrite\Utopia\Response\Model\Headers; -use Appwrite\Utopia\Response\Model\HealthAntivirus; -use Appwrite\Utopia\Response\Model\HealthCertificate; -use Appwrite\Utopia\Response\Model\HealthQueue; -use Appwrite\Utopia\Response\Model\HealthStatus; -use Appwrite\Utopia\Response\Model\HealthTime; -use Appwrite\Utopia\Response\Model\HealthVersion; -use Appwrite\Utopia\Response\Model\Identity; -use Appwrite\Utopia\Response\Model\Index; -use Appwrite\Utopia\Response\Model\Installation; -use Appwrite\Utopia\Response\Model\JWT; -use Appwrite\Utopia\Response\Model\Key; -use Appwrite\Utopia\Response\Model\Language; -use Appwrite\Utopia\Response\Model\Locale; -use Appwrite\Utopia\Response\Model\LocaleCode; -use Appwrite\Utopia\Response\Model\Log; -use Appwrite\Utopia\Response\Model\Membership; -use Appwrite\Utopia\Response\Model\Message; -use Appwrite\Utopia\Response\Model\Metric; -use Appwrite\Utopia\Response\Model\MetricBreakdown; -use Appwrite\Utopia\Response\Model\MFAChallenge; -use Appwrite\Utopia\Response\Model\MFAFactors; -use Appwrite\Utopia\Response\Model\MFARecoveryCodes; -use Appwrite\Utopia\Response\Model\MFAType; -use Appwrite\Utopia\Response\Model\Migration; -use Appwrite\Utopia\Response\Model\MigrationFirebaseProject; -use Appwrite\Utopia\Response\Model\MigrationReport; -use Appwrite\Utopia\Response\Model\Mock; -use Appwrite\Utopia\Response\Model\MockNumber; -use Appwrite\Utopia\Response\Model\None; -use Appwrite\Utopia\Response\Model\Phone; -use Appwrite\Utopia\Response\Model\Platform; -use Appwrite\Utopia\Response\Model\Preferences; -use Appwrite\Utopia\Response\Model\Project; -use Appwrite\Utopia\Response\Model\Provider; -use Appwrite\Utopia\Response\Model\ProviderRepository; -use Appwrite\Utopia\Response\Model\Rule; -use Appwrite\Utopia\Response\Model\Runtime; -use Appwrite\Utopia\Response\Model\Session; -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\TemplateFunction; -use Appwrite\Utopia\Response\Model\TemplateRuntime; -use Appwrite\Utopia\Response\Model\TemplateSMS; -use Appwrite\Utopia\Response\Model\TemplateVariable; -use Appwrite\Utopia\Response\Model\Token; -use Appwrite\Utopia\Response\Model\Topic; -use Appwrite\Utopia\Response\Model\UsageBuckets; -use Appwrite\Utopia\Response\Model\UsageCollection; -use Appwrite\Utopia\Response\Model\UsageDatabase; -use Appwrite\Utopia\Response\Model\UsageDatabases; -use Appwrite\Utopia\Response\Model\UsageFunction; -use Appwrite\Utopia\Response\Model\UsageFunctions; -use Appwrite\Utopia\Response\Model\UsageProject; -use Appwrite\Utopia\Response\Model\UsageStorage; -use Appwrite\Utopia\Response\Model\UsageUsers; -use Appwrite\Utopia\Response\Model\User; -use Appwrite\Utopia\Response\Model\Variable; -use Appwrite\Utopia\Response\Model\VcsContent; -use Appwrite\Utopia\Response\Model\Webhook; +use Appwrite\Utopia\Response\Models; use Exception; use JsonException; -use Swoole\Http\Response as SwooleHTTPResponse; // Keep last use Utopia\Database\Document; -use Utopia\Swoole\Response as SwooleResponse; +use Utopia\Http\Adapter\Swoole\Response as HttpResponse; + +// Keep last /** * @method int getStatusCode() * @method Response setStatusCode(int $code = 200) */ -class Response extends SwooleResponse +class Response extends HttpResponse { // General public const MODEL_NONE = 'none'; @@ -330,162 +228,9 @@ class Response extends SwooleResponse * * @param float $time */ - public function __construct(SwooleHTTPResponse $response) + public function __construct(HttpResponse $response) { - $this - // General - ->setModel(new None()) - ->setModel(new Any()) - ->setModel(new Error()) - ->setModel(new ErrorDev()) - // Lists - ->setModel(new BaseList('Documents List', self::MODEL_DOCUMENT_LIST, 'documents', self::MODEL_DOCUMENT)) - ->setModel(new BaseList('Collections List', self::MODEL_COLLECTION_LIST, 'collections', self::MODEL_COLLECTION)) - ->setModel(new BaseList('Databases List', self::MODEL_DATABASE_LIST, 'databases', self::MODEL_DATABASE)) - ->setModel(new BaseList('Indexes List', self::MODEL_INDEX_LIST, 'indexes', self::MODEL_INDEX)) - ->setModel(new BaseList('Users List', self::MODEL_USER_LIST, 'users', self::MODEL_USER)) - ->setModel(new BaseList('Sessions List', self::MODEL_SESSION_LIST, 'sessions', self::MODEL_SESSION)) - ->setModel(new BaseList('Identities List', self::MODEL_IDENTITY_LIST, 'identities', self::MODEL_IDENTITY)) - ->setModel(new BaseList('Logs List', self::MODEL_LOG_LIST, 'logs', self::MODEL_LOG)) - ->setModel(new BaseList('Files List', self::MODEL_FILE_LIST, 'files', self::MODEL_FILE)) - ->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('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('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)) - ->setModel(new BaseList('Builds List', self::MODEL_BUILD_LIST, 'builds', self::MODEL_BUILD)) // Not used anywhere yet - ->setModel(new BaseList('Projects List', self::MODEL_PROJECT_LIST, 'projects', self::MODEL_PROJECT, true, false)) - ->setModel(new BaseList('Webhooks List', self::MODEL_WEBHOOK_LIST, 'webhooks', self::MODEL_WEBHOOK, true, false)) - ->setModel(new BaseList('API Keys List', self::MODEL_KEY_LIST, 'keys', self::MODEL_KEY, true, false)) - ->setModel(new BaseList('Auth Providers List', self::MODEL_AUTH_PROVIDER_LIST, 'platforms', self::MODEL_AUTH_PROVIDER, true, false)) - ->setModel(new BaseList('Platforms List', self::MODEL_PLATFORM_LIST, 'platforms', self::MODEL_PLATFORM, true, false)) - ->setModel(new BaseList('Countries List', self::MODEL_COUNTRY_LIST, 'countries', self::MODEL_COUNTRY)) - ->setModel(new BaseList('Continents List', self::MODEL_CONTINENT_LIST, 'continents', self::MODEL_CONTINENT)) - ->setModel(new BaseList('Languages List', self::MODEL_LANGUAGE_LIST, 'languages', self::MODEL_LANGUAGE)) - ->setModel(new BaseList('Currencies List', self::MODEL_CURRENCY_LIST, 'currencies', self::MODEL_CURRENCY)) - ->setModel(new BaseList('Phones List', self::MODEL_PHONE_LIST, 'phones', self::MODEL_PHONE)) - ->setModel(new BaseList('Metric List', self::MODEL_METRIC_LIST, 'metrics', self::MODEL_METRIC, true, false)) - ->setModel(new BaseList('Variables List', self::MODEL_VARIABLE_LIST, 'variables', self::MODEL_VARIABLE)) - ->setModel(new BaseList('Status List', self::MODEL_HEALTH_STATUS_LIST, 'statuses', self::MODEL_HEALTH_STATUS)) - ->setModel(new BaseList('Rule List', self::MODEL_PROXY_RULE_LIST, 'rules', self::MODEL_PROXY_RULE)) - ->setModel(new BaseList('Locale codes list', self::MODEL_LOCALE_CODE_LIST, 'localeCodes', self::MODEL_LOCALE_CODE)) - ->setModel(new BaseList('Provider list', self::MODEL_PROVIDER_LIST, 'providers', self::MODEL_PROVIDER)) - ->setModel(new BaseList('Message list', self::MODEL_MESSAGE_LIST, 'messages', self::MODEL_MESSAGE)) - ->setModel(new BaseList('Topic list', self::MODEL_TOPIC_LIST, 'topics', self::MODEL_TOPIC)) - ->setModel(new BaseList('Subscriber list', self::MODEL_SUBSCRIBER_LIST, 'subscribers', self::MODEL_SUBSCRIBER)) - ->setModel(new BaseList('Target list', self::MODEL_TARGET_LIST, 'targets', self::MODEL_TARGET)) - ->setModel(new BaseList('Migrations List', self::MODEL_MIGRATION_LIST, 'migrations', self::MODEL_MIGRATION)) - ->setModel(new BaseList('Migrations Firebase Projects List', self::MODEL_MIGRATION_FIREBASE_PROJECT_LIST, 'projects', self::MODEL_MIGRATION_FIREBASE_PROJECT)) - ->setModel(new BaseList('Specifications List', self::MODEL_SPECIFICATION_LIST, 'specifications', self::MODEL_SPECIFICATION)) - ->setModel(new BaseList('VCS Content List', self::MODEL_VCS_CONTENT_LIST, 'contents', self::MODEL_VCS_CONTENT)) - // Entities - ->setModel(new Database()) - ->setModel(new Collection()) - ->setModel(new Attribute()) - ->setModel(new AttributeList()) - ->setModel(new AttributeString()) - ->setModel(new AttributeInteger()) - ->setModel(new AttributeFloat()) - ->setModel(new AttributeBoolean()) - ->setModel(new AttributeEmail()) - ->setModel(new AttributeEnum()) - ->setModel(new AttributeIP()) - ->setModel(new AttributeURL()) - ->setModel(new AttributeDatetime()) - ->setModel(new AttributeRelationship()) - ->setModel(new Index()) - ->setModel(new ModelDocument()) - ->setModel(new Log()) - ->setModel(new User()) - ->setModel(new AlgoMd5()) - ->setModel(new AlgoSha()) - ->setModel(new AlgoPhpass()) - ->setModel(new AlgoBcrypt()) - ->setModel(new AlgoScrypt()) - ->setModel(new AlgoScryptModified()) - ->setModel(new AlgoArgon2()) - ->setModel(new Account()) - ->setModel(new Preferences()) - ->setModel(new Session()) - ->setModel(new Identity()) - ->setModel(new Token()) - ->setModel(new JWT()) - ->setModel(new Locale()) - ->setModel(new LocaleCode()) - ->setModel(new File()) - ->setModel(new Bucket()) - ->setModel(new Team()) - ->setModel(new Membership()) - ->setModel(new Func()) - ->setModel(new TemplateFunction()) - ->setModel(new TemplateRuntime()) - ->setModel(new TemplateVariable()) - ->setModel(new Installation()) - ->setModel(new ProviderRepository()) - ->setModel(new Detection()) - ->setModel(new VcsContent()) - ->setModel(new Branch()) - ->setModel(new Runtime()) - ->setModel(new Deployment()) - ->setModel(new Execution()) - ->setModel(new Build()) - ->setModel(new Project()) - ->setModel(new Webhook()) - ->setModel(new Key()) - ->setModel(new MockNumber()) - ->setModel(new AuthProvider()) - ->setModel(new Platform()) - ->setModel(new Variable()) - ->setModel(new Country()) - ->setModel(new Continent()) - ->setModel(new Language()) - ->setModel(new Currency()) - ->setModel(new Phone()) - ->setModel(new HealthAntivirus()) - ->setModel(new HealthQueue()) - ->setModel(new HealthStatus()) - ->setModel(new HealthCertificate()) - ->setModel(new HealthTime()) - ->setModel(new HealthVersion()) - ->setModel(new Metric()) - ->setModel(new MetricBreakdown()) - ->setModel(new UsageDatabases()) - ->setModel(new UsageDatabase()) - ->setModel(new UsageCollection()) - ->setModel(new UsageUsers()) - ->setModel(new UsageStorage()) - ->setModel(new UsageBuckets()) - ->setModel(new UsageFunctions()) - ->setModel(new UsageFunction()) - ->setModel(new UsageProject()) - ->setModel(new Headers()) - ->setModel(new Specification()) - ->setModel(new Rule()) - ->setModel(new TemplateSMS()) - ->setModel(new TemplateEmail()) - ->setModel(new ConsoleVariables()) - ->setModel(new MFAChallenge()) - ->setModel(new MFARecoveryCodes()) - ->setModel(new MFAType()) - ->setModel(new MFAFactors()) - ->setModel(new Provider()) - ->setModel(new Message()) - ->setModel(new Topic()) - ->setModel(new Subscriber()) - ->setModel(new Target()) - ->setModel(new Migration()) - ->setModel(new MigrationReport()) - ->setModel(new MigrationFirebaseProject()) - // Tests (keep last) - ->setModel(new Mock()); - - parent::__construct($response); + parent::__construct($response->swoole); } /** @@ -495,49 +240,6 @@ class Response extends SwooleResponse public const CONTENT_TYPE_NULL = 'null'; public const CONTENT_TYPE_MULTIPART = 'multipart/form-data'; - /** - * List of defined output objects - */ - protected $models = []; - - /** - * Set Model Object - * - * @return self - */ - public function setModel(Model $instance) - { - $this->models[$instance->getType()] = $instance; - - return $this; - } - - /** - * Get Model Object - * - * @param string $key - * @return Model - * @throws Exception - */ - public function getModel(string $key): Model - { - if (!isset($this->models[$key])) { - throw new Exception('Undefined model: ' . $key); - } - - return $this->models[$key]; - } - - /** - * Get Models List - * - * @return Model[] - */ - public function getModels(): array - { - return $this->models; - } - public function applyFilters(array $data, string $model): array { foreach ($this->filters as $filter) { @@ -605,7 +307,7 @@ class Response extends SwooleResponse public function output(Document $document, string $model): array { $data = clone $document; - $model = $this->getModel($model); + $model = Models::getModel($model); $output = []; $data = $model->filter($data); @@ -640,7 +342,7 @@ class Response extends SwooleResponse if (\is_array($rule['type'])) { foreach ($rule['type'] as $type) { $condition = false; - foreach ($this->getModel($type)->conditions as $attribute => $val) { + foreach (Models::getModel($type)->conditions as $attribute => $val) { $condition = $item->getAttribute($attribute) === $val; if (!$condition) { break; @@ -655,7 +357,7 @@ class Response extends SwooleResponse $ruleType = $rule['type']; } - if (!array_key_exists($ruleType, $this->models)) { + if (!array_key_exists($ruleType, Models::getModels())) { throw new Exception('Missing model for rule: ' . $ruleType); } diff --git a/src/Appwrite/Utopia/Response/Models.php b/src/Appwrite/Utopia/Response/Models.php new file mode 100644 index 0000000000..2a0393321c --- /dev/null +++ b/src/Appwrite/Utopia/Response/Models.php @@ -0,0 +1,304 @@ +getType()] = $instance; + } + + /** + * Get Model Object + * + * @param string $key + * @return Model + * @throws Exception + */ + public static function getModel(string $key): Model + { + if (!isset(self::$models[$key])) { + throw new Exception('Undefined model: ' . $key); + } + + return self::$models[$key]; + } + + public static function getModels(): array + { + return self::$models; + } +} diff --git a/src/Appwrite/Utopia/View.php b/src/Appwrite/Utopia/View.php index e4ed8164c9..60cafb1ca8 100644 --- a/src/Appwrite/Utopia/View.php +++ b/src/Appwrite/Utopia/View.php @@ -2,7 +2,7 @@ namespace Appwrite\Utopia; -use Utopia\View as OldView; +use Utopia\View\View as OldView; class View extends OldView { diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index 04f2dbd8c8..28c3227979 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -12,7 +12,7 @@ use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Datetime as DatetimeValidator; -use Utopia\Validator\JSON; +use Utopia\Http\Validator\JSON; trait DatabasesBase { @@ -2869,7 +2869,7 @@ trait DatabasesBase $this->assertEquals('Invalid document structure: Attribute "floatRange" has invalid format. Value must be a valid range between 1 and 1', $badFloatRange['body']['message']); $this->assertEquals('Invalid document structure: Attribute "probability" has invalid format. Value must be a valid range between 0 and 1', $badProbability['body']['message']); $this->assertEquals('Invalid document structure: Attribute "upperBound" has invalid format. Value must be a valid range between -9,223,372,036,854,775,808 and 10', $tooHigh['body']['message']); - $this->assertEquals('Invalid document structure: Attribute "lowerBound" has invalid format. Value must be a valid range between 5 and 9,223,372,036,854,775,807', $tooLow['body']['message']); + $this->assertEquals('Invalid document structure: Attribute "lowerBound" has invalid format. Value must be a valid range between 5 and '.\number_format(PHP_INT_MAX), $tooLow['body']['message']); } /** diff --git a/tests/e2e/Services/Databases/DatabasesCustomClientTest.php b/tests/e2e/Services/Databases/DatabasesCustomClientTest.php index 8484996058..9b7be063db 100644 --- a/tests/e2e/Services/Databases/DatabasesCustomClientTest.php +++ b/tests/e2e/Services/Databases/DatabasesCustomClientTest.php @@ -64,9 +64,10 @@ class DatabasesCustomClientTest extends Scope 'required' => true, ]); + $this->assertEquals(202, $response['headers']['status-code']); + sleep(1); - $this->assertEquals(202, $response['headers']['status-code']); // Document aliases write to update, delete $document1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/documents', array_merge([ @@ -82,6 +83,7 @@ class DatabasesCustomClientTest extends Scope ] ]); + $this->assertEquals(201, $document1['headers']['status-code']); $this->assertNotContains(Permission::create(Role::user($this->getUser()['$id'])), $document1['body']['$permissions']); $this->assertContains(Permission::update(Role::user($this->getUser()['$id'])), $document1['body']['$permissions']); $this->assertContains(Permission::delete(Role::user($this->getUser()['$id'])), $document1['body']['$permissions']); diff --git a/tests/e2e/Services/Databases/DatabasesPermissionsGuestTest.php b/tests/e2e/Services/Databases/DatabasesPermissionsGuestTest.php index ca8753f374..4288b92613 100644 --- a/tests/e2e/Services/Databases/DatabasesPermissionsGuestTest.php +++ b/tests/e2e/Services/Databases/DatabasesPermissionsGuestTest.php @@ -17,6 +17,14 @@ class DatabasesPermissionsGuestTest extends Scope use SideClient; use DatabasesPermissionsScope; + protected Authorization $auth; + + public function setUp(): void + { + parent::setUp(); + $this->auth = new Authorization(); + } + public function createCollection(): array { $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ @@ -111,8 +119,8 @@ class DatabasesPermissionsGuestTest extends Scope $this->assertEquals(201, $publicResponse['headers']['status-code']); $this->assertEquals(201, $privateResponse['headers']['status-code']); - $roles = Authorization::getRoles(); - Authorization::cleanRoles(); + $roles = $this->auth->getRoles(); + $this->auth->cleanRoles(); $publicDocuments = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $publicCollectionId . '/documents', [ 'content-type' => 'application/json', @@ -134,7 +142,7 @@ class DatabasesPermissionsGuestTest extends Scope } foreach ($roles as $role) { - Authorization::setRole($role); + $this->auth->addRole($role); } } @@ -145,8 +153,8 @@ class DatabasesPermissionsGuestTest extends Scope $privateCollectionId = $data['privateCollectionId']; $databaseId = $data['databaseId']; - $roles = Authorization::getRoles(); - Authorization::cleanRoles(); + $roles = $this->auth->getRoles(); + $this->auth->cleanRoles(); $publicResponse = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $publicCollectionId . '/documents', [ 'content-type' => 'application/json', @@ -222,7 +230,7 @@ class DatabasesPermissionsGuestTest extends Scope $this->assertEquals(401, $privateDocument['headers']['status-code']); foreach ($roles as $role) { - Authorization::setRole($role); + $this->auth->addRole($role); } } diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index 2d94b9f0e3..f9898757f7 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -7,12 +7,11 @@ use Utopia\CLI\Console; trait FunctionsBase { - protected string $stdout = ''; - protected string $stderr = ''; + protected string $output = ''; protected function packageCode($folder) { - Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr); + Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->output); } protected function awaitDeploymentIsBuilt($functionId, $deploymentId, $checkForSuccess = true): void diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 2958e6cb5f..dccbf2e544 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -10,12 +10,12 @@ use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideServer; -use Utopia\App; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Datetime as DatetimeValidator; +use Utopia\System\System; class FunctionsCustomServerTest extends Scope { @@ -476,7 +476,6 @@ class FunctionsCustomServerTest extends Scope * and ensure variable works as expected in execution. */ $this->assertEmpty($template['body']['variables']); - // Create function using settings from template. // Deployment is automatically created from template inside endpoint $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ @@ -504,7 +503,6 @@ class FunctionsCustomServerTest extends Scope $this->assertNotEmpty($function['body']['$id']); $functionId = $function['body']['$id']; - // List deployments so we can await deployment build $deployments = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments', [ 'content-type' => 'application/json', @@ -2464,7 +2462,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals($cookie, $response['body']); // Await Aggregation - sleep(App::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', 30)); + sleep(System::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', 30)); $tries = 0; while (true) { diff --git a/tests/e2e/Services/GraphQL/Base.php b/tests/e2e/Services/GraphQL/Base.php index 735e4eced5..b77f006bf8 100644 --- a/tests/e2e/Services/GraphQL/Base.php +++ b/tests/e2e/Services/GraphQL/Base.php @@ -2498,6 +2498,6 @@ trait Base protected function packageCode($folder): void { - Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr); + Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout); } } diff --git a/tests/e2e/Services/GraphQL/StorageClientTest.php b/tests/e2e/Services/GraphQL/StorageClientTest.php index 9896598c2d..7d6d617c87 100644 --- a/tests/e2e/Services/GraphQL/StorageClientTest.php +++ b/tests/e2e/Services/GraphQL/StorageClientTest.php @@ -183,7 +183,6 @@ class StorageClientTest extends Scope /** * @depends testCreateFile * @param $file - * @return array * @throws \Exception */ public function testGetFileDownload($file) diff --git a/tests/e2e/Services/GraphQL/StorageServerTest.php b/tests/e2e/Services/GraphQL/StorageServerTest.php index 7fea895b1c..6be103629e 100644 --- a/tests/e2e/Services/GraphQL/StorageServerTest.php +++ b/tests/e2e/Services/GraphQL/StorageServerTest.php @@ -232,7 +232,6 @@ class StorageServerTest extends Scope /** * @depends testCreateFile * @param $file - * @return array * @throws \Exception */ public function testGetFileDownload($file) diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index c41d861f1d..9fc862e85b 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -1691,7 +1691,7 @@ class ProjectsConsoleClientTest extends Scope ]); $this->assertEquals(400, $response['headers']['status-code']); - $this->assertEquals('Invalid `numbers` param: Value must a valid array no longer than 10 items and Phone number must start with a \'+\' can have a maximum of fifteen digits.', $response['body']['message']); + $this->assertEquals('Invalid `numbers` param: Value must a valid array no longer than 10 items', $response['body']['message']); /** * Test for success diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php index c3372b98c5..86aaee2ab0 100644 --- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php @@ -1272,11 +1272,10 @@ class RealtimeCustomClientTest extends Scope $this->assertNotEmpty($function['body']['$id']); $folder = 'timeout'; - $stderr = ''; - $stdout = ''; + $output = ''; $code = realpath(__DIR__ . '/../../../resources/functions') . "/{$folder}/code.tar.gz"; - Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/{$folder} && tar --exclude code.tar.gz -czf code.tar.gz .", '', $stdout, $stderr); + Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/{$folder} && tar --exclude code.tar.gz -czf code.tar.gz .", '', $output); $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', diff --git a/tests/e2e/Services/Storage/StorageCustomClientTest.php b/tests/e2e/Services/Storage/StorageCustomClientTest.php index c723fba50a..55340ab849 100644 --- a/tests/e2e/Services/Storage/StorageCustomClientTest.php +++ b/tests/e2e/Services/Storage/StorageCustomClientTest.php @@ -1089,7 +1089,7 @@ class StorageCustomClientTest extends Scope $this->assertEquals(200, $file['headers']['status-code']); - // Team 1 view success + // Team 2 view success $file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/view', [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1112,6 +1112,8 @@ class StorageCustomClientTest extends Scope 'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'permissions.png'), ]); + $this->assertEquals($file['headers']['status-code'], 401); + // Team 2 create failure $file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', [ 'content-type' => 'multipart/form-data', diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php index d2f132e960..54d55e5e68 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php @@ -486,11 +486,10 @@ class WebhooksCustomServerTest extends Scope /** * Test for SUCCESS */ - $stderr = ''; - $stdout = ''; + $output = ''; $folder = 'timeout'; $code = realpath(__DIR__ . '/../../../resources/functions') . "/{$folder}/code.tar.gz"; - Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/{$folder} && tar --exclude code.tar.gz -czf code.tar.gz .", '', $stdout, $stderr); + Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/{$folder} && tar --exclude code.tar.gz -czf code.tar.gz .", '', $output); $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', diff --git a/tests/resources/docker/docker-compose.yml b/tests/resources/docker/docker-compose.yml index a34b4fcf88..8f86fb8374 100644 --- a/tests/resources/docker/docker-compose.yml +++ b/tests/resources/docker/docker-compose.yml @@ -324,7 +324,7 @@ services: - MYSQL_USER=user - MYSQL_PASSWORD=password - MARIADB_AUTO_UPGRADE=1 - command: 'mysqld --innodb-flush-method=fsync' + command: 'mysqld --innodb-flush-method=fsync --max_connections=5000' maildev: image: djfarrelly/maildev diff --git a/tests/unit/Auth/AuthTest.php b/tests/unit/Auth/AuthTest.php index 705da42879..1ff5850402 100644 --- a/tests/unit/Auth/AuthTest.php +++ b/tests/unit/Auth/AuthTest.php @@ -3,6 +3,7 @@ namespace Tests\Unit\Auth; use Appwrite\Auth\Auth; +use Appwrite\Auth\Authentication; use PHPUnit\Framework\TestCase; use Utopia\Database\DateTime; use Utopia\Database\Document; @@ -13,21 +14,31 @@ use Utopia\Database\Validator\Roles; class AuthTest extends TestCase { + protected Authorization $auth; + protected Authentication $authentication; + + public function setUp(): void + { + parent::setUp(); + $this->auth = new Authorization(); + $this->authentication = new Authentication(); + } + /** * Reset Roles */ public function tearDown(): void { - Authorization::cleanRoles(); - Authorization::setRole(Role::any()->toString()); + $this->auth->cleanRoles(); + $this->auth->addRole(Role::any()->toString()); } public function testCookieName(): void { $name = 'cookie-name'; - $this->assertEquals(Auth::setCookieName($name), $name); - $this->assertEquals(Auth::$cookieName, $name); + $this->assertEquals($this->authentication->setCookieName($name), $name); + $this->assertEquals($this->authentication->getCookieName(), $name); } public function testEncodeDecodeSession(): void @@ -347,7 +358,7 @@ class AuthTest extends TestCase '$id' => '' ]); - $roles = Auth::getRoles($user); + $roles = Auth::getRoles($user, $this->auth); $this->assertCount(1, $roles); $this->assertContains(Role::guests()->toString(), $roles); } @@ -383,7 +394,7 @@ class AuthTest extends TestCase ] ]); - $roles = Auth::getRoles($user); + $roles = Auth::getRoles($user, $this->auth); $this->assertCount(13, $roles); $this->assertContains(Role::users()->toString(), $roles); @@ -404,21 +415,21 @@ class AuthTest extends TestCase $user['emailVerification'] = false; $user['phoneVerification'] = false; - $roles = Auth::getRoles($user); + $roles = Auth::getRoles($user, $this->auth); $this->assertContains(Role::users(Roles::DIMENSION_UNVERIFIED)->toString(), $roles); $this->assertContains(Role::user(ID::custom('123'), Roles::DIMENSION_UNVERIFIED)->toString(), $roles); // Enable single verification type $user['emailVerification'] = true; - $roles = Auth::getRoles($user); + $roles = Auth::getRoles($user, $this->auth); $this->assertContains(Role::users(Roles::DIMENSION_VERIFIED)->toString(), $roles); $this->assertContains(Role::user(ID::custom('123'), Roles::DIMENSION_VERIFIED)->toString(), $roles); } public function testPrivilegedUserRoles(): void { - Authorization::setRole(Auth::USER_ROLE_OWNER); + $this->auth->addRole(Auth::USER_ROLE_OWNER); $user = new Document([ '$id' => ID::custom('123'), 'emailVerification' => true, @@ -444,7 +455,7 @@ class AuthTest extends TestCase ] ]); - $roles = Auth::getRoles($user); + $roles = Auth::getRoles($user, $this->auth); $this->assertCount(7, $roles); $this->assertNotContains(Role::users()->toString(), $roles); @@ -462,7 +473,7 @@ class AuthTest extends TestCase public function testAppUserRoles(): void { - Authorization::setRole(Auth::USER_ROLE_APPS); + $this->auth->addRole(Auth::USER_ROLE_APPS); $user = new Document([ '$id' => ID::custom('123'), 'memberships' => [ @@ -486,7 +497,7 @@ class AuthTest extends TestCase ] ]); - $roles = Auth::getRoles($user); + $roles = Auth::getRoles($user, $this->auth); $this->assertCount(7, $roles); $this->assertNotContains(Role::users()->toString(), $roles); diff --git a/tests/unit/Event/EventTest.php b/tests/unit/Event/EventTest.php index dd9833378f..38534b9b16 100644 --- a/tests/unit/Event/EventTest.php +++ b/tests/unit/Event/EventTest.php @@ -66,9 +66,13 @@ class EventTest extends TestCase $this->assertEquals('eventValue1', $this->object->getParam('eventKey1')); $this->assertEquals('eventValue2', $this->object->getParam('eventKey2')); $this->assertEquals(null, $this->object->getParam('eventKey3')); - global $register; - $pools = $register->get('pools'); - $client = new Client($this->object->getQueue(), $pools->get('queue')->pop()->getResource()); + + global $registry; + $pools = $registry->get('pools'); + $dsn = $pools['pools-queue-queue']['dsn']; + $queue = new Queue\Connection\Redis($dsn->getHost(), $dsn->getPort()); + + $client = new Client($this->object->getQueue(), $queue); $this->assertEquals($client->getQueueSize(), 1); } diff --git a/tests/unit/GraphQL/BuilderTest.php b/tests/unit/GraphQL/BuilderTest.php index d79a104c90..f11045f318 100644 --- a/tests/unit/GraphQL/BuilderTest.php +++ b/tests/unit/GraphQL/BuilderTest.php @@ -4,8 +4,10 @@ namespace Tests\Unit\GraphQL; use Appwrite\GraphQL\Types\Mapper; use Appwrite\Utopia\Response; +use Appwrite\Utopia\Response\Models; use PHPUnit\Framework\TestCase; use Swoole\Http\Response as SwooleResponse; +use Utopia\Http\Adapter\Swoole\Response as UtopiaSwooleResponse; class BuilderTest extends TestCase { @@ -13,8 +15,9 @@ class BuilderTest extends TestCase public function setUp(): void { - $this->response = new Response(new SwooleResponse()); - Mapper::init($this->response->getModels()); + Models::init(); + $this->response = new Response(new UtopiaSwooleResponse(new SwooleResponse())); + Mapper::init(Models::getModels()); } /** @@ -22,7 +25,7 @@ class BuilderTest extends TestCase */ public function testCreateTypeMapping() { - $model = $this->response->getModel(Response::MODEL_COLLECTION); + $model = Models::getModel(Response::MODEL_COLLECTION); $type = Mapper::model(\ucfirst($model->getType())); $this->assertEquals('Collection', $type->name); } diff --git a/tests/unit/Messaging/MessagingChannelsTest.php b/tests/unit/Messaging/MessagingChannelsTest.php index 8ba0374093..77b3cee7d6 100644 --- a/tests/unit/Messaging/MessagingChannelsTest.php +++ b/tests/unit/Messaging/MessagingChannelsTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; +use Utopia\Database\Validator\Authorization as ValidatorAuthorization; class MessagingChannelsTest extends TestCase { @@ -34,8 +35,12 @@ class MessagingChannelsTest extends TestCase 'functions.1', ]; + protected ValidatorAuthorization $auth; + public function setUp(): void { + $this->auth = new ValidatorAuthorization(); + /** * Setup global Counts */ @@ -66,7 +71,7 @@ class MessagingChannelsTest extends TestCase ] ]); - $roles = Auth::getRoles($user); + $roles = Auth::getRoles($user, $this->auth); $parsedChannels = Realtime::convertChannels([0 => $channel], $user->getId()); @@ -90,7 +95,7 @@ class MessagingChannelsTest extends TestCase '$id' => '' ]); - $roles = Auth::getRoles($user); + $roles = Auth::getRoles($user, $this->auth); $parsedChannels = Realtime::convertChannels([0 => $channel], $user->getId()); diff --git a/tests/unit/Migration/MigrationTest.php b/tests/unit/Migration/MigrationTest.php index 536278d55b..8043ec9f94 100644 --- a/tests/unit/Migration/MigrationTest.php +++ b/tests/unit/Migration/MigrationTest.php @@ -36,7 +36,7 @@ abstract class MigrationTest extends TestCase */ public function testMigrationVersions(): void { - require_once __DIR__ . '/../../../app/init.php'; + //require_once __DIR__ . '/../../../app/init.php'; foreach (Migration::$versions as $class) { $this->assertTrue(class_exists('Appwrite\\Migration\\Version\\' . $class)); diff --git a/tests/unit/Utopia/RequestTest.php b/tests/unit/Utopia/RequestTest.php index 73daaa88bc..6124a7a0c1 100644 --- a/tests/unit/Utopia/RequestTest.php +++ b/tests/unit/Utopia/RequestTest.php @@ -7,7 +7,8 @@ use PHPUnit\Framework\TestCase; use Swoole\Http\Request as SwooleRequest; use Tests\Unit\Utopia\Request\Filters\First; use Tests\Unit\Utopia\Request\Filters\Second; -use Utopia\Route; +use Utopia\Http\Adapter\Swoole\Request as UtopiaSwooleRequest; +use Utopia\Http\Route; class RequestTest extends TestCase { @@ -15,7 +16,7 @@ class RequestTest extends TestCase public function setUp(): void { - $this->request = new Request(new SwooleRequest()); + $this->request = new Request(new UtopiaSwooleRequest(new SwooleRequest())); } public function testFilters(): void @@ -36,7 +37,7 @@ class RequestTest extends TestCase // set test header to prevent header populaten inside the request class $this->request->addHeader('EXAMPLE', 'VALUE'); $this->request->setRoute($route); - $this->request->setQueryString([ + $this->request->setQuery([ 'initial' => true, 'first' => false ]); diff --git a/tests/unit/Utopia/ResponseTest.php b/tests/unit/Utopia/ResponseTest.php index 452119fafb..67253e48a0 100644 --- a/tests/unit/Utopia/ResponseTest.php +++ b/tests/unit/Utopia/ResponseTest.php @@ -3,12 +3,14 @@ namespace Tests\Unit\Utopia; use Appwrite\Utopia\Response; +use Appwrite\Utopia\Response\Models; use Exception; use PHPUnit\Framework\TestCase; use Swoole\Http\Response as SwooleResponse; use Tests\Unit\Utopia\Response\Filters\First; use Tests\Unit\Utopia\Response\Filters\Second; use Utopia\Database\Document; +use Utopia\Http\Adapter\Swoole\Response as UtopiaSwooleResponse; class ResponseTest extends TestCase { @@ -16,10 +18,10 @@ class ResponseTest extends TestCase public function setUp(): void { - $this->response = new Response(new SwooleResponse()); - $this->response->setModel(new Single()); - $this->response->setModel(new Lists()); - $this->response->setModel(new Nested()); + $this->response = new Response(new UtopiaSwooleResponse(new SwooleResponse())); + Models::setModel(new Single()); + Models::setModel(new Lists()); + Models::setModel(new Nested()); } public function testFilters(): void From 579d3d7f14fbc23ed1be09bf89500b3313479a1f Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:39:43 -0400 Subject: [PATCH 006/101] chore: lint & updates --- app/cli.php | 12 +-- app/init/constants.php | 7 ++ composer.lock | 94 ++++++++++---------- src/Appwrite/Platform/Tasks/ScheduleBase.php | 2 +- 4 files changed, 61 insertions(+), 54 deletions(-) diff --git a/app/cli.php b/app/cli.php index 51a8e86137..7c2b73e966 100644 --- a/app/cli.php +++ b/app/cli.php @@ -103,12 +103,12 @@ $logError $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING); - try { - $responseCode = $logger->addLog($log); - Console::info('Error log pushed with status code: ' . $responseCode); - } catch (Throwable $th) { - Console::error('Error pushing log: ' . $th->getMessage()); - } + try { + $responseCode = $logger->addLog($log); + Console::info('Error log pushed with status code: ' . $responseCode); + } catch (Throwable $th) { + Console::error('Error pushing log: ' . $th->getMessage()); + } } Console::warning("Failed: {$error->getMessage()}"); diff --git a/app/init/constants.php b/app/init/constants.php index f67023ae3e..3d8217e75a 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -149,10 +149,17 @@ const METRIC_MESSAGES_TYPE_PROVIDER_FAILED = METRIC_MESSAGES . '.{type}.{provid const METRIC_SESSIONS = 'sessions'; const METRIC_DATABASES = 'databases'; const METRIC_COLLECTIONS = 'collections'; +const METRIC_DATABASES_STORAGE = 'databases.storage'; +const METRIC_DATABASES_STORAGE_DISK = 'databases.storage_disk'; const METRIC_DATABASE_ID_COLLECTIONS = '{databaseInternalId}.collections'; +const METRIC_DATABASE_ID_STORAGE = '{databaseInternalId}.databases.storage'; +const METRIC_DATABASE_ID_STORAGE_DISK = '{databaseInternalId}.databases.storage_disk'; + const METRIC_DOCUMENTS = 'documents'; const METRIC_DATABASE_ID_DOCUMENTS = '{databaseInternalId}.documents'; const METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS = '{databaseInternalId}.{collectionInternalId}.documents'; +const METRIC_DATABASE_ID_COLLECTION_ID_STORAGE = '{databaseInternalId}.{collectionInternalId}.databases.storage'; +const METRIC_DATABASE_ID_COLLECTION_ID_STORAGE_DISK = '{databaseInternalId}.{collectionInternalId}.databases.storage_disk'; const METRIC_BUCKETS = 'buckets'; const METRIC_FILES = 'files'; const METRIC_FILES_STORAGE = 'files.storage'; diff --git a/composer.lock b/composer.lock index cff13d28a1..4603450316 100644 --- a/composer.lock +++ b/composer.lock @@ -2126,16 +2126,16 @@ }, { "name": "utopia-php/logger", - "version": "0.6.0", + "version": "0.6.1", "source": { "type": "git", "url": "https://github.com/utopia-php/logger.git", - "reference": "a2d1daeeb8f61fdec6d851950d9a021a3d05c9f9" + "reference": "7e8ff512c6f04577aba1df67c7b9628971946f9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/logger/zipball/a2d1daeeb8f61fdec6d851950d9a021a3d05c9f9", - "reference": "a2d1daeeb8f61fdec6d851950d9a021a3d05c9f9", + "url": "https://api.github.com/repos/utopia-php/logger/zipball/7e8ff512c6f04577aba1df67c7b9628971946f9c", + "reference": "7e8ff512c6f04577aba1df67c7b9628971946f9c", "shasum": "" }, "require": { @@ -2174,9 +2174,9 @@ ], "support": { "issues": "https://github.com/utopia-php/logger/issues", - "source": "https://github.com/utopia-php/logger/tree/0.6.0" + "source": "https://github.com/utopia-php/logger/tree/0.6.1" }, - "time": "2024-05-23T13:37:54+00:00" + "time": "2024-09-20T14:02:12+00:00" }, { "name": "utopia-php/messaging", @@ -3410,16 +3410,16 @@ }, { "name": "laravel/pint", - "version": "v1.17.3", + "version": "v1.18.1", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "9d77be916e145864f10788bb94531d03e1f7b482" + "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/9d77be916e145864f10788bb94531d03e1f7b482", - "reference": "9d77be916e145864f10788bb94531d03e1f7b482", + "url": "https://api.github.com/repos/laravel/pint/zipball/35c00c05ec43e6b46d295efc0f4386ceb30d50d9", + "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9", "shasum": "" }, "require": { @@ -3472,7 +3472,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2024-09-03T15:00:28+00:00" + "time": "2024-09-24T17:22:50+00:00" }, { "name": "matthiasmullie/minify", @@ -3660,16 +3660,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.2.0", + "version": "v5.3.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb" + "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb", - "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3abf7425cd284141dc5d8d14a9ee444de3345d1a", + "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a", "shasum": "" }, "require": { @@ -3712,9 +3712,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.2.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.0" }, - "time": "2024-09-15T16:40:33+00:00" + "time": "2024-09-29T13:56:26+00:00" }, { "name": "phar-io/manifest", @@ -4281,16 +4281,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.30.1", + "version": "1.32.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "51b95ec8670af41009e2b2b56873bad96682413e" + "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/51b95ec8670af41009e2b2b56873bad96682413e", - "reference": "51b95ec8670af41009e2b2b56873bad96682413e", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6ca22b154efdd9e3c68c56f5d94670920a1c19a4", + "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4", "shasum": "" }, "require": { @@ -4322,9 +4322,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.30.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.32.0" }, - "time": "2024-09-07T20:13:05+00:00" + "time": "2024-09-26T07:23:32+00:00" }, { "name": "phpstan/phpstan", @@ -6020,16 +6020,16 @@ }, { "name": "symfony/console", - "version": "v7.1.4", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111" + "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/1eed7af6961d763e7832e874d7f9b21c3ea9c111", - "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111", + "url": "https://api.github.com/repos/symfony/console/zipball/0fa539d12b3ccf068a722bbbffa07ca7079af9ee", + "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee", "shasum": "" }, "require": { @@ -6093,7 +6093,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.4" + "source": "https://github.com/symfony/console/tree/v7.1.5" }, "funding": [ { @@ -6109,7 +6109,7 @@ "type": "tidelift" } ], - "time": "2024-08-15T22:48:53+00:00" + "time": "2024-09-20T08:28:38+00:00" }, { "name": "symfony/deprecation-contracts", @@ -6180,16 +6180,16 @@ }, { "name": "symfony/filesystem", - "version": "v7.1.2", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "92a91985250c251de9b947a14bb2c9390b1a562c" + "reference": "61fe0566189bf32e8cfee78335d8776f64a66f5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/92a91985250c251de9b947a14bb2c9390b1a562c", - "reference": "92a91985250c251de9b947a14bb2c9390b1a562c", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/61fe0566189bf32e8cfee78335d8776f64a66f5a", + "reference": "61fe0566189bf32e8cfee78335d8776f64a66f5a", "shasum": "" }, "require": { @@ -6226,7 +6226,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.1.2" + "source": "https://github.com/symfony/filesystem/tree/v7.1.5" }, "funding": [ { @@ -6242,7 +6242,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T10:03:55+00:00" + "time": "2024-09-17T09:16:35+00:00" }, { "name": "symfony/finder", @@ -6691,16 +6691,16 @@ }, { "name": "symfony/process", - "version": "v7.1.3", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca" + "reference": "5c03ee6369281177f07f7c68252a280beccba847" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/7f2f542c668ad6c313dc4a5e9c3321f733197eca", - "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca", + "url": "https://api.github.com/repos/symfony/process/zipball/5c03ee6369281177f07f7c68252a280beccba847", + "reference": "5c03ee6369281177f07f7c68252a280beccba847", "shasum": "" }, "require": { @@ -6732,7 +6732,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.1.3" + "source": "https://github.com/symfony/process/tree/v7.1.5" }, "funding": [ { @@ -6748,7 +6748,7 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:44:47+00:00" + "time": "2024-09-19T21:48:23+00:00" }, { "name": "symfony/service-contracts", @@ -6835,16 +6835,16 @@ }, { "name": "symfony/string", - "version": "v7.1.4", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b" + "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/6cd670a6d968eaeb1c77c2e76091c45c56bc367b", - "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b", + "url": "https://api.github.com/repos/symfony/string/zipball/d66f9c343fa894ec2037cc928381df90a7ad4306", + "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306", "shasum": "" }, "require": { @@ -6902,7 +6902,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.4" + "source": "https://github.com/symfony/string/tree/v7.1.5" }, "funding": [ { @@ -6918,7 +6918,7 @@ "type": "tidelift" } ], - "time": "2024-08-12T09:59:40+00:00" + "time": "2024-09-20T08:28:38+00:00" }, { "name": "textalk/websocket", diff --git a/src/Appwrite/Platform/Tasks/ScheduleBase.php b/src/Appwrite/Platform/Tasks/ScheduleBase.php index 8dc037897d..ada5e63b07 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleBase.php +++ b/src/Appwrite/Platform/Tasks/ScheduleBase.php @@ -166,7 +166,7 @@ abstract class ScheduleBase extends Action $total = $total + $sum; foreach ($results as $document) { - $localDocument = $this->schedules[$document->getInternalId()] ?? null; + $localDocument = $this->schedules[$document->getInternalId()] ?? null; // Check if resource has been updated since last sync $org = $localDocument !== null ? \strtotime($localDocument['resourceUpdatedAt']) : null; From 5e8ef5523dcc37eae70aa5184a4409af6de13165 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:08:47 -0400 Subject: [PATCH 007/101] Reapply "Fix auth injection" This reverts commit 904e4c86507a26369e409b033336adc3c8d71e09. --- app/controllers/api/avatars.php | 24 ++++++++++++------------ app/controllers/api/vcs.php | 30 +++++++++++++++--------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index 54ba096c5d..2b8327e898 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -604,9 +604,9 @@ Http::get('/v1/cards/cloud') ->inject('contributors') ->inject('employees') ->inject('logger') - ->inject('auth') - ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $auth) use ($getUserGitHub) { - $user = $auth->skip(fn () => $dbForConsole->getDocument('users', $userId)); + ->inject('authorization') + ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization) use ($getUserGitHub) { + $user = $authorization->skip(fn () => $dbForConsole->getDocument('users', $userId)); if ($user->isEmpty() && empty($mock)) { throw new Exception(Exception::USER_NOT_FOUND); @@ -617,7 +617,7 @@ Http::get('/v1/cards/cloud') $email = $user->getAttribute('email', ''); $createdAt = new \DateTime($user->getCreatedAt()); - $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger, $auth); + $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger, $authorization); $githubName = $gitHub['name'] ?? ''; $githubId = $gitHub['id'] ?? ''; @@ -812,9 +812,9 @@ Http::get('/v1/cards/cloud-back') ->inject('contributors') ->inject('employees') ->inject('logger') - ->inject('auth') - ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $auth) use ($getUserGitHub) { - $user = $auth->skip(fn () => $dbForConsole->getDocument('users', $userId)); + ->inject('authorization') + ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization) use ($getUserGitHub) { + $user = $authorization->skip(fn () => $dbForConsole->getDocument('users', $userId)); if ($user->isEmpty() && empty($mock)) { throw new Exception(Exception::USER_NOT_FOUND); @@ -824,7 +824,7 @@ Http::get('/v1/cards/cloud-back') $userId = $user->getId(); $email = $user->getAttribute('email', ''); - $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger, $auth); + $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger, $authorization); $githubId = $gitHub['id'] ?? ''; $isHero = \array_key_exists($email, $heroes); @@ -891,9 +891,9 @@ Http::get('/v1/cards/cloud-og') ->inject('contributors') ->inject('employees') ->inject('logger') - ->inject('auth') - ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $auth) use ($getUserGitHub) { - $user = $auth->skip(fn () => $dbForConsole->getDocument('users', $userId)); + ->inject('authorization') + ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization) use ($getUserGitHub) { + $user = $authorization->skip(fn () => $dbForConsole->getDocument('users', $userId)); if ($user->isEmpty() && empty($mock)) { throw new Exception(Exception::USER_NOT_FOUND); @@ -908,7 +908,7 @@ Http::get('/v1/cards/cloud-og') $email = $user->getAttribute('email', ''); $createdAt = new \DateTime($user->getCreatedAt()); - $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger, $auth); + $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger, $authorization); $githubName = $gitHub['name'] ?? ''; $githubId = $gitHub['id'] ?? ''; diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 987d84bb7e..ef325ee56b 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -900,9 +900,9 @@ Http::post('/v1/vcs/github/events') ->inject('dbForConsole') ->inject('getProjectDB') ->inject('queueForBuilds') - ->inject('auth') + ->inject('authorization') ->action( - function (GitHub $github, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds, Authorization $auth) use ($createGitDeployments) { + function (GitHub $github, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds, Authorization $authorization) use ($createGitDeployments) { $payload = $request->getRawPayload(); $signatureRemote = $request->getHeader('x-hub-signature-256', ''); $signatureLocal = System::getEnv('_APP_VCS_GITHUB_WEBHOOK_SECRET', ''); @@ -936,14 +936,14 @@ Http::post('/v1/vcs/github/events') $github->initializeVariables($providerInstallationId, $privateKey, $githubAppId); //find functionId from functions table - $repositories = $auth->skip(fn () => $dbForConsole->find('repositories', [ + $repositories = $authorization->skip(fn () => $dbForConsole->find('repositories', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::limit(100), ])); // create new deployment only on push and not when branch is created if (!$providerBranchCreated) { - $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForConsole, $queueForBuilds, $getProjectDB, $request, $auth); + $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForConsole, $queueForBuilds, $getProjectDB, $request, $authorization); } } elseif ($event == $github::EVENT_INSTALLATION) { if ($parsedPayload["action"] == "deleted") { @@ -956,13 +956,13 @@ Http::post('/v1/vcs/github/events') ]); foreach ($installations as $installation) { - $repositories = $auth->skip(fn () => $dbForConsole->find('repositories', [ + $repositories = $authorization->skip(fn () => $dbForConsole->find('repositories', [ Query::equal('installationInternalId', [$installation->getInternalId()]), Query::limit(1000) ])); foreach ($repositories as $repository) { - $auth->skip(fn () => $dbForConsole->deleteDocument('repositories', $repository->getId())); + $authorization->skip(fn () => $dbForConsole->deleteDocument('repositories', $repository->getId())); } $dbForConsole->deleteDocument('installations', $installation->getId()); @@ -994,12 +994,12 @@ Http::post('/v1/vcs/github/events') $providerCommitAuthor = $commitDetails["commitAuthor"] ?? ''; $providerCommitMessage = $commitDetails["commitMessage"] ?? ''; - $repositories = $auth->skip(fn () => $dbForConsole->find('repositories', [ + $repositories = $authorization->skip(fn () => $dbForConsole->find('repositories', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::orderDesc('$createdAt') ])); - $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForConsole, $queueForBuilds, $getProjectDB, $request, $auth); + $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForConsole, $queueForBuilds, $getProjectDB, $request, $authorization); } elseif ($parsedPayload["action"] == "closed") { // Allowed external contributions cleanup @@ -1008,7 +1008,7 @@ Http::post('/v1/vcs/github/events') $external = $parsedPayload["external"] ?? true; if ($external) { - $repositories = $auth->skip(fn () => $dbForConsole->find('repositories', [ + $repositories = $authorization->skip(fn () => $dbForConsole->find('repositories', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::orderDesc('$createdAt') ])); @@ -1019,7 +1019,7 @@ Http::post('/v1/vcs/github/events') if (\in_array($providerPullRequestId, $providerPullRequestIds)) { $providerPullRequestIds = \array_diff($providerPullRequestIds, [$providerPullRequestId]); $repository = $repository->setAttribute('providerPullRequestIds', $providerPullRequestIds); - $repository = $auth->skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository)); + $repository = $authorization->skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository)); } } } @@ -1172,15 +1172,15 @@ Http::patch('/v1/vcs/github/installations/:installationId/repositories/:reposito ->inject('dbForConsole') ->inject('getProjectDB') ->inject('queueForBuilds') - ->inject('auth') - ->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds, Authorization $auth) use ($createGitDeployments) { + ->inject('authorization') + ->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds, Authorization $authorization) use ($createGitDeployments) { $installation = $dbForConsole->getDocument('installations', $installationId); if ($installation->isEmpty()) { throw new Exception(Exception::INSTALLATION_NOT_FOUND); } - $repository = $auth->skip(fn () => $dbForConsole->getDocument('repositories', $repositoryId, [ + $repository = $authorization->skip(fn () => $dbForConsole->getDocument('repositories', $repositoryId, [ Query::equal('projectInternalId', [$project->getInternalId()]) ])); @@ -1197,7 +1197,7 @@ Http::patch('/v1/vcs/github/installations/:installationId/repositories/:reposito // TODO: Delete from array when PR is closed - $repository = $auth->skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository)); + $repository = $authorization->skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository)); $privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY'); $githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID'); @@ -1221,7 +1221,7 @@ Http::patch('/v1/vcs/github/installations/:installationId/repositories/:reposito $providerBranch = \explode(':', $pullRequestResponse['head']['label'])[1] ?? ''; $providerCommitHash = $pullRequestResponse['head']['sha'] ?? ''; - $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerCommitHash, $providerPullRequestId, true, $dbForConsole, $queueForBuilds, $getProjectDB, $request, $auth); + $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerCommitHash, $providerPullRequestId, true, $dbForConsole, $queueForBuilds, $getProjectDB, $request, $authorization); $response->noContent(); }); From d19f205207a19f894733c8f4284995493c898d33 Mon Sep 17 00:00:00 2001 From: Evan Date: Thu, 3 Oct 2024 14:38:03 -0700 Subject: [PATCH 008/101] Version Bump --- SECURITY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/SECURITY.md b/SECURITY.md index a17a55e368..d5901533fb 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -11,6 +11,7 @@ | 1.3.x | :white_check_mark: | | 1.4.x | :white_check_mark: | | 1.5.x | :white_check_mark: | +| 1.6.x | :white_check_mark: | ## Reporting a Vulnerability From 6080a363ee11fb6b3efdc5bd7428be0f8dc7b686 Mon Sep 17 00:00:00 2001 From: ItzNotABug Date: Fri, 4 Oct 2024 13:28:38 +0530 Subject: [PATCH 009/101] update: names. --- app/assets/security/10k-common-passwords | 4 ++++ app/controllers/api/avatars.php | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/assets/security/10k-common-passwords b/app/assets/security/10k-common-passwords index 487a4faf54..6902dfb81a 100644 --- a/app/assets/security/10k-common-passwords +++ b/app/assets/security/10k-common-passwords @@ -412,6 +412,7 @@ august sammy cool brian +brien platinum jake bronco @@ -1935,6 +1936,7 @@ panama lucy buffy brianna +brienna welcome1 vette blue22 @@ -3053,6 +3055,7 @@ randall abstr napster brian1 +brien1 bogart high hitler @@ -4123,6 +4126,7 @@ truman cubbies nitram briana +briena ebony kings warner diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index fcff3e4179..f6044dd624 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -609,9 +609,9 @@ App::get('/v1/cards/cloud') $isPlatinum = $user->getInternalId() % 100 === 0; } else { - $name = $mock === 'normal-long' ? 'Sir First Walter O\'Brian Junior' : 'Walter O\'Brian'; + $name = $mock === 'normal-long' ? 'Sir First Walter O\'Brien Junior' : 'Walter O\'Brien'; $createdAt = new \DateTime('now'); - $githubName = $mock === 'normal-no-github' ? '' : ($mock === 'normal-long' ? 'sir-first-walterobrian-junior' : 'walterobrian'); + $githubName = $mock === 'normal-no-github' ? '' : ($mock === 'normal-long' ? 'sir-first-walterobrien-junior' : 'walterobrien'); $isHero = $mock === 'hero'; $isContributor = $mock === 'contributor'; $isEmployee = \str_starts_with($mock, 'employee'); @@ -900,9 +900,9 @@ App::get('/v1/cards/cloud-og') } else { $bgVariation = \str_ends_with($mock, '-bg2') ? '2' : (\str_ends_with($mock, '-bg3') ? '3' : '1'); $cardVariation = \str_ends_with($mock, '-right') ? '2' : (\str_ends_with($mock, '-middle') ? '3' : '1'); - $name = \str_starts_with($mock, 'normal-long') ? 'Sir First Walter O\'Brian Junior' : 'Walter O\'Brian'; + $name = \str_starts_with($mock, 'normal-long') ? 'Sir First Walter O\'Brien Junior' : 'Walter O\'Brien'; $createdAt = new \DateTime('now'); - $githubName = $mock === 'normal-no-github' ? '' : (\str_starts_with($mock, 'normal-long') ? 'sir-first-walterobrian-junior' : 'walterobrian'); + $githubName = $mock === 'normal-no-github' ? '' : (\str_starts_with($mock, 'normal-long') ? 'sir-first-walterobrien-junior' : 'walterobrien'); $isHero = \str_starts_with($mock, 'hero'); $isContributor = \str_starts_with($mock, 'contributor'); $isEmployee = \str_starts_with($mock, 'employee'); From ac5bc22817a846b5f6b6df9ebf1e0684fbe21de8 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 4 Oct 2024 23:23:18 +0100 Subject: [PATCH 010/101] feat: ping endpoint --- app/config/collections.php | 22 ++++++++ app/controllers/general.php | 46 ++++++++++++++++ .../Utopia/Response/Model/Project.php | 12 ++++ tests/e2e/General/PingTest.php | 55 +++++++++++++++++++ 4 files changed, 135 insertions(+) create mode 100644 tests/e2e/General/PingTest.php diff --git a/app/config/collections.php b/app/config/collections.php index 1eb286cf8f..4a19915b1b 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -4541,6 +4541,28 @@ $consoleCollections = array_merge([ 'array' => false, 'filters' => [], ], + [ + '$id' => ID::custom('pingCount'), + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => false, + 'required' => false, + 'default' => 0, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('pingedAt'), + 'type' => Database::VAR_DATETIME, + 'format' => '', + 'size' => 0, + 'signed' => false, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['datetime'], + ] ], 'indexes' => [ [ diff --git a/app/controllers/general.php b/app/controllers/general.php index 04554a940e..7395a479a4 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -25,10 +25,12 @@ use Utopia\App; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Database\Database; +use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; +use Utopia\Database\Validator\UID; use Utopia\Domains\Domain; use Utopia\DSN\DSN; use Utopia\Locale\Locale; @@ -1048,6 +1050,50 @@ App::get('/.well-known/acme-challenge/*') include_once __DIR__ . '/shared/api.php'; include_once __DIR__ . '/shared/api/auth.php'; +App::get('/v1/ping') + ->groups(['api', 'general']) + ->desc('Test the connection between the Appwrite and the SDK.') + ->label('scope', 'public') + ->label('event', 'projects.[projectId].ping') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->inject('response') + ->inject('console') + ->inject('dbForConsole') + ->inject('queueForEvents') + ->action(function (string $projectId, Response $response, Document $project, Database $dbForConsole, Event $queueForEvents) { + if (empty($projectId) || $projectId === 'console') { + throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND); + } + + Console::log('Ping' . json_encode(['projectId' => $projectId], JSON_PRETTY_PRINT)); + + $project = Authorization::skip(function () use ($dbForConsole, $projectId) { + return $dbForConsole->getDocument('projects', $projectId); + }); + + if ($project->isEmpty()) { + Console::log('Ping' . json_encode($project, JSON_PRETTY_PRINT)); + throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND); + } + + $pingCount = $project->getAttribute('pingCount', 0) + 1; + $pingedAt = DateTime::now(); + + $project + ->setAttribute('pingCount', $pingCount) + ->setAttribute('pingedAt', $pingedAt); + + Authorization::skip(function () use ($dbForConsole, $project) { + $dbForConsole->updateDocument('projects', $project->getId(), $project); + }); + + $queueForEvents + ->setParam('projectId', $projectId) + ->setPayload($response->output($project, Response::MODEL_PROJECT)); + + $response->text('Pong!'); + }); + App::wildcard() ->groups(['api']) ->label('scope', 'global') diff --git a/src/Appwrite/Utopia/Response/Model/Project.php b/src/Appwrite/Utopia/Response/Model/Project.php index 80214aaa73..e1d0105587 100644 --- a/src/Appwrite/Utopia/Response/Model/Project.php +++ b/src/Appwrite/Utopia/Response/Model/Project.php @@ -234,6 +234,18 @@ class Project extends Model 'default' => '', 'example' => 'tls', ]) + ->addRule('pingCount', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Number of times the ping was received for this project.', + 'default' => 0, + 'example' => 1, + ]) + ->addRule('pingedAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'Last ping datetime in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) ; $services = Config::getParam('services', []); diff --git a/tests/e2e/General/PingTest.php b/tests/e2e/General/PingTest.php new file mode 100644 index 0000000000..c1199841fb --- /dev/null +++ b/tests/e2e/General/PingTest.php @@ -0,0 +1,55 @@ +client->call(Client::METHOD_GET, '/ping', [], [ + 'projectId' => $this->getProject()['$id'], + ]); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('Pong!', $response['body']); + + // With user session + $response = $this->client->call(Client::METHOD_GET, '/ping', $this->getHeaders(), [ + 'projectId' => $this->getProject()['$id'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('Pong!', $response['body']); + + // With API key + $response = $this->client->call(Client::METHOD_GET, '/ping', [ + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'projectId' => $this->getProject()['$id'], + ]); + + /** + * Test for FAILURE + */ + // Fake project ID + $response = $this->client->call(Client::METHOD_GET, '/ping', \array_merge([ + 'origin' => 'http://localhost', + ]), [ + 'projectId' => 'fake-project-id', + ]); + + $this->assertEquals(404, $response['headers']['status-code']); + $this->assertNotContains('Pong!', $response['body']); + } +} From 78eb1eec2582a3da01f755049032f6f2c08141c8 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 6 Oct 2024 14:15:31 +0300 Subject: [PATCH 011/101] originalId --- app/config/collections.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/config/collections.php b/app/config/collections.php index 1eb286cf8f..0b47862844 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -6094,6 +6094,17 @@ $dbCollections = [ 'array' => false, 'filters' => [], ], + [ + '$id' => ID::custom('originalId'), + 'type' => Database::VAR_STRING, + 'signed' => true, + 'size' => Database::LENGTH_KEY, + 'format' => '', + 'filters' => [], + 'required' => true, + 'default' => null, + 'array' => false, + ], ], 'indexes' => [ [ From 0a4b86214a6adc172ccd3a30beffe828d1c587f8 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 6 Oct 2024 17:20:10 +0300 Subject: [PATCH 012/101] originalId --- app/config/collections.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index 0b47862844..be78b06e92 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -2406,6 +2406,17 @@ $projectCollections = array_merge([ 'array' => false, 'filters' => [], ], + [ + '$id' => ID::custom('originalId'), + 'type' => Database::VAR_STRING, + 'signed' => true, + 'size' => Database::LENGTH_KEY, + 'format' => '', + 'filters' => [], + 'required' => true, + 'default' => null, + 'array' => false, + ], ], 'indexes' => [ [ @@ -6094,17 +6105,6 @@ $dbCollections = [ 'array' => false, 'filters' => [], ], - [ - '$id' => ID::custom('originalId'), - 'type' => Database::VAR_STRING, - 'signed' => true, - 'size' => Database::LENGTH_KEY, - 'format' => '', - 'filters' => [], - 'required' => true, - 'default' => null, - 'array' => false, - ], ], 'indexes' => [ [ From e87a66c426f9f6b7755d28cf409112f5c6605e5c Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 6 Oct 2024 17:29:35 +0300 Subject: [PATCH 013/101] originalId required false --- app/config/collections.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/collections.php b/app/config/collections.php index be78b06e92..cba3100922 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -2413,7 +2413,7 @@ $projectCollections = array_merge([ 'size' => Database::LENGTH_KEY, 'format' => '', 'filters' => [], - 'required' => true, + 'required' => false, 'default' => null, 'array' => false, ], From a7c0ef3d09627fcc1500f09ead612c80972be728 Mon Sep 17 00:00:00 2001 From: shimon Date: Sun, 6 Oct 2024 20:22:25 +0300 Subject: [PATCH 014/101] remove audits deletion --- app/controllers/api/databases.php | 4 ---- src/Appwrite/Platform/Workers/Databases.php | 17 ----------------- 2 files changed, 21 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 9c110647af..47a3468529 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -3733,10 +3733,6 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu ) ); - $queueForDeletes - ->setType(DELETE_TYPE_AUDIT) - ->setDocument($document); - $queueForEvents ->setParam('databaseId', $databaseId) ->setParam('collectionId', $collection->getId()) diff --git a/src/Appwrite/Platform/Workers/Databases.php b/src/Appwrite/Platform/Workers/Databases.php index 56f5f012e8..128e997066 100644 --- a/src/Appwrite/Platform/Workers/Databases.php +++ b/src/Appwrite/Platform/Workers/Databases.php @@ -492,8 +492,6 @@ class Databases extends Action }); $dbForProject->deleteCollection('database_' . $database->getInternalId()); - - $this->deleteAuditLogsByResource('database/' . $database->getId(), $project, $dbForProject); } /** @@ -549,23 +547,8 @@ class Databases extends Action Query::equal('databaseInternalId', [$databaseInternalId]), Query::equal('collectionInternalId', [$collectionInternalId]) ], $dbForProject); - - $this->deleteAuditLogsByResource('database/' . $databaseId . '/collection/' . $collectionId, $project, $dbForProject); } - /** - * @param string $resource - * @param Document $project - * @param Database $dbForProject - * @return void - * @throws Exception - */ - protected function deleteAuditLogsByResource(string $resource, Document $project, Database $dbForProject): void - { - $this->deleteByGroup(Audit::COLLECTION, [ - Query::equal('resource', [$resource]) - ], $dbForProject); - } /** * @param string $collection collectionID From e06b77cf397c45b18d8de24093f51c3bbd90d7e7 Mon Sep 17 00:00:00 2001 From: shimon Date: Sun, 6 Oct 2024 20:25:51 +0300 Subject: [PATCH 015/101] remove audits deletion --- src/Appwrite/Platform/Workers/Databases.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Appwrite/Platform/Workers/Databases.php b/src/Appwrite/Platform/Workers/Databases.php index 128e997066..f697e7be13 100644 --- a/src/Appwrite/Platform/Workers/Databases.php +++ b/src/Appwrite/Platform/Workers/Databases.php @@ -5,7 +5,6 @@ namespace Appwrite\Platform\Workers; use Appwrite\Event\Event; use Appwrite\Messaging\Adapter\Realtime; use Exception; -use Utopia\Audit\Audit; use Utopia\CLI\Console; use Utopia\Database\Database; use Utopia\Database\Document; From 424f24af04eeea8434788fcde5da559718499d90 Mon Sep 17 00:00:00 2001 From: shimon Date: Sun, 6 Oct 2024 21:02:24 +0300 Subject: [PATCH 016/101] remove resource --- app/controllers/api/databases.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 47a3468529..72e3c6d464 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -3656,11 +3656,10 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu ->inject('requestTimestamp') ->inject('response') ->inject('dbForProject') - ->inject('queueForDeletes') ->inject('queueForEvents') ->inject('queueForUsage') ->inject('mode') - ->action(function (string $databaseId, string $collectionId, string $documentId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Usage $queueForUsage, string $mode) { + ->action(function (string $databaseId, string $collectionId, string $documentId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, Usage $queueForUsage, string $mode) { $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); $isAPIKey = Auth::isAppUser(Authorization::getRoles()); From fa513d62429c6cd8b4de88f013e415f144e9518c Mon Sep 17 00:00:00 2001 From: shimon Date: Mon, 7 Oct 2024 11:06:08 +0300 Subject: [PATCH 017/101] debug --- app/controllers/api/projects.php | 15 +++++++++++++++ app/controllers/general.php | 6 ++++++ app/controllers/shared/api.php | 22 +++++++++++++++++++++- 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 934793410b..ca5d522e37 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -88,7 +88,11 @@ App::post('/v1/projects') ->inject('hooks') ->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Request $request, Response $response, Database $dbForConsole, Cache $cache, Group $pools, Hooks $hooks) { + $ctime = time(); + var_dump("[".$region."] - Before getDocument('teams')"); $team = $dbForConsole->getDocument('teams', $teamId); + $diff = time() - $ctime; + var_dump("[".$region."] - After getDocument('teams') : " . $diff . " sec"); if ($team->isEmpty()) { throw new Exception(Exception::TEAM_NOT_FOUND); @@ -145,6 +149,8 @@ App::post('/v1/projects') } try { + $ctime = time(); + var_dump("[".$region."] - Before createDocument('projects')"); $project = $dbForConsole->createDocument('projects', new Document([ '$id' => $projectId, '$permissions' => [ @@ -178,6 +184,10 @@ App::post('/v1/projects') 'search' => implode(' ', [$projectId, $name]), 'database' => $dsn, ])); + + $diff = time() - $ctime; + var_dump("[".$region."] - After createDocument('projects') : " . $diff . " sec"); + } catch (Duplicate) { throw new Exception(Exception::PROJECT_ALREADY_EXISTS); } @@ -229,7 +239,12 @@ App::post('/v1/projects') }, $collection['indexes']); try { + + $ctime = time(); + var_dump("[".$region."] - Before createCollection('".$key."')"); $dbForProject->createCollection($key, $attributes, $indexes); + $diff = time() - $ctime; + var_dump("[".$region."] - After createCollection('".$key."') : " . $diff . " sec"); } catch (Duplicate) { // Collection already exists } diff --git a/app/controllers/general.php b/app/controllers/general.php index 04554a940e..ecaf066176 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -451,6 +451,9 @@ App::init() ->inject('queueForEvents') ->inject('queueForCertificates') ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, Reader $geodb, Usage $queueForUsage, Event $queueForEvents, Certificate $queueForCertificates) { + $ctime = time(); + var_dump("[".$region."] - Before general first init hook"); + /* * Appwrite Router */ @@ -655,6 +658,9 @@ App::init() ) { throw new AppwriteException(AppwriteException::GENERAL_UNKNOWN_ORIGIN, $originValidator->getDescription()); } + + $diff = time() - $ctime; + var_dump("[".$region."] - After general first init hook : " . $diff . " sec"); }); App::options() diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index f0d896c95a..f72672a357 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -162,6 +162,9 @@ App::init() ->inject('mode') ->inject('team') ->action(function (App $utopia, Request $request, Database $dbForConsole, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team) { + $ctime = time(); + var_dump("[".$region."] - Before shared api first init hook"); + $route = $utopia->getRoute(); if ($project->isEmpty()) { @@ -344,6 +347,8 @@ App::init() throw new Exception(Exception::USER_MORE_FACTORS_REQUIRED); } } + $diff = time() - $ctime; + var_dump("[".$region."] - After shared api first init hook : " . $diff . " sec"); }); App::init() @@ -363,7 +368,8 @@ App::init() ->inject('dbForProject') ->inject('mode') ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode) use ($databaseListener) { - + $ctime = time(); + var_dump("[".$region."] - Before shared api second init hook"); $route = $utopia->getRoute(); if ( @@ -521,6 +527,8 @@ App::init() ; } } + $diff = time() - $ctime; + var_dump("[".$region."] - After shared api first init hook : " . $diff . " sec"); }); App::init() @@ -551,6 +559,10 @@ App::shutdown() ->inject('project') ->inject('dbForProject') ->action(function (App $utopia, Request $request, Response $response, Document $project, Database $dbForProject) { + $ctime = time(); + var_dump("[".$region."] - Before shared api first shutdown hook"); + $route = $utopia->getRoute(); + $sessionLimit = $project->getAttribute('auths', [])['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT; $session = $response->getPayload(); $userId = $session['userId'] ?? ''; @@ -575,6 +587,8 @@ App::shutdown() } $dbForProject->purgeCachedDocument('users', $userId); + $diff = time() - $ctime; + var_dump("[".$region."] - After shared api first shutdown hook : " . $diff . " sec"); }); App::shutdown() @@ -597,6 +611,10 @@ App::shutdown() ->inject('dbForConsole') ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Usage $queueForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Database $dbForProject, Func $queueForFunctions, string $mode, Database $dbForConsole) use ($parseLabel) { + $ctime = time(); + var_dump("[".$region."] - Before shared api second shutdown hook"); + $route = $utopia->getRoute(); + $responsePayload = $response->getPayload(); if (!empty($queueForEvents->getEvent())) { @@ -798,6 +816,8 @@ App::shutdown() } } } + $diff = time() - $ctime; + var_dump("[".$region."] - After shared api second shutdown hook : " . $diff . " sec"); }); App::init() From 4590ae11ee4df4761667968210a2210c412429a6 Mon Sep 17 00:00:00 2001 From: shimon Date: Mon, 7 Oct 2024 13:57:02 +0300 Subject: [PATCH 018/101] revert last commit --- app/controllers/api/projects.php | 15 --------------- app/controllers/general.php | 6 ------ app/controllers/shared/api.php | 22 +--------------------- 3 files changed, 1 insertion(+), 42 deletions(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index ca5d522e37..934793410b 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -88,11 +88,7 @@ App::post('/v1/projects') ->inject('hooks') ->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Request $request, Response $response, Database $dbForConsole, Cache $cache, Group $pools, Hooks $hooks) { - $ctime = time(); - var_dump("[".$region."] - Before getDocument('teams')"); $team = $dbForConsole->getDocument('teams', $teamId); - $diff = time() - $ctime; - var_dump("[".$region."] - After getDocument('teams') : " . $diff . " sec"); if ($team->isEmpty()) { throw new Exception(Exception::TEAM_NOT_FOUND); @@ -149,8 +145,6 @@ App::post('/v1/projects') } try { - $ctime = time(); - var_dump("[".$region."] - Before createDocument('projects')"); $project = $dbForConsole->createDocument('projects', new Document([ '$id' => $projectId, '$permissions' => [ @@ -184,10 +178,6 @@ App::post('/v1/projects') 'search' => implode(' ', [$projectId, $name]), 'database' => $dsn, ])); - - $diff = time() - $ctime; - var_dump("[".$region."] - After createDocument('projects') : " . $diff . " sec"); - } catch (Duplicate) { throw new Exception(Exception::PROJECT_ALREADY_EXISTS); } @@ -239,12 +229,7 @@ App::post('/v1/projects') }, $collection['indexes']); try { - - $ctime = time(); - var_dump("[".$region."] - Before createCollection('".$key."')"); $dbForProject->createCollection($key, $attributes, $indexes); - $diff = time() - $ctime; - var_dump("[".$region."] - After createCollection('".$key."') : " . $diff . " sec"); } catch (Duplicate) { // Collection already exists } diff --git a/app/controllers/general.php b/app/controllers/general.php index ecaf066176..04554a940e 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -451,9 +451,6 @@ App::init() ->inject('queueForEvents') ->inject('queueForCertificates') ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, Reader $geodb, Usage $queueForUsage, Event $queueForEvents, Certificate $queueForCertificates) { - $ctime = time(); - var_dump("[".$region."] - Before general first init hook"); - /* * Appwrite Router */ @@ -658,9 +655,6 @@ App::init() ) { throw new AppwriteException(AppwriteException::GENERAL_UNKNOWN_ORIGIN, $originValidator->getDescription()); } - - $diff = time() - $ctime; - var_dump("[".$region."] - After general first init hook : " . $diff . " sec"); }); App::options() diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index f72672a357..f0d896c95a 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -162,9 +162,6 @@ App::init() ->inject('mode') ->inject('team') ->action(function (App $utopia, Request $request, Database $dbForConsole, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team) { - $ctime = time(); - var_dump("[".$region."] - Before shared api first init hook"); - $route = $utopia->getRoute(); if ($project->isEmpty()) { @@ -347,8 +344,6 @@ App::init() throw new Exception(Exception::USER_MORE_FACTORS_REQUIRED); } } - $diff = time() - $ctime; - var_dump("[".$region."] - After shared api first init hook : " . $diff . " sec"); }); App::init() @@ -368,8 +363,7 @@ App::init() ->inject('dbForProject') ->inject('mode') ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode) use ($databaseListener) { - $ctime = time(); - var_dump("[".$region."] - Before shared api second init hook"); + $route = $utopia->getRoute(); if ( @@ -527,8 +521,6 @@ App::init() ; } } - $diff = time() - $ctime; - var_dump("[".$region."] - After shared api first init hook : " . $diff . " sec"); }); App::init() @@ -559,10 +551,6 @@ App::shutdown() ->inject('project') ->inject('dbForProject') ->action(function (App $utopia, Request $request, Response $response, Document $project, Database $dbForProject) { - $ctime = time(); - var_dump("[".$region."] - Before shared api first shutdown hook"); - $route = $utopia->getRoute(); - $sessionLimit = $project->getAttribute('auths', [])['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT; $session = $response->getPayload(); $userId = $session['userId'] ?? ''; @@ -587,8 +575,6 @@ App::shutdown() } $dbForProject->purgeCachedDocument('users', $userId); - $diff = time() - $ctime; - var_dump("[".$region."] - After shared api first shutdown hook : " . $diff . " sec"); }); App::shutdown() @@ -611,10 +597,6 @@ App::shutdown() ->inject('dbForConsole') ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Usage $queueForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Database $dbForProject, Func $queueForFunctions, string $mode, Database $dbForConsole) use ($parseLabel) { - $ctime = time(); - var_dump("[".$region."] - Before shared api second shutdown hook"); - $route = $utopia->getRoute(); - $responsePayload = $response->getPayload(); if (!empty($queueForEvents->getEvent())) { @@ -816,8 +798,6 @@ App::shutdown() } } } - $diff = time() - $ctime; - var_dump("[".$region."] - After shared api second shutdown hook : " . $diff . " sec"); }); App::init() From 6dab42e59a793b6b570b97dabb7fe5f9cc1712fc Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:00:19 +0100 Subject: [PATCH 019/101] chore: use project injectable --- app/controllers/general.php | 14 +------ tests/e2e/General/PingTest.php | 19 ++++----- .../Realtime/RealtimeConsoleClientTest.php | 40 +++++++++++++++++++ 3 files changed, 50 insertions(+), 23 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 7395a479a4..227745b028 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -1061,18 +1061,7 @@ App::get('/v1/ping') ->inject('dbForConsole') ->inject('queueForEvents') ->action(function (string $projectId, Response $response, Document $project, Database $dbForConsole, Event $queueForEvents) { - if (empty($projectId) || $projectId === 'console') { - throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND); - } - - Console::log('Ping' . json_encode(['projectId' => $projectId], JSON_PRETTY_PRINT)); - - $project = Authorization::skip(function () use ($dbForConsole, $projectId) { - return $dbForConsole->getDocument('projects', $projectId); - }); - if ($project->isEmpty()) { - Console::log('Ping' . json_encode($project, JSON_PRETTY_PRINT)); throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND); } @@ -1081,13 +1070,14 @@ App::get('/v1/ping') $project ->setAttribute('pingCount', $pingCount) - ->setAttribute('pingedAt', $pingedAt); + ->setAttribute('pingedAt', $pingedAt); Authorization::skip(function () use ($dbForConsole, $project) { $dbForConsole->updateDocument('projects', $project->getId(), $project); }); $queueForEvents + ->setProject($project) ->setParam('projectId', $projectId) ->setPayload($response->output($project, Response::MODEL_PROJECT)); diff --git a/tests/e2e/General/PingTest.php b/tests/e2e/General/PingTest.php index c1199841fb..96db658cc3 100644 --- a/tests/e2e/General/PingTest.php +++ b/tests/e2e/General/PingTest.php @@ -18,25 +18,24 @@ class PingTest extends Scope * Test for SUCCESS */ // Without user session - $response = $this->client->call(Client::METHOD_GET, '/ping', [], [ - 'projectId' => $this->getProject()['$id'], + $response = $this->client->call(Client::METHOD_GET, '/ping', [ + 'x-appwrite-project' => $this->getProject()['$id'], ]); $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals('Pong!', $response['body']); // With user session - $response = $this->client->call(Client::METHOD_GET, '/ping', $this->getHeaders(), [ - 'projectId' => $this->getProject()['$id'], - ]); + $response = $this->client->call(Client::METHOD_GET, '/ping', array_merge([ + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals('Pong!', $response['body']); // With API key $response = $this->client->call(Client::METHOD_GET, '/ping', [ + 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'projectId' => $this->getProject()['$id'], ]); /** @@ -44,10 +43,8 @@ class PingTest extends Scope */ // Fake project ID $response = $this->client->call(Client::METHOD_GET, '/ping', \array_merge([ - 'origin' => 'http://localhost', - ]), [ - 'projectId' => 'fake-project-id', - ]); + 'x-appwrite-project' => 'fake-project-id', + ], $this->getHeaders())); $this->assertEquals(404, $response['headers']['status-code']); $this->assertNotContains('Pong!', $response['body']); diff --git a/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php b/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php index 60c96c6e19..4bb9d8711e 100644 --- a/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php @@ -478,6 +478,46 @@ class RealtimeConsoleClientTest extends Scope $client->close(); } + public function testPing() + { + $client = $this->getWebsocket(['console'], [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], + ], 'console'); + + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertEquals('connected', $response['type']); + + fwrite(STDOUT, 'Project ID: ' . $this->getProject()['$id'] . "\n"); + + $pong = $this->client->call(Client::METHOD_GET, '/ping', [ + 'origin' => 'http://localhost', + 'x-appwrite-project' => $this->getProject()['$id'], + ]); + + $this->assertEquals(200, $pong['headers']['status-code']); + $this->assertEquals('Pong!', $pong['body']); + + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(1, $response['data']['channels']); + $this->assertContains('console', $response['data']['channels']); + $this->assertContains("projects.{$this->getProject()['$id']}", $response['data']['channels']); + $this->assertContains("projects.{$this->getProject()['$id']}.ping", $response['data']['events']); + $this->assertNotEmpty($response['data']['payload']); + $this->assertArrayHasKey('pingCount', $response['data']['payload']); + $this->assertArrayHasKey('pingedAt', $response['data']['payload']); + $this->assertEquals(1, $response['data']['payload']['pingCount']); + + $client->close(); + } + public function testCreateDeployment() { $response1 = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ From e0bd500aa2f3ea2c07b82da0bead285a0f1c645c Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:58:34 +0100 Subject: [PATCH 020/101] fix: realtime event --- app/controllers/general.php | 11 ++++------- src/Appwrite/Messaging/Adapter/Realtime.php | 6 ++++++ tests/e2e/General/PingTest.php | 3 +++ .../Services/Realtime/RealtimeConsoleClientTest.php | 4 +--- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 227745b028..bd49872436 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -30,7 +30,6 @@ use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; -use Utopia\Database\Validator\UID; use Utopia\Domains\Domain; use Utopia\DSN\DSN; use Utopia\Locale\Locale; @@ -1053,14 +1052,13 @@ include_once __DIR__ . '/shared/api/auth.php'; App::get('/v1/ping') ->groups(['api', 'general']) ->desc('Test the connection between the Appwrite and the SDK.') - ->label('scope', 'public') + ->label('scope', 'global') ->label('event', 'projects.[projectId].ping') - ->param('projectId', '', new UID(), 'Project unique ID.') ->inject('response') - ->inject('console') + ->inject('project') ->inject('dbForConsole') ->inject('queueForEvents') - ->action(function (string $projectId, Response $response, Document $project, Database $dbForConsole, Event $queueForEvents) { + ->action(function (Response $response, Document $project, Database $dbForConsole, Event $queueForEvents) { if ($project->isEmpty()) { throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND); } @@ -1077,8 +1075,7 @@ App::get('/v1/ping') }); $queueForEvents - ->setProject($project) - ->setParam('projectId', $projectId) + ->setParam('projectId', $project->getId()) ->setPayload($response->output($project, Response::MODEL_PROJECT)); $response->text('Pong!'); diff --git a/src/Appwrite/Messaging/Adapter/Realtime.php b/src/Appwrite/Messaging/Adapter/Realtime.php index d0d4a7c725..c437d4d487 100644 --- a/src/Appwrite/Messaging/Adapter/Realtime.php +++ b/src/Appwrite/Messaging/Adapter/Realtime.php @@ -270,6 +270,12 @@ class Realtime extends Adapter $projectId = 'console'; $roles = [Role::team($project->getAttribute('teamId'))->toString()]; break; + case 'projects': + $channels[] = 'console'; + $channels[] = 'projects.' . $parts[1]; + $projectId = 'console'; + $roles = [Role::team($project->getAttribute('teamId'))->toString()]; + break; case 'teams': if ($parts[2] === 'memberships') { $permissionsChanged = $parts[4] ?? false; diff --git a/tests/e2e/General/PingTest.php b/tests/e2e/General/PingTest.php index 96db658cc3..e41bac6736 100644 --- a/tests/e2e/General/PingTest.php +++ b/tests/e2e/General/PingTest.php @@ -38,6 +38,9 @@ class PingTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'], ]); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('Pong!', $response['body']); + /** * Test for FAILURE */ diff --git a/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php b/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php index 4bb9d8711e..0155d251f2 100644 --- a/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php @@ -490,8 +490,6 @@ class RealtimeConsoleClientTest extends Scope $this->assertArrayHasKey('type', $response); $this->assertEquals('connected', $response['type']); - fwrite(STDOUT, 'Project ID: ' . $this->getProject()['$id'] . "\n"); - $pong = $this->client->call(Client::METHOD_GET, '/ping', [ 'origin' => 'http://localhost', 'x-appwrite-project' => $this->getProject()['$id'], @@ -506,7 +504,7 @@ class RealtimeConsoleClientTest extends Scope $this->assertEquals('event', $response['type']); $this->assertNotEmpty($response['data']); $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(1, $response['data']['channels']); + $this->assertCount(2, $response['data']['channels']); $this->assertContains('console', $response['data']['channels']); $this->assertContains("projects.{$this->getProject()['$id']}", $response['data']['channels']); $this->assertContains("projects.{$this->getProject()['$id']}.ping", $response['data']['events']); From c06948b266e461b1e141338ed53fcb6126831d00 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 7 Oct 2024 12:57:07 -0400 Subject: [PATCH 021/101] fixes: phpstan --- src/Appwrite/Platform/Tasks/Specs.php | 10 ---------- .../Services/Functions/FunctionsCustomServerTest.php | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/Appwrite/Platform/Tasks/Specs.php b/src/Appwrite/Platform/Tasks/Specs.php index 9d361aa6c0..776410e4e4 100644 --- a/src/Appwrite/Platform/Tasks/Specs.php +++ b/src/Appwrite/Platform/Tasks/Specs.php @@ -34,16 +34,6 @@ class Specs extends Action return 'specs'; } - public function getRequest(): UtopiaRequest - { - return new AppwriteRequest(new SwooleRequest()); - } - - public function getResponse(): UtopiaResponse - { - return new AppwriteResponse(new SwooleResponse()); - } - public function __construct() { $this diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 59a3041a3f..908a1e9148 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -553,7 +553,7 @@ class FunctionsCustomServerTest extends Scope $folder = 'php-large'; $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr); + Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->output); $chunkSize = 5 * 1024 * 1024; $handle = @fopen($code, "rb"); From d1729404c1bb80e10a4829e21fb11b027b809421 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 7 Oct 2024 13:29:35 -0400 Subject: [PATCH 022/101] fix: adjusting test --- tests/e2e/Services/Functions/FunctionsCustomServerTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 908a1e9148..f605564726 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -1859,10 +1859,11 @@ class FunctionsCustomServerTest extends Scope 'x-appwrite-response-format' => '1.4.0', // Set response format for 1.4 syntax ], $this->getHeaders()), [ - 'queries' => [ 'equal("name", ["Test2"])' ] + 'queries' => [ + Query::equal('name', ['Test2'])->toString(), + ] ] ); - $this->assertEquals(200, $response['headers']['status-code']); $this->assertCount(1, $response['body']['functions']); $this->assertEquals('Test2', $response['body']['functions'][0]['name']); From 160b6c3386835e9e3c7826e732c04502e82c1bcb Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:11:39 -0400 Subject: [PATCH 023/101] chore: updates --- composer.json | 8 ++--- composer.lock | 88 ++++++++++++++++++++++++++++----------------------- 2 files changed, 52 insertions(+), 44 deletions(-) diff --git a/composer.json b/composer.json index 938b53639e..2bc63daaa2 100644 --- a/composer.json +++ b/composer.json @@ -47,13 +47,13 @@ "ext-sockets": "*", "appwrite/php-runtimes": "0.15.*", "appwrite/php-clamav": "2.0.*", - "utopia-php/abuse": "0.44.*", + "utopia-php/abuse": "0.46.*", "utopia-php/analytics": "0.13.*", - "utopia-php/audit": "0.44.*", + "utopia-php/audit": "0.46.*", "utopia-php/cache": "0.10.*", "utopia-php/cli": "0.19.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.54.*", + "utopia-php/database": "0.55.*", "utopia-php/domains": "0.6.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "1.0.*", @@ -62,7 +62,7 @@ "utopia-php/locale": "0.4.*", "utopia-php/logger": "0.6.*", "utopia-php/messaging": "0.12.*", - "utopia-php/migration": "0.5.*", + "utopia-php/migration": "0.7.*", "utopia-php/orchestration": "0.15.*", "utopia-php/platform": "0.8.*", "utopia-php/view": "0.2.*", diff --git a/composer.lock b/composer.lock index e540fc6c17..2fda8b29c4 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "7a1cc88504a554200880c4b2ba7cd6e5", + "content-hash": "99f1d21e82fb9944a2b83284f36b88b2", "packages": [ { "name": "adhocore/jwt", @@ -65,16 +65,16 @@ }, { "name": "appwrite/appwrite", - "version": "10.1.0", + "version": "11.1.0", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-for-php.git", - "reference": "da579af70723cfc117b5af84375bdef117e27312" + "reference": "1d043f543acdb17b9fdb440b1b2dd208e400bad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/da579af70723cfc117b5af84375bdef117e27312", - "reference": "da579af70723cfc117b5af84375bdef117e27312", + "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/1d043f543acdb17b9fdb440b1b2dd208e400bad3", + "reference": "1d043f543acdb17b9fdb440b1b2dd208e400bad3", "shasum": "" }, "require": { @@ -83,7 +83,8 @@ "php": ">=7.1.0" }, "require-dev": { - "phpunit/phpunit": "3.7.35" + "mockery/mockery": "^1.6.6", + "phpunit/phpunit": "^10" }, "type": "library", "autoload": { @@ -99,10 +100,10 @@ "support": { "email": "team@appwrite.io", "issues": "https://github.com/appwrite/sdk-for-php/issues", - "source": "https://github.com/appwrite/sdk-for-php/tree/10.1.0", + "source": "https://github.com/appwrite/sdk-for-php/tree/11.1.0", "url": "https://appwrite.io/support" }, - "time": "2023-11-20T09:56:12+00:00" + "time": "2024-06-26T07:03:23+00:00" }, { "name": "appwrite/php-clamav", @@ -1429,16 +1430,16 @@ }, { "name": "utopia-php/abuse", - "version": "0.44.0", + "version": "0.46.0", "source": { "type": "git", "url": "https://github.com/utopia-php/abuse.git", - "reference": "cd37bba23778f3a0dc54b4ba6ffb36d4d91ed8f1" + "reference": "ab09ecf6ceac5420020e6046ec651c155005d3c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/cd37bba23778f3a0dc54b4ba6ffb36d4d91ed8f1", - "reference": "cd37bba23778f3a0dc54b4ba6ffb36d4d91ed8f1", + "url": "https://api.github.com/repos/utopia-php/abuse/zipball/ab09ecf6ceac5420020e6046ec651c155005d3c3", + "reference": "ab09ecf6ceac5420020e6046ec651c155005d3c3", "shasum": "" }, "require": { @@ -1446,7 +1447,7 @@ "ext-pdo": "*", "ext-redis": "*", "php": ">=8.0", - "utopia-php/database": "0.54.*" + "utopia-php/database": "0.55.*" }, "require-dev": { "laravel/pint": "1.5.*", @@ -1474,9 +1475,9 @@ ], "support": { "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/0.44.0" + "source": "https://github.com/utopia-php/abuse/tree/0.46.0" }, - "time": "2024-09-05T16:09:32+00:00" + "time": "2024-10-07T19:09:29+00:00" }, { "name": "utopia-php/analytics", @@ -1527,21 +1528,21 @@ }, { "name": "utopia-php/audit", - "version": "0.44.0", + "version": "0.46.0", "source": { "type": "git", "url": "https://github.com/utopia-php/audit.git", - "reference": "69eee24e4d6cb8fdae31235d80b9a46b18092139" + "reference": "660bb322ca1e6a9fa121610a85930e65863e1e5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/audit/zipball/69eee24e4d6cb8fdae31235d80b9a46b18092139", - "reference": "69eee24e4d6cb8fdae31235d80b9a46b18092139", + "url": "https://api.github.com/repos/utopia-php/audit/zipball/660bb322ca1e6a9fa121610a85930e65863e1e5d", + "reference": "660bb322ca1e6a9fa121610a85930e65863e1e5d", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/database": "0.54.*" + "utopia-php/database": "0.55.*" }, "require-dev": { "laravel/pint": "1.5.*", @@ -1568,9 +1569,9 @@ ], "support": { "issues": "https://github.com/utopia-php/audit/issues", - "source": "https://github.com/utopia-php/audit/tree/0.44.0" + "source": "https://github.com/utopia-php/audit/tree/0.46.0" }, - "time": "2024-09-05T16:12:41+00:00" + "time": "2024-10-07T19:10:12+00:00" }, { "name": "utopia-php/cache", @@ -1726,16 +1727,16 @@ }, { "name": "utopia-php/database", - "version": "0.54.1", + "version": "0.55.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "c32d6eab5992c927cbf6fb4aad51d76fc5f64946" + "reference": "6f44079999491feb0ef85686f6b2ac7ef54db828" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/c32d6eab5992c927cbf6fb4aad51d76fc5f64946", - "reference": "c32d6eab5992c927cbf6fb4aad51d76fc5f64946", + "url": "https://api.github.com/repos/utopia-php/database/zipball/6f44079999491feb0ef85686f6b2ac7ef54db828", + "reference": "6f44079999491feb0ef85686f6b2ac7ef54db828", "shasum": "" }, "require": { @@ -1776,9 +1777,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.54.1" + "source": "https://github.com/utopia-php/database/tree/0.55.1" }, - "time": "2024-09-10T10:08:37+00:00" + "time": "2024-10-07T18:52:26+00:00" }, { "name": "utopia-php/di", @@ -2231,27 +2232,34 @@ }, { "name": "utopia-php/migration", - "version": "0.5.3", + "version": "0.7.0", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "b30e7834da69e25084b0c8e9ba29e4a7b54c6eb6" + "reference": "aa996ebfd3223fca2aa7d8b9aa31457c95344084" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/b30e7834da69e25084b0c8e9ba29e4a7b54c6eb6", - "reference": "b30e7834da69e25084b0c8e9ba29e4a7b54c6eb6", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/aa996ebfd3223fca2aa7d8b9aa31457c95344084", + "reference": "aa996ebfd3223fca2aa7d8b9aa31457c95344084", "shasum": "" }, "require": { - "appwrite/appwrite": "10.1.0", - "php": "8.*" + "appwrite/appwrite": "11.1.*", + "ext-curl": "*", + "ext-openssl": "*", + "php": "8.3.*", + "utopia-php/database": "0.55.*", + "utopia-php/dsn": "0.2.*", + "utopia-php/storage": "0.19.*" }, "require-dev": { - "laravel/pint": "1.*", - "phpunit/phpunit": "9.*", - "utopia-php/cli": "^0.18.0", - "vlucas/phpdotenv": "5.*" + "ext-pdo": "*", + "laravel/pint": "1.17.*", + "phpstan/phpstan": "1.11.*", + "phpunit/phpunit": "11.2.*", + "utopia-php/cli": "0.19.*", + "vlucas/phpdotenv": "5.6.*" }, "type": "library", "autoload": { @@ -2273,9 +2281,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/0.5.3" + "source": "https://github.com/utopia-php/migration/tree/0.7.0" }, - "time": "2024-09-10T10:45:18+00:00" + "time": "2024-10-07T19:00:18+00:00" }, { "name": "utopia-php/mongo", From 3e9a24232c4e6945996ec056db4b60bdf3628364 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:26:01 -0400 Subject: [PATCH 024/101] fix: introducing local variable --- app/controllers/api/account.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index a76b9dbea5..007435a5df 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1379,6 +1379,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') $email = $oauth2->getUserEmail($accessToken); // Check if this identity is connected to a different user + $sessionUpgrade = false; if (!$user->isEmpty()) { $userId = $user->getId(); @@ -1672,7 +1673,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') ->addCookie($authentication->getCookieName(), Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); } - if (isset($sessionUpgrade) && $sessionUpgrade) { + if ($sessionUpgrade) { foreach ($user->getAttribute('targets', []) as $target) { if ($target->getAttribute('providerType') !== MESSAGE_TYPE_PUSH) { continue; From 7efa5b59f5fec032993d7cf9b9f7d4e7ff60e72e Mon Sep 17 00:00:00 2001 From: Steven Nguyen <1477010+stnguyen90@users.noreply.github.com> Date: Thu, 3 Oct 2024 14:40:31 -0700 Subject: [PATCH 025/101] fix: update blr region name to the new preferred name --- app/config/regions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/regions.php b/app/config/regions.php index b40667ab5e..61029a9996 100644 --- a/app/config/regions.php +++ b/app/config/regions.php @@ -31,7 +31,7 @@ return [ ], 'blr' => [ '$id' => 'blr', - 'name' => 'Banglore', + 'name' => 'Bengaluru', 'disabled' => true, 'flag' => 'in', 'default' => true, From ab5632bfcd18f948d6de5e37ce48efeefbecd5af Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 7 Oct 2024 20:17:45 -0400 Subject: [PATCH 026/101] fix: wrong document check --- app/controllers/api/teams.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 6e6ca758c9..633a95c01e 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -463,17 +463,17 @@ Http::post('/v1/teams/:teamId/memberships') $name = empty($name) ? $invitee->getAttribute('name', '') : $name; } elseif (!empty($email)) { $invitee = $dbForProject->findOne('users', [Query::equal('email', [$email])]); // Get user by email address - if (!empty($invitee) && !empty($phone) && $invitee->getAttribute('phone', '') !== $phone) { + if ($invitee->isEmpty() && !empty($phone) && $invitee->getAttribute('phone', '') !== $phone) { throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given email and phone doesn\'t match', 409); } } elseif (!empty($phone)) { $invitee = $dbForProject->findOne('users', [Query::equal('phone', [$phone])]); - if (!empty($invitee) && !empty($email) && $invitee->getAttribute('email', '') !== $email) { + if ($invitee->isEmpty() && !empty($email) && $invitee->getAttribute('email', '') !== $email) { throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given phone and email doesn\'t match', 409); } } - if (empty($invitee)) { // Create new user if no user with same email found + if ($invitee->isEmpty()) { // Create new user if no user with same email found $limit = $project->getAttribute('auths', [])['limit'] ?? 0; if (!$isPrivilegedUser && !$isAppUser && $limit !== 0 && $project->getId() !== 'console') { // check users limit, console invites are allways allowed. @@ -525,6 +525,7 @@ Http::post('/v1/teams/:teamId/memberships') 'memberships' => null, 'search' => implode(' ', [$userId, $email, $name]), ]))); + } catch (Duplicate $th) { throw new Exception(Exception::USER_ALREADY_EXISTS); } @@ -572,6 +573,7 @@ Http::post('/v1/teams/:teamId/memberships') $dbForProject->purgeCachedDocument('users', $invitee->getId()); } else { try { + print_r($membership); $membership = $dbForProject->createDocument('memberships', $membership); } catch (Duplicate $th) { throw new Exception(Exception::TEAM_INVITE_ALREADY_EXISTS); From c34f8afa819c92de1d2c843f1aa46bba857eb0e7 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 7 Oct 2024 20:34:27 -0400 Subject: [PATCH 027/101] fix: wrong document check --- app/controllers/api/account.php | 36 +++++++++---------- app/controllers/api/functions.php | 2 +- app/controllers/api/migrations.php | 10 +++--- app/controllers/api/projects.php | 20 +++++------ app/controllers/api/proxy.php | 2 +- app/controllers/api/teams.php | 2 +- app/controllers/api/users.php | 12 +++---- app/controllers/api/vcs.php | 8 ++--- app/controllers/general.php | 4 +-- .../Platform/Workers/Certificates.php | 6 ++-- src/Appwrite/Platform/Workers/Messaging.php | 4 +-- 11 files changed, 53 insertions(+), 53 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 007435a5df..ea0c61a949 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -332,7 +332,7 @@ Http::post('/v1/account') $identityWithMatchingEmail = $dbForProject->findOne('identities', [ Query::equal('providerEmail', [$email]), ]); - if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { + if (!$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */ } @@ -395,7 +395,7 @@ Http::post('/v1/account') $existingTarget = $dbForProject->findOne('targets', [ Query::equal('identifier', [$email]), ]); - if ($existingTarget) { + if (!$existingTarget->isEmpty()) { $user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND); } } @@ -840,7 +840,7 @@ Http::post('/v1/account/sessions/email') Query::equal('email', [$email]), ]); - if (!$profile || empty($profile->getAttribute('passwordUpdate')) || !Auth::passwordVerify($password, $profile->getAttribute('password'), $profile->getAttribute('hash'), $profile->getAttribute('hashOptions'))) { + if ($profile->isEmpty() || empty($profile->getAttribute('passwordUpdate')) || !Auth::passwordVerify($password, $profile->getAttribute('password'), $profile->getAttribute('hash'), $profile->getAttribute('hashOptions'))) { throw new Exception(Exception::USER_INVALID_CREDENTIALS); } @@ -1387,7 +1387,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') Query::equal('providerEmail', [$email]), Query::notEqual('userInternalId', $user->getInternalId()), ]); - if (!empty($identityWithMatchingEmail)) { + if (!$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::USER_ALREADY_EXISTS); } @@ -1418,7 +1418,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') Query::equal('provider', [$provider]), Query::equal('providerUid', [$oauth2ID]), ]); - if ($session !== false && !$session->isEmpty()) { + if (!$session->isEmpty()) { $user->setAttributes($dbForProject->getDocument('users', $session->getAttribute('userId'))->getArrayCopy()); } } @@ -1436,7 +1436,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') $userWithEmail = $dbForProject->findOne('users', [ Query::equal('email', [$email]), ]); - if ($userWithEmail !== false && !$userWithEmail->isEmpty()) { + if (!$userWithEmail->isEmpty()) { $user->setAttributes($userWithEmail->getArrayCopy()); } @@ -1447,7 +1447,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') Query::equal('providerUid', [$oauth2ID]), ]); - if ($identity !== false && !$identity->isEmpty()) { + if (!$identity->isEmpty()) { $user = $dbForProject->getDocument('users', $identity->getAttribute('userId')); } } @@ -1467,7 +1467,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') $identityWithMatchingEmail = $dbForProject->findOne('identities', [ Query::equal('providerEmail', [$email]), ]); - if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { + if (!$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */ } @@ -1531,7 +1531,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') Query::equal('provider', [$provider]), Query::equal('providerUid', [$oauth2ID]), ]); - if ($identity === false || $identity->isEmpty()) { + if ($identity->isEmpty()) { // Before creating the identity, check if the email is already associated with another user $userId = $user->getId(); @@ -1816,7 +1816,7 @@ Http::post('/v1/account/tokens/magic-url') $isAppUser = Auth::isAppUser($roles); $result = $dbForProject->findOne('users', [Query::equal('email', [$email])]); - if ($result !== false && !$result->isEmpty()) { + if (!$result->isEmpty()) { $user->setAttributes($result->getArrayCopy()); } else { $limit = $project->getAttribute('auths', [])['limit'] ?? 0; @@ -1833,7 +1833,7 @@ Http::post('/v1/account/tokens/magic-url') $identityWithMatchingEmail = $dbForProject->findOne('identities', [ Query::equal('providerEmail', [$email]), ]); - if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { + if (!$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } @@ -2058,7 +2058,7 @@ Http::post('/v1/account/tokens/email') $isAppUser = Auth::isAppUser($roles); $result = $dbForProject->findOne('users', [Query::equal('email', [$email])]); - if ($result !== false && !$result->isEmpty()) { + if (!$result->isEmpty()) { $user->setAttributes($result->getArrayCopy()); } else { $limit = $project->getAttribute('auths', [])['limit'] ?? 0; @@ -2075,7 +2075,7 @@ Http::post('/v1/account/tokens/email') $identityWithMatchingEmail = $dbForProject->findOne('identities', [ Query::equal('providerEmail', [$email]), ]); - if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { + if (!$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */ } @@ -2350,7 +2350,7 @@ Http::post('/v1/account/tokens/phone') $isAppUser = Auth::isAppUser($roles); $result = $dbForProject->findOne('users', [Query::equal('phone', [$phone])]); - if ($result !== false && !$result->isEmpty()) { + if (!$result->isEmpty()) { $user->setAttributes($result->getArrayCopy()); } else { $limit = $project->getAttribute('auths', [])['limit'] ?? 0; @@ -2674,7 +2674,7 @@ Http::patch('/v1/account/email') Query::equal('providerEmail', [$email]), Query::notEqual('userInternalId', $user->getInternalId()), ]); - if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { + if (!$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */ } @@ -2695,7 +2695,7 @@ Http::patch('/v1/account/email') Query::equal('identifier', [$email]), ])); - if ($target instanceof Document && !$target->isEmpty()) { + if (!$target->isEmpty()) { throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); } @@ -2866,7 +2866,7 @@ Http::patch('/v1/account/phone') Query::equal('identifier', [$phone]), ])); - if ($target instanceof Document && !$target->isEmpty()) { + if (!$target->isEmpty()) { throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); } @@ -3027,7 +3027,7 @@ Http::post('/v1/account/recovery') Query::equal('email', [$email]), ]); - if (!$profile) { + if ($profile->isEmpty()) { throw new Exception(Exception::USER_NOT_FOUND); } diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index d0aa60ad06..77311c8a90 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -2297,7 +2297,7 @@ Http::delete('/v1/functions/:functionId/executions/:executionId') Query::equal('active', [true]), ]); - if ($schedule && !$schedule->isEmpty()) { + if (!$schedule->isEmpty()) { $schedule ->setAttribute('resourceUpdatedAt', DateTime::now()) ->setAttribute('active', false); diff --git a/app/controllers/api/migrations.php b/app/controllers/api/migrations.php index f7bd875e3f..5e08e197ae 100644 --- a/app/controllers/api/migrations.php +++ b/app/controllers/api/migrations.php @@ -120,7 +120,7 @@ Http::post('/v1/migrations/firebase/oauth') Query::equal('provider', ['firebase']), Query::equal('userInternalId', [$user->getInternalId()]), ]); - if ($identity === false || $identity->isEmpty()) { + if ($identity->isEmpty()) { throw new Exception(Exception::USER_IDENTITY_NOT_FOUND); } @@ -569,7 +569,7 @@ Http::get('/v1/migrations/firebase/report/oauth') Query::equal('userInternalId', [$user->getInternalId()]), ]); - if ($identity === false || $identity->isEmpty()) { + if ($identity->isEmpty()) { throw new Exception(Exception::USER_IDENTITY_NOT_FOUND); } @@ -744,7 +744,7 @@ Http::get('/v1/migrations/firebase/redirect') Query::equal('providerEmail', [$email]), ]); - if ($identity !== false && !$identity->isEmpty()) { + if (!$identity->isEmpty()) { if ($identity->getAttribute('userInternalId', '') !== $user->getInternalId()) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } @@ -813,7 +813,7 @@ Http::get('/v1/migrations/firebase/projects') Query::equal('userInternalId', [$user->getInternalId()]), ]); - if ($identity === false || $identity->isEmpty()) { + if ($identity->isEmpty()) { throw new Exception(Exception::USER_IDENTITY_NOT_FOUND); } @@ -893,7 +893,7 @@ Http::get('/v1/migrations/firebase/deauthorize') Query::equal('userInternalId', [$user->getInternalId()]), ]); - if ($identity === false || $identity->isEmpty()) { + if ($identity->isEmpty()) { throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN, 'Not authenticated with Firebase'); //TODO: Replace with USER_IDENTITY_NOT_FOUND } diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index c1b04836d6..8897752f2e 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -1077,7 +1077,7 @@ Http::get('/v1/projects/:projectId/webhooks/:webhookId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($webhook === false || $webhook->isEmpty()) { + if ($webhook->isEmpty()) { throw new Exception(Exception::WEBHOOK_NOT_FOUND); } @@ -1119,7 +1119,7 @@ Http::put('/v1/projects/:projectId/webhooks/:webhookId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($webhook === false || $webhook->isEmpty()) { + if ($webhook->isEmpty()) { throw new Exception(Exception::WEBHOOK_NOT_FOUND); } @@ -1168,7 +1168,7 @@ Http::patch('/v1/projects/:projectId/webhooks/:webhookId/signature') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($webhook === false || $webhook->isEmpty()) { + if ($webhook->isEmpty()) { throw new Exception(Exception::WEBHOOK_NOT_FOUND); } @@ -1205,7 +1205,7 @@ Http::delete('/v1/projects/:projectId/webhooks/:webhookId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($webhook === false || $webhook->isEmpty()) { + if ($webhook->isEmpty()) { throw new Exception(Exception::WEBHOOK_NOT_FOUND); } @@ -1324,7 +1324,7 @@ Http::get('/v1/projects/:projectId/keys/:keyId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($key === false || $key->isEmpty()) { + if ($key->isEmpty()) { throw new Exception(Exception::KEY_NOT_FOUND); } @@ -1360,7 +1360,7 @@ Http::put('/v1/projects/:projectId/keys/:keyId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($key === false || $key->isEmpty()) { + if ($key->isEmpty()) { throw new Exception(Exception::KEY_NOT_FOUND); } @@ -1401,7 +1401,7 @@ Http::delete('/v1/projects/:projectId/keys/:keyId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($key === false || $key->isEmpty()) { + if ($key->isEmpty()) { throw new Exception(Exception::KEY_NOT_FOUND); } @@ -1557,7 +1557,7 @@ Http::get('/v1/projects/:projectId/platforms/:platformId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($platform === false || $platform->isEmpty()) { + if ($platform->isEmpty()) { throw new Exception(Exception::PLATFORM_NOT_FOUND); } @@ -1594,7 +1594,7 @@ Http::put('/v1/projects/:projectId/platforms/:platformId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($platform === false || $platform->isEmpty()) { + if ($platform->isEmpty()) { throw new Exception(Exception::PLATFORM_NOT_FOUND); } @@ -1637,7 +1637,7 @@ Http::delete('/v1/projects/:projectId/platforms/:platformId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($platform === false || $platform->isEmpty()) { + if ($platform->isEmpty()) { throw new Exception(Exception::PLATFORM_NOT_FOUND); } diff --git a/app/controllers/api/proxy.php b/app/controllers/api/proxy.php index 3ca019c9ce..d4283691ae 100644 --- a/app/controllers/api/proxy.php +++ b/app/controllers/api/proxy.php @@ -63,7 +63,7 @@ Http::post('/v1/proxy/rules') Query::equal('domain', [$domain]), ]); - if ($document && !$document->isEmpty()) { + if (!$document->isEmpty()) { if ($document->getAttribute('projectId') === $project->getId()) { $resourceType = $document->getAttribute('resourceType'); $resourceId = $document->getAttribute('resourceId'); diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 633a95c01e..5734e96250 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -473,7 +473,7 @@ Http::post('/v1/teams/:teamId/memberships') } } - if ($invitee->isEmpty()) { // Create new user if no user with same email found + if (!isset($invitee) || $invitee->isEmpty()) { // Create new user if no user with same email found $limit = $project->getAttribute('auths', [])['limit'] ?? 0; if (!$isPrivilegedUser && !$isAppUser && $limit !== 0 && $project->getId() !== 'console') { // check users limit, console invites are allways allowed. diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 3d080fd98e..9f33ee38db 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -63,7 +63,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e $identityWithMatchingEmail = $dbForProject->findOne('identities', [ Query::equal('providerEmail', [$email]), ]); - if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { + if (!$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } } @@ -140,7 +140,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e $existingTarget = $dbForProject->findOne('targets', [ Query::equal('identifier', [$email]), ]); - if ($existingTarget) { + if (!$existingTarget->isEmpty()) { $user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND); } } @@ -164,7 +164,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e $existingTarget = $dbForProject->findOne('targets', [ Query::equal('identifier', [$phone]), ]); - if ($existingTarget) { + if (!$existingTarget->isEmpty()) { $user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND); } } @@ -1215,7 +1215,7 @@ Http::patch('/v1/users/:userId/email') Query::equal('providerEmail', [$email]), Query::notEqual('userInternalId', $user->getInternalId()), ]); - if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { + if (!$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } @@ -1223,7 +1223,7 @@ Http::patch('/v1/users/:userId/email') Query::equal('identifier', [$email]), ]); - if ($target instanceof Document && !$target->isEmpty()) { + if (!$target->isEmpty()) { throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); } } @@ -1313,7 +1313,7 @@ Http::patch('/v1/users/:userId/phone') Query::equal('identifier', [$number]), ]); - if ($target instanceof Document && !$target->isEmpty()) { + if (!$target->isEmpty()) { throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); } } diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index ef325ee56b..1f97e4f2d5 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -108,7 +108,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId Query::orderDesc('$createdAt'), ])); - if ($latestComment !== false && !$latestComment->isEmpty()) { + if (!$latestComment->isEmpty()) { $latestCommentId = $latestComment->getAttribute('providerCommentId', ''); $comment = new Comment(); $comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId)); @@ -369,7 +369,7 @@ Http::get('/v1/vcs/github/callback') $identity = $dbForConsole->findOne('identities', [ Query::equal('providerEmail', [$email]), ]); - if ($identity !== false && !$identity->isEmpty()) { + if (!$identity->isEmpty()) { if ($identity->getAttribute('userInternalId', '') !== $user->getInternalId()) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } @@ -416,7 +416,7 @@ Http::get('/v1/vcs/github/callback') Query::equal('projectInternalId', [$projectInternalId]) ]); - if ($installation === false || $installation->isEmpty()) { + if ($installation->isEmpty()) { $teamId = $project->getAttribute('teamId', ''); $installation = new Document([ @@ -724,7 +724,7 @@ Http::post('/v1/vcs/github/installations/:installationId/providerRepositories') Query::equal('provider', ['github']), Query::equal('userInternalId', [$user->getInternalId()]), ]); - if ($identity === false || $identity->isEmpty()) { + if ($identity->isEmpty()) { throw new Exception(Exception::USER_IDENTITY_NOT_FOUND); } diff --git a/app/controllers/general.php b/app/controllers/general.php index 1e403ef93a..70cf55d280 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -520,7 +520,7 @@ Http::init() $mainDomain = $envDomain; } else { $domainDocument = $dbForConsole->findOne('rules', [Query::orderAsc('$id')]); - $mainDomain = $domainDocument ? $domainDocument->getAttribute('domain') : $domain->get(); + $mainDomain = !$domainDocument->isEmpty() ? $domainDocument->getAttribute('domain') : $domain->get(); } if ($mainDomain !== $domain->get()) { @@ -530,7 +530,7 @@ Http::init() Query::equal('domain', [$domain->get()]) ]); - if (!$domainDocument) { + if ($domainDocument->isEmpty()) { $domainDocument = new Document([ 'domain' => $domain->get(), 'resourceType' => 'api', diff --git a/src/Appwrite/Platform/Workers/Certificates.php b/src/Appwrite/Platform/Workers/Certificates.php index 4a8d928ba2..07e1dcdaa6 100644 --- a/src/Appwrite/Platform/Workers/Certificates.php +++ b/src/Appwrite/Platform/Workers/Certificates.php @@ -126,7 +126,7 @@ class Certificates extends Action $certificate = $dbForConsole->findOne('certificates', [Query::equal('domain', [$domain->get()])]); // If we don't have certificate for domain yet, let's create new document. At the end we save it - if (!$certificate) { + if ($certificate->isEmpty()) { $certificate = new Document(); $certificate->setAttribute('domain', $domain->get()); } @@ -216,7 +216,7 @@ class Certificates extends Action { // Check if update or insert required $certificateDocument = $dbForConsole->findOne('certificates', [Query::equal('domain', [$domain])]); - if (!empty($certificateDocument) && !$certificateDocument->isEmpty()) { + if (!$certificateDocument->isEmpty()) { // Merge new data with current data $certificate = new Document(\array_merge($certificateDocument->getArrayCopy(), $certificate->getArrayCopy())); $certificate = $dbForConsole->updateDocument('certificates', $certificate->getId(), $certificate); @@ -478,7 +478,7 @@ class Certificates extends Action Query::equal('domain', [$domain]), ]); - if ($rule !== false && !$rule->isEmpty()) { + if (!$rule->isEmpty()) { $rule->setAttribute('certificateId', $certificateId); $rule->setAttribute('status', $success ? 'verified' : 'unverified'); $dbForConsole->updateDocument('rules', $rule->getId(), $rule); diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 510fec0431..ddb72803e3 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -178,7 +178,7 @@ class Messaging extends Action Query::equal('type', [$providerType]), ]); - if ($default === false || $default->isEmpty()) { + if ($default->isEmpty()) { $dbForProject->updateDocument('messages', $message->getId(), $message->setAttributes([ 'status' => MessageStatus::FAILED, 'deliveryErrors' => ['No enabled provider found.'] @@ -275,7 +275,7 @@ class Messaging extends Action Query::equal('identifier', [$result['recipient']]) ]); - if ($target instanceof Document && !$target->isEmpty()) { + if (!$target->isEmpty()) { $dbForProject->updateDocument( 'targets', $target->getId(), From 60e0554fdf0c4622bc3220d65d26403ae5358673 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 7 Oct 2024 20:41:44 -0400 Subject: [PATCH 028/101] fix: remove leftover print_r --- app/controllers/api/teams.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 5734e96250..8f5fba30ab 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -573,7 +573,6 @@ Http::post('/v1/teams/:teamId/memberships') $dbForProject->purgeCachedDocument('users', $invitee->getId()); } else { try { - print_r($membership); $membership = $dbForProject->createDocument('memberships', $membership); } catch (Duplicate $th) { throw new Exception(Exception::TEAM_INVITE_ALREADY_EXISTS); From 40266481e0232f6162b4018270fa211667fed8f3 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 7 Oct 2024 20:48:55 -0400 Subject: [PATCH 029/101] chore: package update --- composer.json | 2 +- composer.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 2bc63daaa2..135822bc9e 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,7 @@ "utopia-php/cache": "0.10.*", "utopia-php/cli": "0.19.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.55.*", + "utopia-php/database": "0.55.1", "utopia-php/domains": "0.6.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "1.0.*", diff --git a/composer.lock b/composer.lock index 2fda8b29c4..cb468c1663 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "99f1d21e82fb9944a2b83284f36b88b2", + "content-hash": "b808f001c3ff4c0396cf51f46e8b314a", "packages": [ { "name": "adhocore/jwt", From 25650e7501b8dba8a2a3a5de96283dd4f2e32b89 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 7 Oct 2024 21:01:59 -0400 Subject: [PATCH 030/101] chore: trigger ci From 61dba4eefb77b67e7f61befb9a0de331c5771741 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 7 Oct 2024 21:16:31 -0400 Subject: [PATCH 031/101] revert: reverting commit 39490e9 --- .github/workflows/tests.yml | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6e7012527d..3ca3c9c833 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,22 +16,22 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v3 with: submodules: recursive - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v2 - name: Build Appwrite - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v3 with: context: . push: false tags: ${{ env.IMAGE }} load: true - cache-from: type=gha,scope=appwrite - cache-to: type=gha,mode=max,scope=appwrite + cache-from: type=gha + cache-to: type=gha,mode=max outputs: type=docker,dest=/tmp/${{ env.IMAGE }}.tar build-args: | DEBUG=false @@ -39,13 +39,12 @@ jobs: VERSION=dev - name: Cache Docker Image - uses: actions/cache@v4 + uses: actions/cache@v3 with: key: ${{ env.CACHE_KEY }} - restore-keys: | - appwrite-dev- path: /tmp/${{ env.IMAGE }}.tar + unit_test: name: Unit Test runs-on: ubuntu-latest @@ -53,10 +52,10 @@ jobs: steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v3 - name: Load Cache - uses: actions/cache@v4 + uses: actions/cache@v3 with: key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar @@ -83,10 +82,10 @@ jobs: needs: setup steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v3 - name: Load Cache - uses: actions/cache@v4 + uses: actions/cache@v3 with: key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar @@ -131,10 +130,10 @@ jobs: steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v3 - name: Load Cache - uses: actions/cache@v4 + uses: actions/cache@v3 with: key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar @@ -158,9 +157,9 @@ jobs: needs: setup steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v3 - name: Load Cache - uses: actions/cache@v4 + uses: actions/cache@v3 with: key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar From be1cada848d36dae0994ac7047b2e25a9843a0cc Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 8 Oct 2024 14:17:34 +1300 Subject: [PATCH 032/101] Fix specs --- app/config/specs/open-api3-1.6.x-client.json | 2 +- app/config/specs/open-api3-1.6.x-console.json | 2 +- app/config/specs/open-api3-1.6.x-server.json | 2 +- app/config/specs/swagger2-1.6.x-client.json | 114 +++++++++--------- app/config/specs/swagger2-1.6.x-console.json | 114 +++++++++--------- app/config/specs/swagger2-1.6.x-server.json | 114 +++++++++--------- app/controllers/api/functions.php | 2 +- 7 files changed, 166 insertions(+), 184 deletions(-) diff --git a/app/config/specs/open-api3-1.6.x-client.json b/app/config/specs/open-api3-1.6.x-client.json index 8a9967090d..c655ef3581 100644 --- a/app/config/specs/open-api3-1.6.x-client.json +++ b/app/config/specs/open-api3-1.6.x-client.json @@ -5084,7 +5084,7 @@ "body": { "type": "string", "description": "HTTP body of execution. Default value is empty string.", - "x-example": null + "x-example": "" }, "async": { "type": "boolean", diff --git a/app/config/specs/open-api3-1.6.x-console.json b/app/config/specs/open-api3-1.6.x-console.json index 07749889d8..667a88effc 100644 --- a/app/config/specs/open-api3-1.6.x-console.json +++ b/app/config/specs/open-api3-1.6.x-console.json @@ -11054,7 +11054,7 @@ "body": { "type": "string", "description": "HTTP body of execution. Default value is empty string.", - "x-example": null + "x-example": "" }, "async": { "type": "boolean", diff --git a/app/config/specs/open-api3-1.6.x-server.json b/app/config/specs/open-api3-1.6.x-server.json index ef41688ca8..74f22a7e40 100644 --- a/app/config/specs/open-api3-1.6.x-server.json +++ b/app/config/specs/open-api3-1.6.x-server.json @@ -9941,7 +9941,7 @@ "body": { "type": "string", "description": "HTTP body of execution. Default value is empty string.", - "x-example": null + "x-example": "" }, "async": { "type": "boolean", diff --git a/app/config/specs/swagger2-1.6.x-client.json b/app/config/specs/swagger2-1.6.x-client.json index ce9ea857bb..0f9b3bef2f 100644 --- a/app/config/specs/swagger2-1.6.x-client.json +++ b/app/config/specs/swagger2-1.6.x-client.json @@ -5167,7 +5167,7 @@ "summary": "Create execution", "operationId": "functionsCreateExecution", "consumes": [ - "multipart\/form-data" + "application\/json" ], "produces": [ "multipart\/form-data" @@ -5226,65 +5226,59 @@ "in": "path" }, { - "name": "body", - "description": "HTTP body of execution. Default value is empty string.", - "required": false, - "type": "payload", - "default": "", - "in": "formData" - }, - { - "name": "async", - "description": "Execute code in the background. Default value is false.", - "required": false, - "type": "boolean", - "x-example": false, - "default": false, - "in": "formData" - }, - { - "name": "path", - "description": "HTTP path of execution. Path can include query params. Default value is \/", - "required": false, - "type": "string", - "x-example": "", - "default": "\/", - "in": "formData" - }, - { - "name": "method", - "description": "HTTP method of execution. Default value is GET.", - "required": false, - "type": "string", - "x-example": "GET", - "enum": [ - "GET", - "POST", - "PUT", - "PATCH", - "DELETE", - "OPTIONS" - ], - "x-enum-name": "ExecutionMethod", - "x-enum-keys": [], - "default": "POST", - "in": "formData" - }, - { - "name": "headers", - "description": "HTTP headers of execution. Defaults to empty.", - "required": false, - "type": "object", - "default": [], - "x-example": "{}", - "in": "formData" - }, - { - "name": "scheduledAt", - "description": "Scheduled execution time in [ISO 8601](https:\/\/www.iso.org\/iso-8601-date-and-time-format.html) format. DateTime value must be in future with precision in minutes.", - "required": false, - "type": "string", - "in": "formData" + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "body": { + "type": "string", + "description": "HTTP body of execution. Default value is empty string.", + "default": "", + "x-example": "" + }, + "async": { + "type": "boolean", + "description": "Execute code in the background. Default value is false.", + "default": false, + "x-example": false + }, + "path": { + "type": "string", + "description": "HTTP path of execution. Path can include query params. Default value is \/", + "default": "\/", + "x-example": "" + }, + "method": { + "type": "string", + "description": "HTTP method of execution. Default value is GET.", + "default": "POST", + "x-example": "GET", + "enum": [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE", + "OPTIONS" + ], + "x-enum-name": "ExecutionMethod", + "x-enum-keys": [] + }, + "headers": { + "type": "object", + "description": "HTTP headers of execution. Defaults to empty.", + "default": [], + "x-example": "{}" + }, + "scheduledAt": { + "type": "string", + "description": "Scheduled execution time in [ISO 8601](https:\/\/www.iso.org\/iso-8601-date-and-time-format.html) format. DateTime value must be in future with precision in minutes.", + "default": null, + "x-example": null + } + } + } } ] } diff --git a/app/config/specs/swagger2-1.6.x-console.json b/app/config/specs/swagger2-1.6.x-console.json index 51935a5e01..658718901d 100644 --- a/app/config/specs/swagger2-1.6.x-console.json +++ b/app/config/specs/swagger2-1.6.x-console.json @@ -11131,7 +11131,7 @@ "summary": "Create execution", "operationId": "functionsCreateExecution", "consumes": [ - "multipart\/form-data" + "application\/json" ], "produces": [ "multipart\/form-data" @@ -11190,65 +11190,59 @@ "in": "path" }, { - "name": "body", - "description": "HTTP body of execution. Default value is empty string.", - "required": false, - "type": "payload", - "default": "", - "in": "formData" - }, - { - "name": "async", - "description": "Execute code in the background. Default value is false.", - "required": false, - "type": "boolean", - "x-example": false, - "default": false, - "in": "formData" - }, - { - "name": "path", - "description": "HTTP path of execution. Path can include query params. Default value is \/", - "required": false, - "type": "string", - "x-example": "", - "default": "\/", - "in": "formData" - }, - { - "name": "method", - "description": "HTTP method of execution. Default value is GET.", - "required": false, - "type": "string", - "x-example": "GET", - "enum": [ - "GET", - "POST", - "PUT", - "PATCH", - "DELETE", - "OPTIONS" - ], - "x-enum-name": "ExecutionMethod", - "x-enum-keys": [], - "default": "POST", - "in": "formData" - }, - { - "name": "headers", - "description": "HTTP headers of execution. Defaults to empty.", - "required": false, - "type": "object", - "default": [], - "x-example": "{}", - "in": "formData" - }, - { - "name": "scheduledAt", - "description": "Scheduled execution time in [ISO 8601](https:\/\/www.iso.org\/iso-8601-date-and-time-format.html) format. DateTime value must be in future with precision in minutes.", - "required": false, - "type": "string", - "in": "formData" + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "body": { + "type": "string", + "description": "HTTP body of execution. Default value is empty string.", + "default": "", + "x-example": "" + }, + "async": { + "type": "boolean", + "description": "Execute code in the background. Default value is false.", + "default": false, + "x-example": false + }, + "path": { + "type": "string", + "description": "HTTP path of execution. Path can include query params. Default value is \/", + "default": "\/", + "x-example": "" + }, + "method": { + "type": "string", + "description": "HTTP method of execution. Default value is GET.", + "default": "POST", + "x-example": "GET", + "enum": [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE", + "OPTIONS" + ], + "x-enum-name": "ExecutionMethod", + "x-enum-keys": [] + }, + "headers": { + "type": "object", + "description": "HTTP headers of execution. Defaults to empty.", + "default": [], + "x-example": "{}" + }, + "scheduledAt": { + "type": "string", + "description": "Scheduled execution time in [ISO 8601](https:\/\/www.iso.org\/iso-8601-date-and-time-format.html) format. DateTime value must be in future with precision in minutes.", + "default": null, + "x-example": null + } + } + } } ] } diff --git a/app/config/specs/swagger2-1.6.x-server.json b/app/config/specs/swagger2-1.6.x-server.json index af6274226f..918a0b3b1d 100644 --- a/app/config/specs/swagger2-1.6.x-server.json +++ b/app/config/specs/swagger2-1.6.x-server.json @@ -10022,7 +10022,7 @@ "summary": "Create execution", "operationId": "functionsCreateExecution", "consumes": [ - "multipart\/form-data" + "application\/json" ], "produces": [ "multipart\/form-data" @@ -10083,65 +10083,59 @@ "in": "path" }, { - "name": "body", - "description": "HTTP body of execution. Default value is empty string.", - "required": false, - "type": "payload", - "default": "", - "in": "formData" - }, - { - "name": "async", - "description": "Execute code in the background. Default value is false.", - "required": false, - "type": "boolean", - "x-example": false, - "default": false, - "in": "formData" - }, - { - "name": "path", - "description": "HTTP path of execution. Path can include query params. Default value is \/", - "required": false, - "type": "string", - "x-example": "", - "default": "\/", - "in": "formData" - }, - { - "name": "method", - "description": "HTTP method of execution. Default value is GET.", - "required": false, - "type": "string", - "x-example": "GET", - "enum": [ - "GET", - "POST", - "PUT", - "PATCH", - "DELETE", - "OPTIONS" - ], - "x-enum-name": "ExecutionMethod", - "x-enum-keys": [], - "default": "POST", - "in": "formData" - }, - { - "name": "headers", - "description": "HTTP headers of execution. Defaults to empty.", - "required": false, - "type": "object", - "default": [], - "x-example": "{}", - "in": "formData" - }, - { - "name": "scheduledAt", - "description": "Scheduled execution time in [ISO 8601](https:\/\/www.iso.org\/iso-8601-date-and-time-format.html) format. DateTime value must be in future with precision in minutes.", - "required": false, - "type": "string", - "in": "formData" + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "body": { + "type": "string", + "description": "HTTP body of execution. Default value is empty string.", + "default": "", + "x-example": "" + }, + "async": { + "type": "boolean", + "description": "Execute code in the background. Default value is false.", + "default": false, + "x-example": false + }, + "path": { + "type": "string", + "description": "HTTP path of execution. Path can include query params. Default value is \/", + "default": "\/", + "x-example": "" + }, + "method": { + "type": "string", + "description": "HTTP method of execution. Default value is GET.", + "default": "POST", + "x-example": "GET", + "enum": [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE", + "OPTIONS" + ], + "x-enum-name": "ExecutionMethod", + "x-enum-keys": [] + }, + "headers": { + "type": "object", + "description": "HTTP headers of execution. Defaults to empty.", + "default": [], + "x-example": "{}" + }, + "scheduledAt": { + "type": "string", + "description": "Scheduled execution time in [ISO 8601](https:\/\/www.iso.org\/iso-8601-date-and-time-format.html) format. DateTime value must be in future with precision in minutes.", + "default": null, + "x-example": null + } + } + } } ] } diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 6e429fd8cf..55b62810f4 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -1717,7 +1717,7 @@ App::post('/v1/functions/:functionId/executions') ->label('sdk.response.model', Response::MODEL_EXECUTION) ->label('sdk.request.type', Response::CONTENT_TYPE_JSON) ->param('functionId', '', new UID(), 'Function ID.') - ->param('body', '', new Payload(10485760, 0), 'HTTP body of execution. Default value is empty string.', true) + ->param('body', '', new Text(10485760, 0), 'HTTP body of execution. Default value is empty string.', true) ->param('async', false, new Boolean(), 'Execute code in the background. Default value is false.', true) ->param('path', '/', new Text(2048), 'HTTP path of execution. Path can include query params. Default value is /', true) ->param('method', 'POST', new Whitelist(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], true), 'HTTP method of execution. Default value is GET.', true) From eea9e229237a84e592b1a868016d289a117b2069 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 8 Oct 2024 14:17:49 +1300 Subject: [PATCH 033/101] Release ruby --- app/config/platforms.php | 2 +- composer.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/config/platforms.php b/app/config/platforms.php index 40cea19fd3..d14c930279 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -321,7 +321,7 @@ return [ [ 'key' => 'ruby', 'name' => 'Ruby', - 'version' => '12.1.0', + 'version' => '12.1.1', 'url' => 'https://github.com/appwrite/sdk-for-ruby', 'package' => 'https://rubygems.org/gems/appwrite', 'enabled' => true, diff --git a/composer.lock b/composer.lock index 147800df32..0b78df3532 100644 --- a/composer.lock +++ b/composer.lock @@ -2993,16 +2993,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.39.21", + "version": "0.39.23", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "9754b190d33aaad56fdb8defc94f90248184c5ac" + "reference": "0acceabb7593c9c07c5db85a84a5ebac60896763" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/9754b190d33aaad56fdb8defc94f90248184c5ac", - "reference": "9754b190d33aaad56fdb8defc94f90248184c5ac", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/0acceabb7593c9c07c5db85a84a5ebac60896763", + "reference": "0acceabb7593c9c07c5db85a84a5ebac60896763", "shasum": "" }, "require": { @@ -3038,9 +3038,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/0.39.21" + "source": "https://github.com/appwrite/sdk-generator/tree/0.39.23" }, - "time": "2024-09-10T08:49:29+00:00" + "time": "2024-10-08T00:38:57+00:00" }, { "name": "doctrine/annotations", From 03dd5a15799121836fd2204036d0ef5239f824bd Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 8 Oct 2024 14:17:58 +1300 Subject: [PATCH 034/101] Fix versions --- app/config/platforms.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/config/platforms.php b/app/config/platforms.php index d14c930279..907c01b237 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -15,7 +15,7 @@ return [ [ 'key' => 'web', 'name' => 'Web', - 'version' => '16.0.0', + 'version' => '16.0.2', 'url' => 'https://github.com/appwrite/sdk-for-web', 'package' => 'https://www.npmjs.com/package/appwrite', 'enabled' => true, @@ -285,7 +285,7 @@ return [ [ 'key' => 'php', 'name' => 'PHP', - 'version' => '12.0.0', + 'version' => '12.1.0', 'url' => 'https://github.com/appwrite/sdk-for-php', 'package' => 'https://packagist.org/packages/appwrite/appwrite', 'enabled' => true, @@ -357,7 +357,7 @@ return [ [ 'key' => 'dotnet', 'name' => '.NET', - 'version' => '0.10.0', + 'version' => '0.10.1', 'url' => 'https://github.com/appwrite/sdk-for-dotnet', 'package' => 'https://www.nuget.org/packages/Appwrite', 'enabled' => true, From ba24b668c981de7d7aeaebb0a74946220f817243 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 8 Oct 2024 14:43:06 +1300 Subject: [PATCH 035/101] Lint --- app/controllers/api/functions.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 76e731a177..f1e8d82a9b 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -11,7 +11,6 @@ use Appwrite\Event\Validator\FunctionEvent; use Appwrite\Extend\Exception; use Appwrite\Extend\Exception as AppwriteException; use Appwrite\Functions\Validator\Headers; -use Appwrite\Functions\Validator\Payload; use Appwrite\Functions\Validator\RuntimeSpecification; use Appwrite\Messaging\Adapter\Realtime; use Appwrite\Platform\Tasks\ScheduleExecutions; From 8bc37026cd9fbdcc820c5c12378eba8995a33f21 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 8 Oct 2024 20:54:40 +1300 Subject: [PATCH 036/101] Revert "Feat adding coroutines" --- .github/workflows/codeql-phpstan.yml | 16 - .github/workflows/tests.yml | 31 +- app/cli.php | 272 ++- app/config/variables.php | 2 +- app/controllers/api/account.php | 616 +++--- app/controllers/api/avatars.php | 102 +- app/controllers/api/console.php | 12 +- app/controllers/api/databases.php | 427 ++-- app/controllers/api/functions.php | 213 +- app/controllers/api/graphql.php | 29 +- app/controllers/api/health.php | 340 ++- app/controllers/api/locale.php | 20 +- app/controllers/api/messaging.php | 181 +- app/controllers/api/migrations.php | 64 +- app/controllers/api/project.php | 35 +- app/controllers/api/projects.php | 294 ++- app/controllers/api/proxy.php | 22 +- app/controllers/api/storage.php | 211 +- app/controllers/api/teams.php | 115 +- app/controllers/api/users.php | 123 +- app/controllers/api/vcs.php | 97 +- app/controllers/general.php | 267 +-- app/controllers/mock.php | 34 +- app/controllers/shared/api.php | 122 +- app/controllers/shared/api/auth.php | 22 +- app/controllers/web/console.php | 6 +- app/controllers/web/home.php | 4 +- app/http.php | 501 +++-- app/init.php | 1867 +++++++++++++++-- app/init/config.php | 37 - app/init/constants.php | 202 -- app/init/database/filters.php | 397 ---- app/init/database/formats.php | 43 - app/init/locale.php | 23 - app/init/resources.php | 1091 ---------- app/realtime.php | 277 ++- app/views/install/compose.phtml | 5 +- app/worker.php | 363 +++- composer.json | 39 +- composer.lock | 505 ++--- docker-compose.yml | 6 +- docs/tutorials/add-route.md | 30 +- phpstan.neon | 11 - src/Appwrite/Auth/Auth.php | 36 +- src/Appwrite/Auth/Authentication.php | 57 - src/Appwrite/Auth/Validator/MockNumber.php | 5 +- src/Appwrite/Auth/Validator/Password.php | 2 +- src/Appwrite/Auth/Validator/Phone.php | 2 +- src/Appwrite/Event/Func.php | 4 +- src/Appwrite/Event/Validator/Event.php | 2 +- src/Appwrite/Functions/Validator/Headers.php | 2 +- src/Appwrite/Functions/Validator/Payload.php | 2 +- .../Validator/RuntimeSpecification.php | 2 +- src/Appwrite/GraphQL/Resolvers.php | 170 +- src/Appwrite/GraphQL/Schema.php | 119 +- src/Appwrite/GraphQL/Types/Mapper.php | 64 +- src/Appwrite/Migration/Migration.php | 36 +- src/Appwrite/Network/Validator/CNAME.php | 2 +- src/Appwrite/Network/Validator/Email.php | 4 +- src/Appwrite/Network/Validator/Origin.php | 4 +- src/Appwrite/Platform/Tasks/Doctor.php | 104 +- src/Appwrite/Platform/Tasks/Install.php | 11 +- src/Appwrite/Platform/Tasks/Migrate.php | 22 +- src/Appwrite/Platform/Tasks/QueueCount.php | 4 +- src/Appwrite/Platform/Tasks/QueueRetry.php | 4 +- src/Appwrite/Platform/Tasks/SSL.php | 4 +- src/Appwrite/Platform/Tasks/ScheduleBase.php | 136 +- .../Platform/Tasks/ScheduleExecutions.php | 30 +- .../Platform/Tasks/ScheduleFunctions.php | 17 +- .../Platform/Tasks/ScheduleMessages.php | 24 +- src/Appwrite/Platform/Tasks/Specs.php | 65 +- src/Appwrite/Platform/Tasks/Upgrade.php | 4 +- src/Appwrite/Platform/Workers/Audits.php | 6 +- src/Appwrite/Platform/Workers/Builds.php | 66 +- .../Platform/Workers/Certificates.php | 36 +- src/Appwrite/Platform/Workers/Deletes.php | 14 +- src/Appwrite/Platform/Workers/Mails.php | 6 +- src/Appwrite/Platform/Workers/Messaging.php | 4 +- src/Appwrite/Promises/Promise.php | 2 +- src/Appwrite/Promises/Swoole.php | 17 +- src/Appwrite/Specification/Format.php | 10 +- .../Specification/Format/OpenAPI3.php | 53 +- .../Specification/Format/Swagger2.php | 42 +- src/Appwrite/Task/Validator/Cron.php | 2 +- .../Utopia/Database/Validator/CompoundUID.php | 2 +- .../Utopia/Database/Validator/ProjectId.php | 2 +- src/Appwrite/Utopia/Queue/Connections.php | 36 - src/Appwrite/Utopia/Request.php | 38 +- src/Appwrite/Utopia/Response.php | 318 ++- src/Appwrite/Utopia/Response/Models.php | 304 --- src/Appwrite/Utopia/View.php | 2 +- .../e2e/Services/Databases/DatabasesBase.php | 4 +- .../Databases/DatabasesCustomClientTest.php | 4 +- .../DatabasesPermissionsGuestTest.php | 20 +- .../e2e/Services/Functions/FunctionsBase.php | 6 +- .../Functions/FunctionsCustomServerTest.php | 7 +- .../FunctionsScheduleTest.php | 3 +- tests/e2e/Services/GraphQL/Base.php | 2 +- .../Services/GraphQL/StorageClientTest.php | 1 + .../Services/GraphQL/StorageServerTest.php | 1 + .../Projects/ProjectsConsoleClientTest.php | 2 +- .../Storage/StorageCustomClientTest.php | 4 +- .../Webhooks/WebhooksCustomServerTest.php | 5 +- tests/resources/docker/docker-compose.yml | 2 +- tests/unit/Auth/AuthTest.php | 35 +- tests/unit/Event/EventTest.php | 10 +- tests/unit/GraphQL/BuilderTest.php | 9 +- .../unit/Messaging/MessagingChannelsTest.php | 9 +- tests/unit/Migration/MigrationTest.php | 2 +- tests/unit/Utopia/RequestTest.php | 7 +- tests/unit/Utopia/ResponseTest.php | 10 +- 111 files changed, 5230 insertions(+), 5885 deletions(-) delete mode 100644 .github/workflows/codeql-phpstan.yml delete mode 100644 app/init/config.php delete mode 100644 app/init/constants.php delete mode 100644 app/init/database/filters.php delete mode 100644 app/init/database/formats.php delete mode 100644 app/init/locale.php delete mode 100644 app/init/resources.php delete mode 100644 phpstan.neon delete mode 100644 src/Appwrite/Auth/Authentication.php delete mode 100644 src/Appwrite/Utopia/Queue/Connections.php delete mode 100644 src/Appwrite/Utopia/Response/Models.php diff --git a/.github/workflows/codeql-phpstan.yml b/.github/workflows/codeql-phpstan.yml deleted file mode 100644 index 3253e2c38b..0000000000 --- a/.github/workflows/codeql-phpstan.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: "CodeQL" - -on: [pull_request] -jobs: - lint: - name: CodeQL - runs-on: ubuntu-latest - - steps: - - name: Check out the repo - uses: actions/checkout@v2 - - - name: Run CodeQL - run: | - docker run --rm -v $PWD:/app composer sh -c \ - "composer install --profile --ignore-platform-reqs && composer check" \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3ca3c9c833..6e7012527d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,22 +16,22 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Build Appwrite - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v6 with: context: . push: false tags: ${{ env.IMAGE }} load: true - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=appwrite + cache-to: type=gha,mode=max,scope=appwrite outputs: type=docker,dest=/tmp/${{ env.IMAGE }}.tar build-args: | DEBUG=false @@ -39,12 +39,13 @@ jobs: VERSION=dev - name: Cache Docker Image - uses: actions/cache@v3 + uses: actions/cache@v4 with: key: ${{ env.CACHE_KEY }} + restore-keys: | + appwrite-dev- path: /tmp/${{ env.IMAGE }}.tar - unit_test: name: Unit Test runs-on: ubuntu-latest @@ -52,10 +53,10 @@ jobs: steps: - name: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Load Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar @@ -82,10 +83,10 @@ jobs: needs: setup steps: - name: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Load Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar @@ -130,10 +131,10 @@ jobs: steps: - name: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Load Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar @@ -157,9 +158,9 @@ jobs: needs: setup steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Load Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar diff --git a/app/cli.php b/app/cli.php index 7c2b73e966..23502ec402 100644 --- a/app/cli.php +++ b/app/cli.php @@ -7,134 +7,214 @@ use Appwrite\Event\Delete; use Appwrite\Event\Func; use Appwrite\Platform\Appwrite; use Appwrite\Runtimes\Runtimes; -use Swoole\Runtime; -use Utopia\CLI\Adapters\Swoole as SwooleCLI; +use Utopia\Cache\Adapter\Sharding; +use Utopia\Cache\Cache; +use Utopia\CLI\CLI; use Utopia\CLI\Console; use Utopia\Config\Config; +use Utopia\Database\Database; +use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -use Utopia\DI\Dependency; +use Utopia\DSN\DSN; use Utopia\Logger\Log; use Utopia\Platform\Service; +use Utopia\Pools\Group; use Utopia\Queue\Connection; use Utopia\Registry\Registry; use Utopia\System\System; -global $registry, $container; - -Runtime::enableCoroutine(SWOOLE_HOOK_ALL); - // overwriting runtimes to be architectur agnostic for CLI Config::setParam('runtimes', (new Runtimes('v4'))->getAll(supported: false)); // require controllers after overwriting runtimes require_once __DIR__ . '/controllers/general.php'; -/** - * @var Registry $registry - * @var Container $container - */ -$context = new Dependency(); -$register = new Dependency(); -$logError = new Dependency(); -$queueForDeletes = new Dependency(); -$queueForFunctions = new Dependency(); -$queueForCertificates = new Dependency(); +Authorization::disable(); -$context - ->setName('context') - ->setCallback(fn () => $container); +CLI::setResource('register', fn () => $register); -$register - ->setName('register') - ->setCallback(function () use (&$registry): Registry { - return $registry; - }); +CLI::setResource('cache', function ($pools) { + $list = Config::getParam('pools-cache', []); + $adapters = []; -$queueForFunctions - ->setName('queueForFunctions') - ->inject('queue') - ->setCallback(function (Connection $queue) { - return new Func($queue); - }); + foreach ($list as $value) { + $adapters[] = $pools + ->get($value) + ->pop() + ->getResource() + ; + } + return new Cache(new Sharding($adapters)); +}, ['pools']); -$queueForDeletes - ->setName('queueForDeletes') - ->inject('queue') - ->setCallback(function (Connection $queue) { - return new Delete($queue); - }); +CLI::setResource('pools', function (Registry $register) { + return $register->get('pools'); +}, ['register']); -$queueForCertificates - ->setName('queueForCertificates') - ->inject('queue') - ->setCallback(function (Connection $queue) { - return new Certificate($queue); - }); +CLI::setResource('dbForConsole', function ($pools, $cache) { + $sleep = 3; + $maxAttempts = 5; + $attempts = 0; + $ready = false; -$logError - ->setName('logError') - ->inject('register') - ->setCallback(function (Registry $register) { - return function (Throwable $error, string $namespace, string $action) use ($register) { - $logger = $register->get('logger'); + do { + $attempts++; + try { + // Prepare database connection + $dbAdapter = $pools + ->get('console') + ->pop() + ->getResource(); - if ($logger) { - $version = System::getEnv('_APP_VERSION', 'UNKNOWN'); + $dbForConsole = new Database($dbAdapter, $cache); - $log = new Log(); - $log->setNamespace($namespace); - $log->setServer(\gethostname()); - $log->setVersion($version); - $log->setType(Log::TYPE_ERROR); - $log->setMessage($error->getMessage()); + $dbForConsole + ->setNamespace('_console') + ->setMetadata('host', \gethostname()) + ->setMetadata('project', 'console'); - $log->addTag('code', $error->getCode()); - $log->addTag('verboseType', get_class($error)); + // Ensure tables exist + $collections = Config::getParam('collections', [])['console']; + $last = \array_key_last($collections); - $log->addExtra('file', $error->getFile()); - $log->addExtra('line', $error->getLine()); - $log->addExtra('trace', $error->getTraceAsString()); - $log->addExtra('trace', $error->getTraceAsString()); - - $log->setAction($action); - - $isProduction = System::getEnv('_APP_ENV', 'development') === 'production'; - - $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING); - - try { - $responseCode = $logger->addLog($log); - Console::info('Error log pushed with status code: ' . $responseCode); - } catch (Throwable $th) { - Console::error('Error pushing log: ' . $th->getMessage()); - } + if (!($dbForConsole->exists($dbForConsole->getDatabase(), $last))) { /** TODO cache ready variable using registry */ + throw new Exception('Tables not ready yet.'); } - Console::warning("Failed: {$error->getMessage()}"); - Console::warning($error->getTraceAsString()); - }; - }); + $ready = true; + } catch (\Throwable $err) { + Console::warning($err->getMessage()); + $pools->get('console')->reclaim(); + sleep($sleep); + } + } while ($attempts < $maxAttempts && !$ready); -$container->set($context); -$container->set($logError); -$container->set($register); -$container->set($queueForDeletes); -$container->set($queueForFunctions); -$container->set($queueForCertificates); + if (!$ready) { + throw new Exception("Console is not ready yet. Please try again later."); + } + + return $dbForConsole; +}, ['pools', 'cache']); + +CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) { + $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools + + return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases) { + if ($project->isEmpty() || $project->getId() === 'console') { + return $dbForConsole; + } + + 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')); + } + + if (isset($databases[$dsn->getHost()])) { + $database = $databases[$dsn->getHost()]; + + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + $database + ->setSharedTables(true) + ->setTenant($project->getInternalId()) + ->setNamespace($dsn->getParam('namespace')); + } else { + $database + ->setSharedTables(false) + ->setTenant(null) + ->setNamespace('_' . $project->getInternalId()); + } + + return $database; + } + + $dbAdapter = $pools + ->get($dsn->getHost()) + ->pop() + ->getResource(); + + $database = new Database($dbAdapter, $cache); + + $databases[$dsn->getHost()] = $database; + + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + $database + ->setSharedTables(true) + ->setTenant($project->getInternalId()) + ->setNamespace($dsn->getParam('namespace')); + } else { + $database + ->setSharedTables(false) + ->setTenant(null) + ->setNamespace('_' . $project->getInternalId()); + } + + $database + ->setMetadata('host', \gethostname()) + ->setMetadata('project', $project->getId()); + + return $database; + }; +}, ['pools', 'dbForConsole', 'cache']); + +CLI::setResource('queue', function (Group $pools) { + return $pools->get('queue')->pop()->getResource(); +}, ['pools']); +CLI::setResource('queueForFunctions', function (Connection $queue) { + return new Func($queue); +}, ['queue']); +CLI::setResource('queueForDeletes', function (Connection $queue) { + return new Delete($queue); +}, ['queue']); +CLI::setResource('queueForCertificates', function (Connection $queue) { + return new Certificate($queue); +}, ['queue']); +CLI::setResource('logError', function (Registry $register) { + return function (Throwable $error, string $namespace, string $action) use ($register) { + $logger = $register->get('logger'); + + if ($logger) { + $version = System::getEnv('_APP_VERSION', 'UNKNOWN'); + + $log = new Log(); + $log->setNamespace($namespace); + $log->setServer(\gethostname()); + $log->setVersion($version); + $log->setType(Log::TYPE_ERROR); + $log->setMessage($error->getMessage()); + + $log->addTag('code', $error->getCode()); + $log->addTag('verboseType', get_class($error)); + + $log->addExtra('file', $error->getFile()); + $log->addExtra('line', $error->getLine()); + $log->addExtra('trace', $error->getTraceAsString()); + + $log->setAction($action); + + $isProduction = System::getEnv('_APP_ENV', 'development') === 'production'; + $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING); + + try { + $responseCode = $logger->addLog($log); + Console::info('Error log pushed with status code: ' . $responseCode); + } catch (Throwable $th) { + Console::error('Error pushing log: ' . $th->getMessage()); + } + } + + Console::warning("Failed: {$error->getMessage()}"); + Console::warning($error->getTraceAsString()); + }; +}, ['register']); $platform = new Appwrite(); -$platform->init(Service::TYPE_TASK, ['adapter' => new SwooleCLI(1)]); +$platform->init(Service::TYPE_TASK); $cli = $platform->getCli(); -$cli - ->init() - ->inject('authorization') - ->action(function (Authorization $authorization) { - $authorization->disable(); - }); - $cli ->error() ->inject('error') @@ -142,6 +222,4 @@ $cli Console::error($error->getMessage()); }); -$cli - ->setContainer($container) - ->run(); +$cli->run(); diff --git a/app/config/variables.php b/app/config/variables.php index 161951e3de..113fbae335 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -254,7 +254,7 @@ return [ 'name' => '_APP_WORKER_PER_CORE', 'description' => 'Internal Worker per core for the API, Realtime and Executor containers. Can be configured to optimize performance.', 'introduction' => '0.13.0', - 'default' => 2, + 'default' => 6, 'required' => false, 'question' => '', 'filter' => '' diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index ea0c61a949..64441fee5c 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2,7 +2,6 @@ use Ahc\Jwt\JWT; use Appwrite\Auth\Auth; -use Appwrite\Auth\Authentication; use Appwrite\Auth\MFA\Challenge; use Appwrite\Auth\MFA\Type; use Appwrite\Auth\MFA\Type\TOTP; @@ -29,6 +28,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Identities; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use MaxMind\Db\Reader; +use Utopia\App; use Utopia\Audit\Audit as EventAudit; use Utopia\Config\Config; use Utopia\Database\Database; @@ -45,16 +45,15 @@ use Utopia\Database\Validator\Queries; use Utopia\Database\Validator\Query\Limit; use Utopia\Database\Validator\Query\Offset; use Utopia\Database\Validator\UID; -use Utopia\Http\Http; -use Utopia\Http\Validator\ArrayList; -use Utopia\Http\Validator\Assoc; -use Utopia\Http\Validator\Boolean; -use Utopia\Http\Validator\Host; -use Utopia\Http\Validator\Text; -use Utopia\Http\Validator\URL; -use Utopia\Http\Validator\WhiteList; use Utopia\Locale\Locale; use Utopia\System\System; +use Utopia\Validator\ArrayList; +use Utopia\Validator\Assoc; +use Utopia\Validator\Boolean; +use Utopia\Validator\Host; +use Utopia\Validator\Text; +use Utopia\Validator\URL; +use Utopia\Validator\WhiteList; $oauthDefaultSuccess = '/console/auth/oauth2/success'; $oauthDefaultFailure = '/console/auth/oauth2/failure'; @@ -145,13 +144,14 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc ->trigger(); }; -$createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Authorization $authorization, Authentication $authentication) { - $roles = $authorization->getRoles(); + +$createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails) { + $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); /** @var Utopia\Database\Document $user */ - $userFromRequest = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId)); + $userFromRequest = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId)); if ($userFromRequest->isEmpty()) { throw new Exception(Exception::USER_INVALID_TOKEN); @@ -197,7 +197,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res $detector->getDevice() )); - $authorization->addRole(Role::user($user->getId())->toString()); + Authorization::setRole(Role::user($user->getId())->toString()); $session = $dbForProject->createDocument('sessions', $session ->setAttribute('$permissions', [ @@ -206,7 +206,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res Permission::delete(Role::user($user->getId())), ])); - $authorization->skip(fn () => $dbForProject->deleteDocument('tokens', $verifiedToken->getId())); + Authorization::skip(fn () => $dbForProject->deleteDocument('tokens', $verifiedToken->getId())); $dbForProject->purgeCachedDocument('users', $user->getId()); // Magic URL + Email OTP @@ -247,15 +247,15 @@ $createSession = function (string $userId, string $secret, Request $request, Res ->setParam('sessionId', $session->getId()); if (!Config::getParam('domainVerification')) { - $response->addHeader('X-Fallback-Cookies', \json_encode([$authentication->getCookieName() => Auth::encodeSession($user->getId(), $sessionSecret)])); + $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $sessionSecret)])); } $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)); $protocol = $request->getProtocol(); $response - ->addCookie($authentication->getCookieName() . '_legacy', Auth::encodeSession($user->getId(), $sessionSecret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie($authentication->getCookieName(), Auth::encodeSession($user->getId(), $sessionSecret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $sessionSecret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $sessionSecret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->setStatusCode(Response::STATUS_CODE_CREATED); $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); @@ -270,7 +270,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res $response->dynamic($session, Response::MODEL_SESSION); }; -Http::post('/v1/account') +App::post('/v1/account') ->desc('Create account') ->groups(['api', 'account', 'auth']) ->label('event', 'users.[userId].create') @@ -298,8 +298,7 @@ Http::post('/v1/account') ->inject('dbForProject') ->inject('queueForEvents') ->inject('hooks') - ->inject('authorization') - ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks, Authorization $authorization) { + ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) { $email = \strtolower($email); if ('console' === $project->getId()) { @@ -332,7 +331,7 @@ Http::post('/v1/account') $identityWithMatchingEmail = $dbForProject->findOne('identities', [ Query::equal('providerEmail', [$email]), ]); - if (!$identityWithMatchingEmail->isEmpty()) { + if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */ } @@ -377,9 +376,9 @@ Http::post('/v1/account') 'accessedAt' => DateTime::now(), ]); $user->removeAttribute('$internalId'); - $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user)); + $user = Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); try { - $target = $authorization->skip(fn () => $dbForProject->createDocument('targets', new Document([ + $target = Authorization::skip(fn () => $dbForProject->createDocument('targets', new Document([ '$permissions' => [ Permission::read(Role::user($user->getId())), Permission::update(Role::user($user->getId())), @@ -395,7 +394,7 @@ Http::post('/v1/account') $existingTarget = $dbForProject->findOne('targets', [ Query::equal('identifier', [$email]), ]); - if (!$existingTarget->isEmpty()) { + if ($existingTarget) { $user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND); } } @@ -405,9 +404,9 @@ Http::post('/v1/account') throw new Exception(Exception::USER_ALREADY_EXISTS); } - $authorization->removeRole(Role::guests()->toString()); - $authorization->addRole(Role::user($user->getId())->toString()); - $authorization->addRole(Role::users()->toString()); + Authorization::unsetRole(Role::guests()->toString()); + Authorization::setRole(Role::user($user->getId())->toString()); + Authorization::setRole(Role::users()->toString()); $queueForEvents->setParam('userId', $user->getId()); @@ -416,7 +415,7 @@ Http::post('/v1/account') ->dynamic($user, Response::MODEL_ACCOUNT); }); -Http::get('/v1/account') +App::get('/v1/account') ->desc('Get account') ->groups(['api', 'account']) ->label('scope', 'account') @@ -439,7 +438,7 @@ Http::get('/v1/account') $response->dynamic($user, Response::MODEL_ACCOUNT); }); -Http::delete('/v1/account') +App::delete('/v1/account') ->desc('Delete account') ->groups(['api', 'account']) ->label('event', 'users.[userId].delete') @@ -487,7 +486,7 @@ Http::delete('/v1/account') $response->noContent(); }); -Http::get('/v1/account/sessions') +App::get('/v1/account/sessions') ->desc('List sessions') ->groups(['api', 'account']) ->label('scope', 'account') @@ -502,16 +501,15 @@ Http::get('/v1/account/sessions') ->inject('response') ->inject('user') ->inject('locale') - ->inject('authorization') - ->inject('authentication') - ->action(function (Response $response, Document $user, Locale $locale, Authorization $authorization, Authentication $authentication) { + ->inject('project') + ->action(function (Response $response, Document $user, Locale $locale, Document $project) { - $roles = $authorization->getRoles(); + $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); $sessions = $user->getAttribute('sessions', []); - $current = Auth::sessionVerify($sessions, $authentication->getSecret()); + $current = Auth::sessionVerify($sessions, Auth::$secret); foreach ($sessions as $key => $session) {/** @var Document $session */ $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); @@ -529,7 +527,7 @@ Http::get('/v1/account/sessions') ]), Response::MODEL_SESSION_LIST); }); -Http::delete('/v1/account/sessions') +App::delete('/v1/account/sessions') ->desc('Delete sessions') ->groups(['api', 'account']) ->label('scope', 'account') @@ -550,8 +548,7 @@ Http::delete('/v1/account/sessions') ->inject('locale') ->inject('queueForEvents') ->inject('queueForDeletes') - ->inject('authentication') - ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Authentication $authentication) { + ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes) { $protocol = $request->getProtocol(); $sessions = $user->getAttribute('sessions', []); @@ -567,13 +564,13 @@ Http::delete('/v1/account/sessions') ->setAttribute('current', false) ->setAttribute('countryName', $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'))); - if ($session->getAttribute('secret') == Auth::hash($authentication->getSecret())) { + if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { $session->setAttribute('current', true); // If current session delete the cookies too $response - ->addCookie($authentication->getCookieName() . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie($authentication->getCookieName(), '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); + ->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); // Use current session for events. $queueForEvents @@ -595,7 +592,7 @@ Http::delete('/v1/account/sessions') $response->noContent(); }); -Http::get('/v1/account/sessions/:sessionId') +App::get('/v1/account/sessions/:sessionId') ->desc('Get session') ->groups(['api', 'account']) ->label('scope', 'account') @@ -612,17 +609,16 @@ Http::get('/v1/account/sessions/:sessionId') ->inject('response') ->inject('user') ->inject('locale') - ->inject('authorization') - ->inject('authentication') - ->action(function (?string $sessionId, Response $response, Document $user, Locale $locale, Authorization $authorization, Authentication $authentication) { + ->inject('project') + ->action(function (?string $sessionId, Response $response, Document $user, Locale $locale, Document $project) { - $roles = $authorization->getRoles(); + $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); $sessions = $user->getAttribute('sessions', []); $sessionId = ($sessionId === 'current') - ? Auth::sessionVerify($user->getAttribute('sessions'), $authentication->getSecret()) + ? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret) : $sessionId; foreach ($sessions as $session) {/** @var Document $session */ @@ -630,7 +626,7 @@ Http::get('/v1/account/sessions/:sessionId') $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); $session - ->setAttribute('current', ($session->getAttribute('secret') == Auth::hash($authentication->getSecret()))) + ->setAttribute('current', ($session->getAttribute('secret') == Auth::hash(Auth::$secret))) ->setAttribute('countryName', $countryName) ->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $session->getAttribute('secret', '') : '') ; @@ -642,7 +638,7 @@ Http::get('/v1/account/sessions/:sessionId') throw new Exception(Exception::USER_SESSION_NOT_FOUND); }); -Http::delete('/v1/account/sessions/:sessionId') +App::delete('/v1/account/sessions/:sessionId') ->desc('Delete session') ->groups(['api', 'account', 'mfa']) ->label('scope', 'account') @@ -665,12 +661,12 @@ Http::delete('/v1/account/sessions/:sessionId') ->inject('locale') ->inject('queueForEvents') ->inject('queueForDeletes') - ->inject('authentication') - ->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Authentication $authentication) { + ->inject('project') + ->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Document $project) { $protocol = $request->getProtocol(); $sessionId = ($sessionId === 'current') - ? Auth::sessionVerify($user->getAttribute('sessions'), $authentication->getSecret()) + ? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret) : $sessionId; $sessions = $user->getAttribute('sessions', []); @@ -689,7 +685,7 @@ Http::delete('/v1/account/sessions/:sessionId') $session->setAttribute('current', false); - if ($session->getAttribute('secret') == Auth::hash($authentication->getSecret())) { // If current session delete the cookies too + if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too $session ->setAttribute('current', true) ->setAttribute('countryName', $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'))); @@ -699,8 +695,8 @@ Http::delete('/v1/account/sessions/:sessionId') } $response - ->addCookie($authentication->getCookieName() . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie($authentication->getCookieName(), '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); + ->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); } $dbForProject->purgeCachedDocument('users', $user->getId()); @@ -722,7 +718,7 @@ Http::delete('/v1/account/sessions/:sessionId') throw new Exception(Exception::USER_SESSION_NOT_FOUND); }); -Http::patch('/v1/account/sessions/:sessionId') +App::patch('/v1/account/sessions/:sessionId') ->desc('Update session') ->groups(['api', 'account']) ->label('scope', 'account') @@ -744,11 +740,10 @@ Http::patch('/v1/account/sessions/:sessionId') ->inject('dbForProject') ->inject('project') ->inject('queueForEvents') - ->inject('authentication') - ->action(function (?string $sessionId, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Authentication $authentication) { + ->action(function (?string $sessionId, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents) { $sessionId = ($sessionId === 'current') - ? Auth::sessionVerify($user->getAttribute('sessions'), $authentication->getSecret()) + ? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret) : $sessionId; $sessions = $user->getAttribute('sessions', []); @@ -799,7 +794,7 @@ Http::patch('/v1/account/sessions/:sessionId') return $response->dynamic($session, Response::MODEL_SESSION); }); -Http::post('/v1/account/sessions/email') +App::post('/v1/account/sessions/email') ->alias('/v1/account/sessions') ->desc('Create email password session') ->groups(['api', 'account', 'auth', 'session']) @@ -830,9 +825,7 @@ Http::post('/v1/account/sessions/email') ->inject('queueForEvents') ->inject('queueForMails') ->inject('hooks') - ->inject('authorization') - ->inject('authentication') - ->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks, Authorization $authorization, Authentication $authentication) { + ->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks) { $email = \strtolower($email); $protocol = $request->getProtocol(); @@ -840,7 +833,7 @@ Http::post('/v1/account/sessions/email') Query::equal('email', [$email]), ]); - if ($profile->isEmpty() || empty($profile->getAttribute('passwordUpdate')) || !Auth::passwordVerify($password, $profile->getAttribute('password'), $profile->getAttribute('hash'), $profile->getAttribute('hashOptions'))) { + if (!$profile || empty($profile->getAttribute('passwordUpdate')) || !Auth::passwordVerify($password, $profile->getAttribute('password'), $profile->getAttribute('hash'), $profile->getAttribute('hashOptions'))) { throw new Exception(Exception::USER_INVALID_CREDENTIALS); } @@ -848,7 +841,7 @@ Http::post('/v1/account/sessions/email') throw new Exception(Exception::USER_BLOCKED); // User is in status blocked } - $roles = $authorization->getRoles(); + $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -879,7 +872,7 @@ Http::post('/v1/account/sessions/email') $detector->getDevice() )); - $authorization->addRole(Role::user($user->getId())->toString()); + Authorization::setRole(Role::user($user->getId())->toString()); // Re-hash if not using recommended algo if ($user->getAttribute('hash') !== Auth::DEFAULT_ALGO) { @@ -900,15 +893,15 @@ Http::post('/v1/account/sessions/email') if (!Config::getParam('domainVerification')) { $response - ->addHeader('X-Fallback-Cookies', \json_encode([$authentication->getCookieName() => Auth::encodeSession($user->getId(), $secret)])) + ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])) ; } $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)); $response - ->addCookie($authentication->getCookieName() . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie($authentication->getCookieName(), Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->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')) ->setStatusCode(Response::STATUS_CODE_CREATED) ; @@ -936,7 +929,7 @@ Http::post('/v1/account/sessions/email') $response->dynamic($session, Response::MODEL_SESSION); }); -Http::post('/v1/account/sessions/anonymous') +App::post('/v1/account/sessions/anonymous') ->desc('Create anonymous session') ->groups(['api', 'account', 'auth', 'session']) ->label('event', 'users.[userId].sessions.[sessionId].create') @@ -962,11 +955,9 @@ Http::post('/v1/account/sessions/anonymous') ->inject('dbForProject') ->inject('geodb') ->inject('queueForEvents') - ->inject('authorization') - ->inject('authentication') - ->action(function (Request $request, Response $response, Locale $locale, Document $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents, Authorization $authorization, Authentication $authentication) { + ->action(function (Request $request, Response $response, Locale $locale, Document $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents) { $protocol = $request->getProtocol(); - $roles = $authorization->getRoles(); + $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -1012,7 +1003,7 @@ Http::post('/v1/account/sessions/anonymous') 'accessedAt' => DateTime::now(), ]); $user->removeAttribute('$internalId'); - $authorization->skip(fn () => $dbForProject->createDocument('users', $user)); + Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); // Create session token $duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; @@ -1038,7 +1029,7 @@ Http::post('/v1/account/sessions/anonymous') $detector->getDevice() )); - $authorization->addRole(Role::user($user->getId())->toString()); + Authorization::setRole(Role::user($user->getId())->toString()); $session = $dbForProject->createDocument('sessions', $session-> setAttribute('$permissions', [ Permission::read(Role::user($user->getId())), @@ -1054,14 +1045,14 @@ Http::post('/v1/account/sessions/anonymous') ; if (!Config::getParam('domainVerification')) { - $response->addHeader('X-Fallback-Cookies', \json_encode([$authentication->getCookieName() => Auth::encodeSession($user->getId(), $secret)])); + $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])); } $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration)); $response - ->addCookie($authentication->getCookieName() . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie($authentication->getCookieName(), Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->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')) ->setStatusCode(Response::STATUS_CODE_CREATED) ; @@ -1076,7 +1067,7 @@ Http::post('/v1/account/sessions/anonymous') $response->dynamic($session, Response::MODEL_SESSION); }); -Http::post('/v1/account/sessions/token') +App::post('/v1/account/sessions/token') ->desc('Create session') ->label('event', 'users.[userId].sessions.[sessionId].create') ->groups(['api', 'account', 'session']) @@ -1104,11 +1095,9 @@ Http::post('/v1/account/sessions/token') ->inject('geodb') ->inject('queueForEvents') ->inject('queueForMails') - ->inject('authorization') - ->inject('authentication') ->action($createSession); -Http::get('/v1/account/sessions/oauth2/:provider') +App::get('/v1/account/sessions/oauth2/:provider') ->desc('Create OAuth2 session') ->groups(['api', 'account']) ->label('error', __DIR__ . '/../../views/general/error.phtml') @@ -1178,7 +1167,7 @@ Http::get('/v1/account/sessions/oauth2/:provider') ->redirect($oauth2->getLoginURL()); }); -Http::get('/v1/account/sessions/oauth2/callback/:provider/:projectId') +App::get('/v1/account/sessions/oauth2/callback/:provider/:projectId') ->desc('OAuth2 callback') ->groups(['account']) ->label('error', __DIR__ . '/../../views/general/error.phtml') @@ -1208,7 +1197,7 @@ Http::get('/v1/account/sessions/oauth2/callback/:provider/:projectId') . \http_build_query($params)); }); -Http::post('/v1/account/sessions/oauth2/callback/:provider/:projectId') +App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId') ->desc('OAuth2 callback') ->groups(['account']) ->label('error', __DIR__ . '/../../views/general/error.phtml') @@ -1239,7 +1228,7 @@ Http::post('/v1/account/sessions/oauth2/callback/:provider/:projectId') . \http_build_query($params)); }); -Http::get('/v1/account/sessions/oauth2/:provider/redirect') +App::get('/v1/account/sessions/oauth2/:provider/redirect') ->desc('OAuth2 redirect') ->groups(['api', 'account', 'session']) ->label('error', __DIR__ . '/../../views/general/error.phtml') @@ -1263,9 +1252,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') ->inject('dbForProject') ->inject('geodb') ->inject('queueForEvents') - ->inject('authorization') - ->inject('authentication') - ->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Reader $geodb, Event $queueForEvents, Authorization $authorization, Authentication $authentication) use ($oauthDefaultSuccess) { + ->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Reader $geodb, Event $queueForEvents) use ($oauthDefaultSuccess) { $protocol = $request->getProtocol(); $callback = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId(); $defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => '']; @@ -1379,7 +1366,6 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') $email = $oauth2->getUserEmail($accessToken); // Check if this identity is connected to a different user - $sessionUpgrade = false; if (!$user->isEmpty()) { $userId = $user->getId(); @@ -1387,7 +1373,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') Query::equal('providerEmail', [$email]), Query::notEqual('userInternalId', $user->getInternalId()), ]); - if (!$identityWithMatchingEmail->isEmpty()) { + if (!empty($identityWithMatchingEmail)) { throw new Exception(Exception::USER_ALREADY_EXISTS); } @@ -1403,7 +1389,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') } $sessions = $user->getAttribute('sessions', []); - $current = Auth::sessionVerify($sessions, $authentication->getSecret()); + $current = Auth::sessionVerify($sessions, Auth::$secret); if ($current) { // Delete current session of new one. $currentDocument = $dbForProject->getDocument('sessions', $current); @@ -1418,7 +1404,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') Query::equal('provider', [$provider]), Query::equal('providerUid', [$oauth2ID]), ]); - if (!$session->isEmpty()) { + if ($session !== false && !$session->isEmpty()) { $user->setAttributes($dbForProject->getDocument('users', $session->getAttribute('userId'))->getArrayCopy()); } } @@ -1436,7 +1422,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') $userWithEmail = $dbForProject->findOne('users', [ Query::equal('email', [$email]), ]); - if (!$userWithEmail->isEmpty()) { + if ($userWithEmail !== false && !$userWithEmail->isEmpty()) { $user->setAttributes($userWithEmail->getArrayCopy()); } @@ -1447,7 +1433,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') Query::equal('providerUid', [$oauth2ID]), ]); - if (!$identity->isEmpty()) { + if ($identity !== false && !$identity->isEmpty()) { $user = $dbForProject->getDocument('users', $identity->getAttribute('userId')); } } @@ -1467,7 +1453,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') $identityWithMatchingEmail = $dbForProject->findOne('identities', [ Query::equal('providerEmail', [$email]), ]); - if (!$identityWithMatchingEmail->isEmpty()) { + if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */ } @@ -1500,16 +1486,15 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') 'accessedAt' => DateTime::now(), ]); $user->removeAttribute('$internalId'); - $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user)); - + $userDoc = Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); $dbForProject->createDocument('targets', new Document([ '$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(), + 'userId' => $userDoc->getId(), + 'userInternalId' => $userDoc->getInternalId(), 'providerType' => MESSAGE_TYPE_EMAIL, 'identifier' => $email, ])); @@ -1519,8 +1504,8 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') } } - $authorization->addRole(Role::user($user->getId())->toString()); - $authorization->addRole(Role::users()->toString()); + Authorization::setRole(Role::user($user->getId())->toString()); + Authorization::setRole(Role::users()->toString()); if (false === $user->getAttribute('status')) { // Account is blocked $failureRedirect(Exception::USER_BLOCKED); // User is in status blocked @@ -1531,7 +1516,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') Query::equal('provider', [$provider]), Query::equal('providerUid', [$oauth2ID]), ]); - if ($identity->isEmpty()) { + if ($identity === false || $identity->isEmpty()) { // Before creating the identity, check if the email is already associated with another user $userId = $user->getId(); @@ -1579,7 +1564,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') $dbForProject->updateDocument('users', $user->getId(), $user); - $authorization->addRole(Role::user($user->getId())->toString()); + Authorization::setRole(Role::user($user->getId())->toString()); $state['success'] = URLParser::parse($state['success']); $query = URLParser::parseQuery($state['success']['query']); @@ -1601,7 +1586,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') 'ip' => $request->getIP(), ]); - $authorization->addRole(Role::user($user->getId())->toString()); + Authorization::setRole(Role::user($user->getId())->toString()); $token = $dbForProject->createDocument('tokens', $token ->setAttribute('$permissions', [ @@ -1651,7 +1636,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') $session->setAttribute('expire', $expire); if (!Config::getParam('domainVerification')) { - $response->addHeader('X-Fallback-Cookies', \json_encode([$authentication->getCookieName() => Auth::encodeSession($user->getId(), $secret)])); + $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])); } $queueForEvents @@ -1664,16 +1649,16 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') if ($state['success']['path'] == $oauthDefaultSuccess) { $query['project'] = $project->getId(); $query['domain'] = Config::getParam('cookieDomain'); - $query['key'] = $authentication->getCookieName(); + $query['key'] = Auth::$cookieName; $query['secret'] = Auth::encodeSession($user->getId(), $secret); } $response - ->addCookie($authentication->getCookieName() . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie($authentication->getCookieName(), Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')); + ->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')); } - if ($sessionUpgrade) { + if (isset($sessionUpgrade) && $sessionUpgrade) { foreach ($user->getAttribute('targets', []) as $target) { if ($target->getAttribute('providerType') !== MESSAGE_TYPE_PUSH) { continue; @@ -1701,7 +1686,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') ; }); -Http::get('/v1/account/tokens/oauth2/:provider') +App::get('/v1/account/tokens/oauth2/:provider') ->desc('Create OAuth2 token') ->groups(['api', 'account']) ->label('error', __DIR__ . '/../../views/general/error.phtml') @@ -1770,7 +1755,7 @@ Http::get('/v1/account/tokens/oauth2/:provider') ->redirect($oauth2->getLoginURL()); }); -Http::post('/v1/account/tokens/magic-url') +App::post('/v1/account/tokens/magic-url') ->alias('/v1/account/sessions/magic-url') ->desc('Create magic URL token') ->groups(['api', 'account', 'auth']) @@ -1800,8 +1785,7 @@ Http::post('/v1/account/tokens/magic-url') ->inject('locale') ->inject('queueForEvents') ->inject('queueForMails') - ->inject('authorization') - ->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, Authorization $authorization) { + ->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails) { if (empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled'); } @@ -1811,12 +1795,12 @@ Http::post('/v1/account/tokens/magic-url') $phrase = Phrase::generate(); } - $roles = $authorization->getRoles(); + $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); $result = $dbForProject->findOne('users', [Query::equal('email', [$email])]); - if (!$result->isEmpty()) { + if ($result !== false && !$result->isEmpty()) { $user->setAttributes($result->getArrayCopy()); } else { $limit = $project->getAttribute('auths', [])['limit'] ?? 0; @@ -1833,7 +1817,7 @@ Http::post('/v1/account/tokens/magic-url') $identityWithMatchingEmail = $dbForProject->findOne('identities', [ Query::equal('providerEmail', [$email]), ]); - if (!$identityWithMatchingEmail->isEmpty()) { + if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } @@ -1866,7 +1850,7 @@ Http::post('/v1/account/tokens/magic-url') ]); $user->removeAttribute('$internalId'); - $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user)); + Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); } $tokenSecret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_MAGIC_URL); @@ -1883,7 +1867,7 @@ Http::post('/v1/account/tokens/magic-url') 'ip' => $request->getIP(), ]); - $authorization->addRole(Role::user($user->getId())->toString()); + Authorization::setRole(Role::user($user->getId())->toString()); $token = $dbForProject->createDocument('tokens', $token ->setAttribute('$permissions', [ @@ -2015,7 +1999,7 @@ Http::post('/v1/account/tokens/magic-url') ->dynamic($token, Response::MODEL_TOKEN); }); -Http::post('/v1/account/tokens/email') +App::post('/v1/account/tokens/email') ->desc('Create email token (OTP)') ->groups(['api', 'account', 'auth']) ->label('scope', 'sessions.write') @@ -2043,8 +2027,7 @@ Http::post('/v1/account/tokens/email') ->inject('locale') ->inject('queueForEvents') ->inject('queueForMails') - ->inject('authorization') - ->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, Authorization $authorization) { + ->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails) { if (empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled'); } @@ -2053,12 +2036,12 @@ Http::post('/v1/account/tokens/email') $phrase = Phrase::generate(); } - $roles = $authorization->getRoles(); + $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); $result = $dbForProject->findOne('users', [Query::equal('email', [$email])]); - if (!$result->isEmpty()) { + if ($result !== false && !$result->isEmpty()) { $user->setAttributes($result->getArrayCopy()); } else { $limit = $project->getAttribute('auths', [])['limit'] ?? 0; @@ -2075,7 +2058,7 @@ Http::post('/v1/account/tokens/email') $identityWithMatchingEmail = $dbForProject->findOne('identities', [ Query::equal('providerEmail', [$email]), ]); - if (!$identityWithMatchingEmail->isEmpty()) { + if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */ } @@ -2106,7 +2089,7 @@ Http::post('/v1/account/tokens/email') ]); $user->removeAttribute('$internalId'); - $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user)); + Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); } $tokenSecret = Auth::codeGenerator(6); @@ -2123,7 +2106,7 @@ Http::post('/v1/account/tokens/email') 'ip' => $request->getIP(), ]); - $authorization->addRole(Role::user($user->getId())->toString()); + Authorization::setRole(Role::user($user->getId())->toString()); $token = $dbForProject->createDocument('tokens', $token ->setAttribute('$permissions', [ @@ -2245,7 +2228,7 @@ Http::post('/v1/account/tokens/email') ->dynamic($token, Response::MODEL_TOKEN); }); -Http::put('/v1/account/sessions/magic-url') +App::put('/v1/account/sessions/magic-url') ->desc('Update magic URL session') ->label('event', 'users.[userId].sessions.[sessionId].create') ->groups(['api', 'account', 'session']) @@ -2274,11 +2257,9 @@ Http::put('/v1/account/sessions/magic-url') ->inject('geodb') ->inject('queueForEvents') ->inject('queueForMails') - ->inject('authorization') - ->inject('authentication') ->action($createSession); -Http::put('/v1/account/sessions/phone') +App::put('/v1/account/sessions/phone') ->desc('Update phone session') ->label('event', 'users.[userId].sessions.[sessionId].create') ->groups(['api', 'account', 'session']) @@ -2307,11 +2288,9 @@ Http::put('/v1/account/sessions/phone') ->inject('geodb') ->inject('queueForEvents') ->inject('queueForMails') - ->inject('authorization') - ->inject('authentication') ->action($createSession); -Http::post('/v1/account/tokens/phone') +App::post('/v1/account/tokens/phone') ->alias('/v1/account/sessions/phone') ->desc('Create phone token') ->groups(['api', 'account']) @@ -2339,18 +2318,17 @@ Http::post('/v1/account/tokens/phone') ->inject('queueForEvents') ->inject('queueForMessaging') ->inject('locale') - ->inject('authorization') - ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, Authorization $authorization) { + ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale) { if (empty(System::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); } - $roles = $authorization->getRoles(); + $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); $result = $dbForProject->findOne('users', [Query::equal('phone', [$phone])]); - if (!$result->isEmpty()) { + if ($result !== false && !$result->isEmpty()) { $user->setAttributes($result->getArrayCopy()); } else { $limit = $project->getAttribute('auths', [])['limit'] ?? 0; @@ -2389,9 +2367,9 @@ Http::post('/v1/account/tokens/phone') ]); $user->removeAttribute('$internalId'); - $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user)); + Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); try { - $target = $authorization->skip(fn () => $dbForProject->createDocument('targets', new Document([ + $target = Authorization::skip(fn () => $dbForProject->createDocument('targets', new Document([ '$permissions' => [ Permission::read(Role::user($user->getId())), Permission::update(Role::user($user->getId())), @@ -2437,7 +2415,7 @@ Http::post('/v1/account/tokens/phone') 'ip' => $request->getIP(), ]); - $authorization->addRole(Role::user($user->getId())->toString()); + Authorization::setRole(Role::user($user->getId())->toString()); $token = $dbForProject->createDocument('tokens', $token ->setAttribute('$permissions', [ @@ -2493,7 +2471,7 @@ Http::post('/v1/account/tokens/phone') ->dynamic($token, Response::MODEL_TOKEN); }); -Http::post('/v1/account/jwts') +App::post('/v1/account/jwts') ->alias('/v1/account/jwt') ->desc('Create JWT') ->groups(['api', 'account', 'auth']) @@ -2511,15 +2489,14 @@ Http::post('/v1/account/jwts') ->inject('response') ->inject('user') ->inject('dbForProject') - ->inject('authentication') - ->action(function (Response $response, Document $user, Database $dbForProject, Authentication $authentication) { + ->action(function (Response $response, Document $user, Database $dbForProject) { $sessions = $user->getAttribute('sessions', []); $current = new Document(); foreach ($sessions as $session) { /** @var Utopia\Database\Document $session */ - if ($session->getAttribute('secret') == Auth::hash($authentication->getSecret())) { // If current session delete the cookies too + if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too $current = $session; } } @@ -2538,7 +2515,7 @@ Http::post('/v1/account/jwts') ])]), Response::MODEL_JWT); }); -Http::get('/v1/account/prefs') +App::get('/v1/account/prefs') ->desc('Get account preferences') ->groups(['api', 'account']) ->label('scope', 'account') @@ -2560,7 +2537,7 @@ Http::get('/v1/account/prefs') $response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES); }); -Http::get('/v1/account/logs') +App::get('/v1/account/logs') ->desc('List logs') ->groups(['api', 'account']) ->label('scope', 'account') @@ -2577,8 +2554,7 @@ Http::get('/v1/account/logs') ->inject('locale') ->inject('geodb') ->inject('dbForProject') - ->inject('authorization') - ->action(function (array $queries, Response $response, Document $user, Locale $locale, Reader $geodb, Database $dbForProject, Authorization $authorization) { + ->action(function (array $queries, Response $response, Document $user, Locale $locale, Reader $geodb, Database $dbForProject) { try { $queries = Query::parseQueries($queries); @@ -2590,7 +2566,7 @@ Http::get('/v1/account/logs') $limit = $grouped['limit'] ?? APP_LIMIT_COUNT; $offset = $grouped['offset'] ?? 0; - $audit = new EventAudit($dbForProject, $authorization); + $audit = new EventAudit($dbForProject); $logs = $audit->getLogsByUser($user->getInternalId(), $limit, $offset); @@ -2626,101 +2602,7 @@ Http::get('/v1/account/logs') ]), Response::MODEL_LOG_LIST); }); -Http::patch('/v1/account/email') - ->desc('Update email') - ->groups(['api', 'account']) - ->label('event', 'users.[userId].update.email') - ->label('scope', 'account') - ->label('audits.event', 'user.update') - ->label('audits.resource', 'user/{response.$id}') - ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) - ->label('sdk.namespace', 'account') - ->label('sdk.method', 'updateEmail') - ->label('sdk.description', '/docs/references/account/update-email.md') - ->label('sdk.response.code', Response::STATUS_CODE_OK) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_USER) - ->label('sdk.offline.model', '/account') - ->label('sdk.offline.key', 'current') - ->param('email', '', new Email(), 'User email.') - ->param('password', '', new Password(), 'User password. Must be at least 8 chars.') - ->inject('requestTimestamp') - ->inject('response') - ->inject('user') - ->inject('dbForProject') - ->inject('queueForEvents') - ->inject('project') - ->inject('hooks') - ->inject('authorization') - ->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, Authorization $authorization) { - // passwordUpdate will be empty if the user has never set a password - $passwordUpdate = $user->getAttribute('passwordUpdate'); - - if ( - !empty($passwordUpdate) && - !Auth::passwordVerify($password, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions')) - ) { // Double check user password - throw new Exception(Exception::USER_INVALID_CREDENTIALS); - } - - $hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, false]); - - $oldEmail = $user->getAttribute('email'); - - $email = \strtolower($email); - - // Makes sure this email is not already used in another identity - $identityWithMatchingEmail = $dbForProject->findOne('identities', [ - Query::equal('providerEmail', [$email]), - Query::notEqual('userInternalId', $user->getInternalId()), - ]); - if (!$identityWithMatchingEmail->isEmpty()) { - throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */ - } - - $user - ->setAttribute('email', $email) - ->setAttribute('emailVerification', false) // After this user needs to confirm mail again - ; - - if (empty($passwordUpdate)) { - $user - ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)) - ->setAttribute('hash', Auth::DEFAULT_ALGO) - ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS) - ->setAttribute('passwordUpdate', DateTime::now()); - } - - $target = $authorization->skip(fn () => $dbForProject->findOne('targets', [ - Query::equal('identifier', [$email]), - ])); - - if (!$target->isEmpty()) { - throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); - } - - try { - $user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user)); - /** - * @var Document $oldTarget - */ - $oldTarget = $user->find('identifier', $oldEmail, 'targets'); - - if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) { - $authorization->skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email))); - } - $dbForProject->purgeCachedDocument('users', $user->getId()); - } catch (Duplicate) { - throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */ - } - - $queueForEvents->setParam('userId', $user->getId()); - - $response->dynamic($user, Response::MODEL_ACCOUNT); - }); - - -Http::patch('/v1/account/name') +App::patch('/v1/account/name') ->desc('Update name') ->groups(['api', 'account']) ->label('event', 'users.[userId].update.name') @@ -2753,7 +2635,7 @@ Http::patch('/v1/account/name') $response->dynamic($user, Response::MODEL_ACCOUNT); }); -Http::patch('/v1/account/password') +App::patch('/v1/account/password') ->desc('Update password') ->groups(['api', 'account']) ->label('event', 'users.[userId].update.password') @@ -2823,7 +2705,99 @@ Http::patch('/v1/account/password') $response->dynamic($user, Response::MODEL_ACCOUNT); }); -Http::patch('/v1/account/phone') +App::patch('/v1/account/email') + ->desc('Update email') + ->groups(['api', 'account']) + ->label('event', 'users.[userId].update.email') + ->label('scope', 'account') + ->label('audits.event', 'user.update') + ->label('audits.resource', 'user/{response.$id}') + ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) + ->label('sdk.namespace', 'account') + ->label('sdk.method', 'updateEmail') + ->label('sdk.description', '/docs/references/account/update-email.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_USER) + ->label('sdk.offline.model', '/account') + ->label('sdk.offline.key', 'current') + ->param('email', '', new Email(), 'User email.') + ->param('password', '', new Password(), 'User password. Must be at least 8 chars.') + ->inject('requestTimestamp') + ->inject('response') + ->inject('user') + ->inject('dbForProject') + ->inject('queueForEvents') + ->inject('project') + ->inject('hooks') + ->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks) { + // passwordUpdate will be empty if the user has never set a password + $passwordUpdate = $user->getAttribute('passwordUpdate'); + + if ( + !empty($passwordUpdate) && + !Auth::passwordVerify($password, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions')) + ) { // Double check user password + throw new Exception(Exception::USER_INVALID_CREDENTIALS); + } + + $hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, false]); + + $oldEmail = $user->getAttribute('email'); + + $email = \strtolower($email); + + // Makes sure this email is not already used in another identity + $identityWithMatchingEmail = $dbForProject->findOne('identities', [ + Query::equal('providerEmail', [$email]), + Query::notEqual('userInternalId', $user->getInternalId()), + ]); + if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { + throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */ + } + + $user + ->setAttribute('email', $email) + ->setAttribute('emailVerification', false) // After this user needs to confirm mail again + ; + + if (empty($passwordUpdate)) { + $user + ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS)) + ->setAttribute('hash', Auth::DEFAULT_ALGO) + ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS) + ->setAttribute('passwordUpdate', DateTime::now()); + } + + $target = Authorization::skip(fn () => $dbForProject->findOne('targets', [ + Query::equal('identifier', [$email]), + ])); + + if ($target instanceof Document && !$target->isEmpty()) { + throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); + } + + try { + $user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user)); + /** + * @var Document $oldTarget + */ + $oldTarget = $user->find('identifier', $oldEmail, 'targets'); + + if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) { + Authorization::skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email))); + } + $dbForProject->purgeCachedDocument('users', $user->getId()); + } catch (Duplicate) { + throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */ + } + + $queueForEvents->setParam('userId', $user->getId()); + + $response->dynamic($user, Response::MODEL_ACCOUNT); + }); + +App::patch('/v1/account/phone') ->desc('Update phone') ->groups(['api', 'account']) ->label('event', 'users.[userId].update.phone') @@ -2848,8 +2822,7 @@ Http::patch('/v1/account/phone') ->inject('queueForEvents') ->inject('project') ->inject('hooks') - ->inject('authorization') - ->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, Authorization $authorization) { + ->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks) { // passwordUpdate will be empty if the user has never set a password $passwordUpdate = $user->getAttribute('passwordUpdate'); @@ -2862,11 +2835,11 @@ Http::patch('/v1/account/phone') $hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, false]); - $target = $authorization->skip(fn () => $dbForProject->findOne('targets', [ + $target = Authorization::skip(fn () => $dbForProject->findOne('targets', [ Query::equal('identifier', [$phone]), ])); - if (!$target->isEmpty()) { + if ($target instanceof Document && !$target->isEmpty()) { throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); } @@ -2893,7 +2866,7 @@ Http::patch('/v1/account/phone') $oldTarget = $user->find('identifier', $oldPhone, 'targets'); if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) { - $authorization->skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone))); + Authorization::skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone))); } $dbForProject->purgeCachedDocument('users', $user->getId()); } catch (Duplicate $th) { @@ -2905,7 +2878,7 @@ Http::patch('/v1/account/phone') $response->dynamic($user, Response::MODEL_ACCOUNT); }); -Http::patch('/v1/account/prefs') +App::patch('/v1/account/prefs') ->desc('Update preferences') ->groups(['api', 'account']) ->label('event', 'users.[userId].update.prefs') @@ -2938,7 +2911,7 @@ Http::patch('/v1/account/prefs') $response->dynamic($user, Response::MODEL_ACCOUNT); }); -Http::patch('/v1/account/status') +App::patch('/v1/account/status') ->desc('Update status') ->groups(['api', 'account']) ->label('event', 'users.[userId].update.status') @@ -2958,8 +2931,7 @@ Http::patch('/v1/account/status') ->inject('user') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('authentication') - ->action(function (?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authentication $authentication) { + ->action(function (?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) { $user->setAttribute('status', false); @@ -2975,14 +2947,14 @@ Http::patch('/v1/account/status') $protocol = $request->getProtocol(); $response - ->addCookie($authentication->getCookieName() . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie($authentication->getCookieName(), '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ; $response->dynamic($user, Response::MODEL_ACCOUNT); }); -Http::post('/v1/account/recovery') +App::post('/v1/account/recovery') ->desc('Create password recovery') ->groups(['api', 'account']) ->label('scope', 'sessions.write') @@ -3009,15 +2981,14 @@ Http::post('/v1/account/recovery') ->inject('locale') ->inject('queueForMails') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $email, string $url, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Mail $queueForMails, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $email, string $url, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Mail $queueForMails, Event $queueForEvents) { if (empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled'); } $url = htmlentities($url); - $roles = $authorization->getRoles(); + $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -3027,7 +2998,7 @@ Http::post('/v1/account/recovery') Query::equal('email', [$email]), ]); - if ($profile->isEmpty()) { + if (!$profile) { throw new Exception(Exception::USER_NOT_FOUND); } @@ -3051,7 +3022,7 @@ Http::post('/v1/account/recovery') 'ip' => $request->getIP(), ]); - $authorization->addRole(Role::user($profile->getId())->toString()); + Authorization::setRole(Role::user($profile->getId())->toString()); $recovery = $dbForProject->createDocument('tokens', $recovery ->setAttribute('$permissions', [ @@ -3073,7 +3044,7 @@ Http::post('/v1/account/recovery') $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl'); $message - ->setParam('{{body}}', $body, escape: false) + ->setParam('{{body}}', $body, escapeHtml: false) ->setParam('{{hello}}', $locale->getText("emails.recovery.hello")) ->setParam('{{footer}}', $locale->getText("emails.recovery.footer")) ->setParam('{{thanks}}', $locale->getText("emails.recovery.thanks")) @@ -3163,7 +3134,7 @@ Http::post('/v1/account/recovery') ->dynamic($recovery, Response::MODEL_TOKEN); }); -Http::put('/v1/account/recovery') +App::put('/v1/account/recovery') ->desc('Create password recovery (confirmation)') ->groups(['api', 'account']) ->label('scope', 'sessions.write') @@ -3189,8 +3160,7 @@ Http::put('/v1/account/recovery') ->inject('project') ->inject('queueForEvents') ->inject('hooks') - ->inject('authorization') - ->action(function (string $userId, string $secret, string $password, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks, Authorization $authorization) { + ->action(function (string $userId, string $secret, string $password, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks) { $profile = $dbForProject->getDocument('users', $userId); if ($profile->isEmpty()) { @@ -3204,7 +3174,7 @@ Http::put('/v1/account/recovery') throw new Exception(Exception::USER_INVALID_TOKEN); } - $authorization->addRole(Role::user($profile->getId())->toString()); + Authorization::setRole(Role::user($profile->getId())->toString()); $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); @@ -3249,7 +3219,7 @@ Http::put('/v1/account/recovery') $response->dynamic($recoveryDocument, Response::MODEL_TOKEN); }); -Http::post('/v1/account/verification') +App::post('/v1/account/verification') ->desc('Create email verification') ->groups(['api', 'account']) ->label('scope', 'account') @@ -3274,8 +3244,7 @@ Http::post('/v1/account/verification') ->inject('locale') ->inject('queueForEvents') ->inject('queueForMails') - ->inject('authorization') - ->action(function (string $url, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, Authorization $authorization) { + ->action(function (string $url, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails) { if (empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled'); @@ -3286,7 +3255,7 @@ Http::post('/v1/account/verification') throw new Exception(Exception::USER_EMAIL_ALREADY_VERIFIED); } - $roles = $authorization->getRoles(); + $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); $verificationSecret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_VERIFICATION); @@ -3303,7 +3272,7 @@ Http::post('/v1/account/verification') 'ip' => $request->getIP(), ]); - $authorization->addRole(Role::user($user->getId())->toString()); + Authorization::setRole(Role::user($user->getId())->toString()); $verification = $dbForProject->createDocument('tokens', $verification ->setAttribute('$permissions', [ @@ -3325,7 +3294,7 @@ Http::post('/v1/account/verification') $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl'); $message - ->setParam('{{body}}', $body, escape: false) + ->setParam('{{body}}', $body, escapeHtml: false) ->setParam('{{hello}}', $locale->getText("emails.verification.hello")) ->setParam('{{footer}}', $locale->getText("emails.verification.footer")) ->setParam('{{thanks}}', $locale->getText("emails.verification.thanks")) @@ -3415,7 +3384,7 @@ Http::post('/v1/account/verification') ->dynamic($verification, Response::MODEL_TOKEN); }); -Http::put('/v1/account/verification') +App::put('/v1/account/verification') ->desc('Create email verification (confirmation)') ->groups(['api', 'account']) ->label('scope', 'public') @@ -3437,10 +3406,9 @@ Http::put('/v1/account/verification') ->inject('user') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) { - $profile = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId)); + $profile = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId)); if ($profile->isEmpty()) { throw new Exception(Exception::USER_NOT_FOUND); @@ -3453,7 +3421,7 @@ Http::put('/v1/account/verification') throw new Exception(Exception::USER_INVALID_TOKEN); } - $authorization->addRole(Role::user($profile->getId())->toString()); + Authorization::setRole(Role::user($profile->getId())->toString()); $profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('emailVerification', true)); @@ -3475,7 +3443,7 @@ Http::put('/v1/account/verification') $response->dynamic($verification, Response::MODEL_TOKEN); }); -Http::post('/v1/account/verification/phone') +App::post('/v1/account/verification/phone') ->desc('Create phone verification') ->groups(['api', 'account', 'auth']) ->label('scope', 'account') @@ -3500,8 +3468,7 @@ Http::post('/v1/account/verification/phone') ->inject('queueForMessaging') ->inject('project') ->inject('locale') - ->inject('authorization') - ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale, Authorization $authorization) { + ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale) { if (empty(System::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); } @@ -3515,7 +3482,7 @@ Http::post('/v1/account/verification/phone') throw new Exception(Exception::USER_PHONE_ALREADY_VERIFIED); } - $roles = $authorization->getRoles(); + $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -3544,7 +3511,7 @@ Http::post('/v1/account/verification/phone') 'ip' => $request->getIP(), ]); - $authorization->addRole(Role::user($user->getId())->toString()); + Authorization::setRole(Role::user($user->getId())->toString()); $verification = $dbForProject->createDocument('tokens', $verification ->setAttribute('$permissions', [ @@ -3604,7 +3571,7 @@ Http::post('/v1/account/verification/phone') ->dynamic($verification, Response::MODEL_TOKEN); }); -Http::put('/v1/account/verification/phone') +App::put('/v1/account/verification/phone') ->desc('Update phone verification (confirmation)') ->groups(['api', 'account']) ->label('scope', 'public') @@ -3626,10 +3593,9 @@ Http::put('/v1/account/verification/phone') ->inject('user') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) { - $profile = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId)); + $profile = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId)); if ($profile->isEmpty()) { throw new Exception(Exception::USER_NOT_FOUND); @@ -3641,7 +3607,7 @@ Http::put('/v1/account/verification/phone') throw new Exception(Exception::USER_INVALID_TOKEN); } - $authorization->addRole(Role::user($profile->getId())->toString()); + Authorization::setRole(Role::user($profile->getId())->toString()); $profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('phoneVerification', true)); @@ -3663,7 +3629,7 @@ Http::put('/v1/account/verification/phone') $response->dynamic($verificationDocument, Response::MODEL_TOKEN); }); -Http::patch('/v1/account/mfa') +App::patch('/v1/account/mfa') ->desc('Update MFA') ->groups(['api', 'account']) ->label('event', 'users.[userId].update.mfa') @@ -3716,8 +3682,8 @@ Http::patch('/v1/account/mfa') $response->dynamic($user, Response::MODEL_ACCOUNT); }); -Http::get('/v1/account/mfa/factors') - ->desc('List Factors') +App::get('/v1/account/mfa/factors') + ->desc('List factors') ->groups(['api', 'account', 'mfa']) ->label('scope', 'account') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) @@ -3748,8 +3714,8 @@ Http::get('/v1/account/mfa/factors') $response->dynamic($factors, Response::MODEL_MFA_FACTORS); }); -Http::post('/v1/account/mfa/authenticators/:type') - ->desc('Create Authenticator') +App::post('/v1/account/mfa/authenticators/:type') + ->desc('Create authenticator') ->groups(['api', 'account']) ->label('event', 'users.[userId].update.mfa') ->label('scope', 'account') @@ -3820,8 +3786,8 @@ Http::post('/v1/account/mfa/authenticators/:type') $response->dynamic($model, Response::MODEL_MFA_TYPE); }); -Http::put('/v1/account/mfa/authenticators/:type') - ->desc('Verify Authenticator') +App::put('/v1/account/mfa/authenticators/:type') + ->desc('Verify authenticator') ->groups(['api', 'account']) ->label('event', 'users.[userId].update.mfa') ->label('scope', 'account') @@ -3885,8 +3851,8 @@ Http::put('/v1/account/mfa/authenticators/:type') $response->dynamic($user, Response::MODEL_ACCOUNT); }); -Http::post('/v1/account/mfa/recovery-codes') - ->desc('Create MFA Recovery Codes') +App::post('/v1/account/mfa/recovery-codes') + ->desc('Create MFA recovery codes') ->groups(['api', 'account']) ->label('event', 'users.[userId].update.mfa') ->label('scope', 'account') @@ -3927,8 +3893,8 @@ Http::post('/v1/account/mfa/recovery-codes') $response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES); }); -Http::patch('/v1/account/mfa/recovery-codes') - ->desc('Regenerate MFA Recovery Codes') +App::patch('/v1/account/mfa/recovery-codes') + ->desc('Regenerate MFA recovery codes') ->groups(['api', 'account', 'mfaProtected']) ->label('event', 'users.[userId].update.mfa') ->label('scope', 'account') @@ -3968,8 +3934,8 @@ Http::patch('/v1/account/mfa/recovery-codes') $response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES); }); -Http::get('/v1/account/mfa/recovery-codes') - ->desc('Get MFA Recovery Codes') +App::get('/v1/account/mfa/recovery-codes') + ->desc('Get MFA recovery codes') ->groups(['api', 'account', 'mfaProtected']) ->label('scope', 'account') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) @@ -3998,8 +3964,8 @@ Http::get('/v1/account/mfa/recovery-codes') $response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES); }); -Http::delete('/v1/account/mfa/authenticators/:type') - ->desc('Delete Authenticator') +App::delete('/v1/account/mfa/authenticators/:type') + ->desc('Delete authenticator') ->groups(['api', 'account', 'mfaProtected']) ->label('event', 'users.[userId].delete.mfa') ->label('scope', 'account') @@ -4036,8 +4002,8 @@ Http::delete('/v1/account/mfa/authenticators/:type') $response->noContent(); }); -Http::post('/v1/account/mfa/challenge') - ->desc('Create MFA Challenge') +App::post('/v1/account/mfa/challenge') + ->desc('Create MFA challenge') ->groups(['api', 'account', 'mfa']) ->label('scope', 'account') ->label('event', 'users.[userId].challenges.[challengeId].create') @@ -4224,8 +4190,8 @@ Http::post('/v1/account/mfa/challenge') $response->dynamic($challenge, Response::MODEL_MFA_CHALLENGE); }); -Http::put('/v1/account/mfa/challenge') - ->desc('Create MFA Challenge (confirmation)') +App::put('/v1/account/mfa/challenge') + ->desc('Create MFA challenge (confirmation)') ->groups(['api', 'account', 'mfa']) ->label('scope', 'account') ->label('event', 'users.[userId].sessions.[sessionId].create') @@ -4311,7 +4277,7 @@ Http::put('/v1/account/mfa/challenge') $response->dynamic($session, Response::MODEL_SESSION); }); -Http::post('/v1/account/targets/push') +App::post('/v1/account/targets/push') ->desc('Create push target') ->groups(['api', 'account']) ->label('scope', 'targets.write') @@ -4332,14 +4298,12 @@ Http::post('/v1/account/targets/push') ->inject('request') ->inject('response') ->inject('dbForProject') - ->inject('authorization') - ->inject('authentication') - ->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization, Authentication $authentication) { + ->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject) { $targetId = $targetId == 'unique()' ? ID::unique() : $targetId; - $provider = $authorization->skip(fn () => $dbForProject->getDocument('providers', $providerId)); + $provider = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId)); - $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId)); + $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId)); if (!$target->isEmpty()) { throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); @@ -4350,7 +4314,7 @@ Http::post('/v1/account/targets/push') $device = $detector->getDevice(); - $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), $authentication->getSecret()); + $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret); $session = $dbForProject->getDocument('sessions', $sessionId); try { @@ -4386,7 +4350,7 @@ Http::post('/v1/account/targets/push') ->dynamic($target, Response::MODEL_TARGET); }); -Http::put('/v1/account/targets/:targetId/push') +App::put('/v1/account/targets/:targetId/push') ->desc('Update push target') ->groups(['api', 'account']) ->label('scope', 'targets.write') @@ -4406,10 +4370,9 @@ Http::put('/v1/account/targets/:targetId/push') ->inject('request') ->inject('response') ->inject('dbForProject') - ->inject('authorization') - ->action(function (string $targetId, string $identifier, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) { + ->action(function (string $targetId, string $identifier, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject) { - $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId)); + $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId)); if ($target->isEmpty()) { throw new Exception(Exception::USER_TARGET_NOT_FOUND); @@ -4442,7 +4405,7 @@ Http::put('/v1/account/targets/:targetId/push') ->dynamic($target, Response::MODEL_TARGET); }); -Http::delete('/v1/account/targets/:targetId/push') +App::delete('/v1/account/targets/:targetId/push') ->desc('Delete push target') ->groups(['api', 'account']) ->label('scope', 'targets.write') @@ -4462,9 +4425,8 @@ Http::delete('/v1/account/targets/:targetId/push') ->inject('request') ->inject('response') ->inject('dbForProject') - ->inject('authorization') - ->action(function (string $targetId, Event $queueForEvents, Delete $queueForDeletes, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) { - $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId)); + ->action(function (string $targetId, Event $queueForEvents, Delete $queueForDeletes, Document $user, Request $request, Response $response, Database $dbForProject) { + $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId)); if ($target->isEmpty()) { throw new Exception(Exception::USER_TARGET_NOT_FOUND); @@ -4489,8 +4451,8 @@ Http::delete('/v1/account/targets/:targetId/push') $response->noContent(); }); -Http::get('/v1/account/identities') - ->desc('List Identities') +App::get('/v1/account/identities') + ->desc('List identities') ->groups(['api', 'account']) ->label('scope', 'account') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) @@ -4545,7 +4507,7 @@ Http::get('/v1/account/identities') ]), Response::MODEL_IDENTITY_LIST); }); -Http::delete('/v1/account/identities/:identityId') +App::delete('/v1/account/identities/:identityId') ->desc('Delete identity') ->groups(['api', 'account']) ->label('scope', 'account') diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index 2b8327e898..fcff3e4179 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -5,7 +5,7 @@ use Appwrite\URL\URL as URLParse; use Appwrite\Utopia\Response; use chillerlan\QRCode\QRCode; use chillerlan\QRCode\QROptions; -use Utopia\CLI\Console; +use Utopia\App; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\DateTime; @@ -14,17 +14,15 @@ use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\UID; use Utopia\Domains\Domain; use Utopia\Fetch\Client; -use Utopia\Http\Http; -use Utopia\Http\Validator\Boolean; -use Utopia\Http\Validator\HexColor; -use Utopia\Http\Validator\Range; -use Utopia\Http\Validator\Text; -use Utopia\Http\Validator\URL; -use Utopia\Http\Validator\WhiteList; use Utopia\Image\Image; -use Utopia\Logger\Log; use Utopia\Logger\Logger; use Utopia\System\System; +use Utopia\Validator\Boolean; +use Utopia\Validator\HexColor; +use Utopia\Validator\Range; +use Utopia\Validator\Text; +use Utopia\Validator\URL; +use Utopia\Validator\WhiteList; $avatarCallback = function (string $type, string $code, int $width, int $height, int $quality, Response $response) { @@ -63,9 +61,9 @@ $avatarCallback = function (string $type, string $code, int $width, int $height, unset($image); }; -$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForConsole, ?Logger $logger, Authorization $auth) { +$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForConsole, ?Logger $logger) { try { - $user = $auth->skip(fn () => $dbForConsole->getDocument('users', $userId)); + $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); $sessions = $user->getAttribute('sessions', []); @@ -116,7 +114,7 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro ->setAttribute('providerRefreshToken', $refreshToken) ->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry(''))); - $auth->skip(fn () => $dbForProject->updateDocument('sessions', $gitHubSession->getId(), $gitHubSession)); + Authorization::skip(fn () => $dbForProject->updateDocument('sessions', $gitHubSession->getId(), $gitHubSession)); $dbForProject->purgeCachedDocument('users', $user->getId()); } catch (Throwable $err) { @@ -124,7 +122,7 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro do { $previousAccessToken = $gitHubSession->getAttribute('providerAccessToken'); - $user = $auth->skip(fn () => $dbForConsole->getDocument('users', $userId)); + $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); $sessions = $user->getAttribute('sessions', []); $gitHubSession = new Document(); @@ -156,42 +154,11 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro 'id' => $githubId ]; } catch (Exception $error) { - if ($logger) { - $version = System::getEnv('_APP_VERSION', 'UNKNOWN'); - - $log = new Log(); - $log->setNamespace('console'); - $log->setServer(\gethostname()); - $log->setVersion($version); - $log->setType(Log::TYPE_ERROR); - $log->setMessage($error->getMessage()); - - $log->addTag('code', $error->getCode()); - $log->addTag('verboseType', get_class($error)); - - $log->addExtra('file', $error->getFile()); - $log->addExtra('line', $error->getLine()); - $log->addExtra('trace', $error->getTraceAsString()); - $log->addExtra('detailedTrace', $error->getTrace()); - - $log->setAction('avatarsGetGitHub'); - - $isProduction = System::getEnv('_APP_ENV', 'development') === 'production'; - - $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING); - - $responseCode = $logger->addLog($log); - Console::info('GitHub error log pushed with status code: ' . $responseCode); - } - - Console::warning("Failed: {$error->getMessage()}"); - Console::warning($error->getTraceAsString()); - return []; } }; -Http::get('/v1/avatars/credit-cards/:code') +App::get('/v1/avatars/credit-cards/:code') ->desc('Get credit card icon') ->groups(['api', 'avatars']) ->label('scope', 'avatars.read') @@ -211,7 +178,7 @@ Http::get('/v1/avatars/credit-cards/:code') ->inject('response') ->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('credit-cards', $code, $width, $height, $quality, $response)); -Http::get('/v1/avatars/browsers/:code') +App::get('/v1/avatars/browsers/:code') ->desc('Get browser icon') ->groups(['api', 'avatars']) ->label('scope', 'avatars.read') @@ -231,7 +198,7 @@ Http::get('/v1/avatars/browsers/:code') ->inject('response') ->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('browsers', $code, $width, $height, $quality, $response)); -Http::get('/v1/avatars/flags/:code') +App::get('/v1/avatars/flags/:code') ->desc('Get country flag') ->groups(['api', 'avatars']) ->label('scope', 'avatars.read') @@ -251,7 +218,7 @@ Http::get('/v1/avatars/flags/:code') ->inject('response') ->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('flags', $code, $width, $height, $quality, $response)); -Http::get('/v1/avatars/image') +App::get('/v1/avatars/image') ->desc('Get image from URL') ->groups(['api', 'avatars']) ->label('scope', 'avatars.read') @@ -314,7 +281,7 @@ Http::get('/v1/avatars/image') unset($image); }); -Http::get('/v1/avatars/favicon') +App::get('/v1/avatars/favicon') ->desc('Get favicon') ->groups(['api', 'avatars']) ->label('scope', 'avatars.read') @@ -459,7 +426,7 @@ Http::get('/v1/avatars/favicon') unset($image); }); -Http::get('/v1/avatars/qr') +App::get('/v1/avatars/qr') ->desc('Get QR code') ->groups(['api', 'avatars']) ->label('scope', 'avatars.read') @@ -499,7 +466,7 @@ Http::get('/v1/avatars/qr') ->send($image->output('png', 9)); }); -Http::get('/v1/avatars/initials') +App::get('/v1/avatars/initials') ->desc('Get user initials') ->groups(['api', 'avatars']) ->label('scope', 'avatars.read') @@ -582,8 +549,8 @@ Http::get('/v1/avatars/initials') ->file($image->getImageBlob()); }); -Http::get('/v1/cards/cloud') - ->desc('Get Front Of Cloud Card') +App::get('/v1/cards/cloud') + ->desc('Get front Of Cloud Card') ->groups(['api', 'avatars']) ->label('scope', 'avatars.read') ->label('cache', true) @@ -604,9 +571,8 @@ Http::get('/v1/cards/cloud') ->inject('contributors') ->inject('employees') ->inject('logger') - ->inject('authorization') - ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization) use ($getUserGitHub) { - $user = $authorization->skip(fn () => $dbForConsole->getDocument('users', $userId)); + ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) { + $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); if ($user->isEmpty() && empty($mock)) { throw new Exception(Exception::USER_NOT_FOUND); @@ -617,7 +583,7 @@ Http::get('/v1/cards/cloud') $email = $user->getAttribute('email', ''); $createdAt = new \DateTime($user->getCreatedAt()); - $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger, $authorization); + $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger); $githubName = $gitHub['name'] ?? ''; $githubId = $gitHub['id'] ?? ''; @@ -790,8 +756,8 @@ Http::get('/v1/cards/cloud') ->file($baseImage->getImageBlob()); }); -Http::get('/v1/cards/cloud-back') - ->desc('Get Back Of Cloud Card') +App::get('/v1/cards/cloud-back') + ->desc('Get back Of Cloud Card') ->groups(['api', 'avatars']) ->label('scope', 'avatars.read') ->label('cache', true) @@ -812,9 +778,8 @@ Http::get('/v1/cards/cloud-back') ->inject('contributors') ->inject('employees') ->inject('logger') - ->inject('authorization') - ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization) use ($getUserGitHub) { - $user = $authorization->skip(fn () => $dbForConsole->getDocument('users', $userId)); + ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) { + $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); if ($user->isEmpty() && empty($mock)) { throw new Exception(Exception::USER_NOT_FOUND); @@ -824,7 +789,7 @@ Http::get('/v1/cards/cloud-back') $userId = $user->getId(); $email = $user->getAttribute('email', ''); - $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger, $authorization); + $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger); $githubId = $gitHub['id'] ?? ''; $isHero = \array_key_exists($email, $heroes); @@ -869,8 +834,8 @@ Http::get('/v1/cards/cloud-back') ->file($baseImage->getImageBlob()); }); -Http::get('/v1/cards/cloud-og') - ->desc('Get OG Image From Cloud Card') +App::get('/v1/cards/cloud-og') + ->desc('Get OG image From Cloud Card') ->groups(['api', 'avatars']) ->label('scope', 'avatars.read') ->label('cache', true) @@ -891,9 +856,8 @@ Http::get('/v1/cards/cloud-og') ->inject('contributors') ->inject('employees') ->inject('logger') - ->inject('authorization') - ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization) use ($getUserGitHub) { - $user = $authorization->skip(fn () => $dbForConsole->getDocument('users', $userId)); + ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) { + $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); if ($user->isEmpty() && empty($mock)) { throw new Exception(Exception::USER_NOT_FOUND); @@ -908,7 +872,7 @@ Http::get('/v1/cards/cloud-og') $email = $user->getAttribute('email', ''); $createdAt = new \DateTime($user->getCreatedAt()); - $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger, $authorization); + $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger); $githubName = $gitHub['name'] ?? ''; $githubId = $gitHub['id'] ?? ''; diff --git a/app/controllers/api/console.php b/app/controllers/api/console.php index 2a393497ae..eeb823a3d3 100644 --- a/app/controllers/api/console.php +++ b/app/controllers/api/console.php @@ -2,12 +2,12 @@ use Appwrite\Extend\Exception; use Appwrite\Utopia\Response; +use Utopia\App; use Utopia\Database\Document; -use Utopia\Http\Http; -use Utopia\Http\Validator\Text; use Utopia\System\System; +use Utopia\Validator\Text; -Http::init() +App::init() ->groups(['console']) ->inject('project') ->action(function (Document $project) { @@ -17,7 +17,7 @@ Http::init() }); -Http::get('/v1/console/variables') +App::get('/v1/console/variables') ->desc('Get variables') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -56,8 +56,8 @@ Http::get('/v1/console/variables') $response->dynamic($variables, Response::MODEL_CONSOLE_VARIABLES); }); -Http::post('/v1/console/assistant') - ->desc('Ask Query') +App::post('/v1/console/assistant') + ->desc('Ask query') ->groups(['api', 'assistant']) ->label('scope', 'assistant.read') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 98aace214b..9c110647af 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -16,6 +16,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Indexes; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use MaxMind\Db\Reader; +use Utopia\App; use Utopia\Audit\Audit; use Utopia\Config\Config; use Utopia\Database\Database; @@ -34,7 +35,6 @@ use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; -use Utopia\Database\Validator\Authorization\Input; use Utopia\Database\Validator\Datetime as DatetimeValidator; use Utopia\Database\Validator\Index as IndexValidator; use Utopia\Database\Validator\Key; @@ -44,19 +44,18 @@ use Utopia\Database\Validator\Query\Limit; use Utopia\Database\Validator\Query\Offset; use Utopia\Database\Validator\Structure; use Utopia\Database\Validator\UID; -use Utopia\Http\Http; -use Utopia\Http\Validator\ArrayList; -use Utopia\Http\Validator\Boolean; -use Utopia\Http\Validator\FloatValidator; -use Utopia\Http\Validator\Integer; -use Utopia\Http\Validator\IP; -use Utopia\Http\Validator\JSON; -use Utopia\Http\Validator\Nullable; -use Utopia\Http\Validator\Range; -use Utopia\Http\Validator\Text; -use Utopia\Http\Validator\URL; -use Utopia\Http\Validator\WhiteList; use Utopia\Locale\Locale; +use Utopia\Validator\ArrayList; +use Utopia\Validator\Boolean; +use Utopia\Validator\FloatValidator; +use Utopia\Validator\Integer; +use Utopia\Validator\IP; +use Utopia\Validator\JSON; +use Utopia\Validator\Nullable; +use Utopia\Validator\Range; +use Utopia\Validator\Text; +use Utopia\Validator\URL; +use Utopia\Validator\WhiteList; /** * * Create attribute of varying type @@ -78,7 +77,7 @@ use Utopia\Locale\Locale; * @throws ConflictException * @throws Exception */ -function createAttribute(string $databaseId, string $collectionId, Document $attribute, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): Document +function createAttribute(string $databaseId, string $collectionId, Document $attribute, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): Document { $key = $attribute->getAttribute('key'); $type = $attribute->getAttribute('type', ''); @@ -92,7 +91,7 @@ function createAttribute(string $databaseId, string $collectionId, Document $att $default = $attribute->getAttribute('default'); $options = $attribute->getAttribute('options', []); - $db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($db->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -227,7 +226,6 @@ function createAttribute(string $databaseId, string $collectionId, Document $att } function updateAttribute( - Authorization $authorization, string $databaseId, string $collectionId, string $key, @@ -241,10 +239,10 @@ function updateAttribute( int|float $min = null, int|float $max = null, array $elements = null, - string $newKey = null, array $options = [], + string $newKey = null, ): Document { - $db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($db->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -423,19 +421,19 @@ function updateAttribute( return $attribute; } -Http::init() +App::init() ->groups(['api', 'database']) ->inject('request') ->inject('dbForProject') ->action(function (Request $request, Database $dbForProject) { $timeout = \intval($request->getHeader('x-appwrite-timeout')); - if (!empty($timeout) && Http::isDevelopment()) { + if (!empty($timeout) && App::isDevelopment()) { $dbForProject->setTimeout($timeout); } }); -Http::post('/v1/databases') +App::post('/v1/databases') ->desc('Create database') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].create') @@ -513,7 +511,7 @@ Http::post('/v1/databases') ->dynamic($database, Response::MODEL_DATABASE); }); -Http::get('/v1/databases') +App::get('/v1/databases') ->desc('List databases') ->groups(['api', 'database']) ->label('scope', 'databases.read') @@ -566,7 +564,7 @@ Http::get('/v1/databases') ]), Response::MODEL_DATABASE_LIST); }); -Http::get('/v1/databases/:databaseId') +App::get('/v1/databases/:databaseId') ->desc('Get database') ->groups(['api', 'database']) ->label('scope', 'databases.read') @@ -591,7 +589,7 @@ Http::get('/v1/databases/:databaseId') $response->dynamic($database, Response::MODEL_DATABASE); }); -Http::get('/v1/databases/:databaseId/logs') +App::get('/v1/databases/:databaseId/logs') ->desc('List database logs') ->groups(['api', 'database']) ->label('scope', 'databases.read') @@ -608,8 +606,7 @@ Http::get('/v1/databases/:databaseId/logs') ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->inject('authorization') - ->action(function (string $databaseId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) { + ->action(function (string $databaseId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { $database = $dbForProject->getDocument('databases', $databaseId); @@ -683,7 +680,7 @@ Http::get('/v1/databases/:databaseId/logs') }); -Http::put('/v1/databases/:databaseId') +App::put('/v1/databases/:databaseId') ->desc('Update database') ->groups(['api', 'database', 'schema']) ->label('scope', 'databases.write') @@ -721,7 +718,7 @@ Http::put('/v1/databases/:databaseId') $response->dynamic($database, Response::MODEL_DATABASE); }); -Http::delete('/v1/databases/:databaseId') +App::delete('/v1/databases/:databaseId') ->desc('Delete database') ->groups(['api', 'database', 'schema']) ->label('scope', 'databases.write') @@ -769,7 +766,7 @@ Http::delete('/v1/databases/:databaseId') $response->noContent(); }); -Http::post('/v1/databases/:databaseId/collections') +App::post('/v1/databases/:databaseId/collections') ->desc('Create collection') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].create') @@ -793,10 +790,9 @@ Http::post('/v1/databases/:databaseId/collections') ->inject('dbForProject') ->inject('mode') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, Response $response, Database $dbForProject, string $mode, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, Response $response, Database $dbForProject, string $mode, Event $queueForEvents) { - $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -837,7 +833,7 @@ Http::post('/v1/databases/:databaseId/collections') ->dynamic($collection, Response::MODEL_COLLECTION); }); -Http::get('/v1/databases/:databaseId/collections') +App::get('/v1/databases/:databaseId/collections') ->alias('/v1/database/collections', ['databaseId' => 'default']) ->desc('List collections') ->groups(['api', 'database']) @@ -855,10 +851,9 @@ Http::get('/v1/databases/:databaseId/collections') ->inject('response') ->inject('dbForProject') ->inject('mode') - ->inject('authorization') - ->action(function (string $databaseId, array $queries, string $search, Response $response, Database $dbForProject, string $mode, Authorization $authorization) { + ->action(function (string $databaseId, array $queries, string $search, Response $response, Database $dbForProject, string $mode) { - $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -901,7 +896,7 @@ Http::get('/v1/databases/:databaseId/collections') ]), Response::MODEL_COLLECTION_LIST); }); -Http::get('/v1/databases/:databaseId/collections/:collectionId') +App::get('/v1/databases/:databaseId/collections/:collectionId') ->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default']) ->desc('Get collection') ->groups(['api', 'database']) @@ -918,10 +913,9 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId') ->inject('response') ->inject('dbForProject') ->inject('mode') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, string $mode, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, string $mode) { - $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -936,7 +930,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId') $response->dynamic($collection, Response::MODEL_COLLECTION); }); -Http::get('/v1/databases/:databaseId/collections/:collectionId/logs') +App::get('/v1/databases/:databaseId/collections/:collectionId/logs') ->alias('/v1/database/collections/:collectionId/logs', ['databaseId' => 'default']) ->desc('List collection logs') ->groups(['api', 'database']) @@ -955,10 +949,9 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/logs') ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { - $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -1037,7 +1030,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/logs') }); -Http::put('/v1/databases/:databaseId/collections/:collectionId') +App::put('/v1/databases/:databaseId/collections/:collectionId') ->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default']) ->desc('Update collection') ->groups(['api', 'database', 'schema']) @@ -1062,10 +1055,9 @@ Http::put('/v1/databases/:databaseId/collections/:collectionId') ->inject('dbForProject') ->inject('mode') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, Response $response, Database $dbForProject, string $mode, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, Response $response, Database $dbForProject, string $mode, Event $queueForEvents) { - $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -1101,7 +1093,7 @@ Http::put('/v1/databases/:databaseId/collections/:collectionId') $response->dynamic($collection, Response::MODEL_COLLECTION); }); -Http::delete('/v1/databases/:databaseId/collections/:collectionId') +App::delete('/v1/databases/:databaseId/collections/:collectionId') ->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default']) ->desc('Delete collection') ->groups(['api', 'database', 'schema']) @@ -1122,10 +1114,9 @@ Http::delete('/v1/databases/:databaseId/collections/:collectionId') ->inject('queueForDatabase') ->inject('queueForEvents') ->inject('mode') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, string $mode, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, string $mode) { - $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -1157,7 +1148,7 @@ Http::delete('/v1/databases/:databaseId/collections/:collectionId') $response->noContent(); }); -Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string') +App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string') ->alias('/v1/database/collections/:collectionId/attributes/string', ['databaseId' => 'default']) ->desc('Create string attribute') ->groups(['api', 'database', 'schema']) @@ -1184,8 +1175,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/strin ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, ?int $size, ?bool $required, ?string $default, bool $array, bool $encrypt, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, ?int $size, ?bool $required, ?string $default, bool $array, bool $encrypt, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { // Ensure attribute default is within required size $validator = new Text($size, 0); @@ -1207,7 +1197,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/strin 'default' => $default, 'array' => $array, 'filters' => $filters, - ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization); + ]), $response, $dbForProject, $queueForDatabase, $queueForEvents); $response @@ -1215,7 +1205,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/strin ->dynamic($attribute, Response::MODEL_ATTRIBUTE_STRING); }); -Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email') +App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email') ->alias('/v1/database/collections/:collectionId/attributes/email', ['databaseId' => 'default']) ->desc('Create email attribute') ->groups(['api', 'database', 'schema']) @@ -1240,8 +1230,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, @@ -1251,14 +1240,14 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email 'default' => $default, 'array' => $array, 'format' => APP_DATABASE_ATTRIBUTE_EMAIL, - ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization); + ]), $response, $dbForProject, $queueForDatabase, $queueForEvents); $response ->setStatusCode(Response::STATUS_CODE_ACCEPTED) ->dynamic($attribute, Response::MODEL_ATTRIBUTE_EMAIL); }); -Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum') +App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum') ->alias('/v1/database/collections/:collectionId/attributes/enum', ['databaseId' => 'default']) ->desc('Create enum attribute') ->groups(['api', 'database', 'schema']) @@ -1284,8 +1273,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum' ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, array $elements, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, array $elements, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { if (!is_null($default) && !in_array($default, $elements)) { throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Default value not found in elements'); } @@ -1299,14 +1287,14 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum' 'array' => $array, 'format' => APP_DATABASE_ATTRIBUTE_ENUM, 'formatOptions' => ['elements' => $elements], - ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization); + ]), $response, $dbForProject, $queueForDatabase, $queueForEvents); $response ->setStatusCode(Response::STATUS_CODE_ACCEPTED) ->dynamic($attribute, Response::MODEL_ATTRIBUTE_ENUM); }); -Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') +App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') ->alias('/v1/database/collections/:collectionId/attributes/ip', ['databaseId' => 'default']) ->desc('Create IP address attribute') ->groups(['api', 'database', 'schema']) @@ -1331,8 +1319,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, @@ -1342,14 +1329,14 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') 'default' => $default, 'array' => $array, 'format' => APP_DATABASE_ATTRIBUTE_IP, - ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization); + ]), $response, $dbForProject, $queueForDatabase, $queueForEvents); $response ->setStatusCode(Response::STATUS_CODE_ACCEPTED) ->dynamic($attribute, Response::MODEL_ATTRIBUTE_IP); }); -Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') +App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') ->alias('/v1/database/collections/:collectionId/attributes/url', ['databaseId' => 'default']) ->desc('Create URL attribute') ->groups(['api', 'database', 'schema']) @@ -1374,8 +1361,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, @@ -1385,14 +1371,14 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') 'default' => $default, 'array' => $array, 'format' => APP_DATABASE_ATTRIBUTE_URL, - ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization); + ]), $response, $dbForProject, $queueForDatabase, $queueForEvents); $response ->setStatusCode(Response::STATUS_CODE_ACCEPTED) ->dynamic($attribute, Response::MODEL_ATTRIBUTE_URL); }); -Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/integer') +App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/integer') ->alias('/v1/database/collections/:collectionId/attributes/integer', ['databaseId' => 'default']) ->desc('Create integer attribute') ->groups(['api', 'database', 'schema']) @@ -1419,8 +1405,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/integ ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { // Ensure attribute default is within range $min = (is_null($min)) ? PHP_INT_MIN : \intval($min); @@ -1450,7 +1435,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/integ 'min' => $min, 'max' => $max, ], - ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization); + ]), $response, $dbForProject, $queueForDatabase, $queueForEvents); $formatOptions = $attribute->getAttribute('formatOptions', []); @@ -1464,7 +1449,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/integ ->dynamic($attribute, Response::MODEL_ATTRIBUTE_INTEGER); }); -Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float') +App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float') ->alias('/v1/database/collections/:collectionId/attributes/float', ['databaseId' => 'default']) ->desc('Create float attribute') ->groups(['api', 'database', 'schema']) @@ -1491,8 +1476,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { // Ensure attribute default is within range $min = (is_null($min)) ? -PHP_FLOAT_MAX : \floatval($min); @@ -1525,7 +1509,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float 'min' => $min, 'max' => $max, ], - ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization); + ]), $response, $dbForProject, $queueForDatabase, $queueForEvents); $formatOptions = $attribute->getAttribute('formatOptions', []); @@ -1539,7 +1523,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float ->dynamic($attribute, Response::MODEL_ATTRIBUTE_FLOAT); }); -Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean') +App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean') ->alias('/v1/database/collections/:collectionId/attributes/boolean', ['databaseId' => 'default']) ->desc('Create boolean attribute') ->groups(['api', 'database', 'schema']) @@ -1564,8 +1548,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boole ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { $attribute = createAttribute($databaseId, $collectionId, new Document([ 'key' => $key, @@ -1574,14 +1557,14 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boole 'required' => $required, 'default' => $default, 'array' => $array, - ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization); + ]), $response, $dbForProject, $queueForDatabase, $queueForEvents); $response ->setStatusCode(Response::STATUS_CODE_ACCEPTED) ->dynamic($attribute, Response::MODEL_ATTRIBUTE_BOOLEAN); }); -Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/datetime') +App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/datetime') ->alias('/v1/database/collections/:collectionId/attributes/datetime', ['databaseId' => 'default']) ->desc('Create datetime attribute') ->groups(['api', 'database']) @@ -1606,8 +1589,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/datet ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { $filters[] = 'datetime'; @@ -1619,14 +1601,14 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/datet 'default' => $default, 'array' => $array, 'filters' => $filters, - ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization); + ]), $response, $dbForProject, $queueForDatabase, $queueForEvents); $response ->setStatusCode(Response::STATUS_CODE_ACCEPTED) ->dynamic($attribute, Response::MODEL_ATTRIBUTE_DATETIME); }); -Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relationship') +App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relationship') ->alias('/v1/database/collections/:collectionId/attributes/relationship', ['databaseId' => 'default']) ->desc('Create relationship attribute') ->groups(['api', 'database']) @@ -1653,7 +1635,6 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relat ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->inject('authorization') ->action(function ( string $databaseId, string $collectionId, @@ -1666,13 +1647,12 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relat Response $response, Database $dbForProject, EventDatabase $queueForDatabase, - Event $queueForEvents, - Authorization $authorization + Event $queueForEvents ) { $key ??= $relatedCollectionId; $twoWayKey ??= $collectionId; - $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -1742,8 +1722,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relat $response, $dbForProject, $queueForDatabase, - $queueForEvents, - $authorization + $queueForEvents ); $options = $attribute->getAttribute('options', []); @@ -1757,7 +1736,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relat ->dynamic($attribute, Response::MODEL_ATTRIBUTE_RELATIONSHIP); }); -Http::get('/v1/databases/:databaseId/collections/:collectionId/attributes') +App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') ->alias('/v1/database/collections/:collectionId/attributes', ['databaseId' => 'default']) ->desc('List attributes') ->groups(['api', 'database']) @@ -1774,10 +1753,9 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/attributes') ->param('queries', [], new Attributes(), '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(', ', Attributes::ALLOWED_ATTRIBUTES), true) ->inject('response') ->inject('dbForProject') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject) { /** @var Document $database */ - $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -1811,7 +1789,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/attributes') if ($cursor) { $attributeId = $cursor->getValue(); - $cursorDocument = $authorization->skip(fn () => $dbForProject->find('attributes', [ + $cursorDocument = Authorization::skip(fn () => $dbForProject->find('attributes', [ Query::equal('collectionInternalId', [$collection->getInternalId()]), Query::equal('databaseInternalId', [$database->getInternalId()]), Query::equal('key', [$attributeId]), @@ -1836,7 +1814,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/attributes') ]), Response::MODEL_ATTRIBUTE_LIST); }); -Http::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key') +App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key') ->alias('/v1/database/collections/:collectionId/attributes/:key', ['databaseId' => 'default']) ->desc('Get attribute') ->groups(['api', 'database']) @@ -1863,10 +1841,9 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key') ->param('key', '', new Key(), 'Attribute Key.') ->inject('response') ->inject('dbForProject') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject) { - $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -1912,7 +1889,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key') $response->dynamic($attribute, $model); }); -Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/string/:key') +App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/string/:key') ->desc('Update string attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -1935,11 +1912,9 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/stri ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?int $size, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?int $size, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) { $attribute = updateAttribute( - authorization: $authorization, databaseId: $databaseId, collectionId: $collectionId, key: $key, @@ -1957,7 +1932,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/stri ->dynamic($attribute, Response::MODEL_ATTRIBUTE_STRING); }); -Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/email/:key') +App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/email/:key') ->desc('Update email attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -1979,10 +1954,8 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/emai ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) { $attribute = updateAttribute( - authorization: $authorization, databaseId: $databaseId, collectionId: $collectionId, key: $key, @@ -2000,7 +1973,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/emai ->dynamic($attribute, Response::MODEL_ATTRIBUTE_EMAIL); }); -Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/enum/:key') +App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/enum/:key') ->desc('Update enum attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -2023,10 +1996,8 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/enum ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, ?array $elements, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, ?array $elements, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) { $attribute = updateAttribute( - authorization: $authorization, databaseId: $databaseId, collectionId: $collectionId, key: $key, @@ -2045,7 +2016,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/enum ->dynamic($attribute, Response::MODEL_ATTRIBUTE_ENUM); }); -Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/ip/:key') +App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/ip/:key') ->desc('Update IP address attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -2067,10 +2038,8 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/ip/: ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) { $attribute = updateAttribute( - authorization: $authorization, databaseId: $databaseId, collectionId: $collectionId, key: $key, @@ -2088,7 +2057,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/ip/: ->dynamic($attribute, Response::MODEL_ATTRIBUTE_IP); }); -Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/url/:key') +App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/url/:key') ->desc('Update URL attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -2110,10 +2079,8 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/url/ ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) { $attribute = updateAttribute( - authorization: $authorization, databaseId: $databaseId, collectionId: $collectionId, key: $key, @@ -2131,7 +2098,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/url/ ->dynamic($attribute, Response::MODEL_ATTRIBUTE_URL); }); -Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/integer/:key') +App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/integer/:key') ->desc('Update integer attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -2155,10 +2122,8 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/inte ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) { $attribute = updateAttribute( - authorization: $authorization, databaseId: $databaseId, collectionId: $collectionId, key: $key, @@ -2184,7 +2149,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/inte ->dynamic($attribute, Response::MODEL_ATTRIBUTE_INTEGER); }); -Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/float/:key') +App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/float/:key') ->desc('Update float attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -2208,10 +2173,8 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/floa ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) { $attribute = updateAttribute( - authorization: $authorization, databaseId: $databaseId, collectionId: $collectionId, key: $key, @@ -2237,7 +2200,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/floa ->dynamic($attribute, Response::MODEL_ATTRIBUTE_FLOAT); }); -Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean/:key') +App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean/:key') ->desc('Update boolean attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -2259,10 +2222,8 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/bool ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) { $attribute = updateAttribute( - authorization: $authorization, databaseId: $databaseId, collectionId: $collectionId, key: $key, @@ -2279,7 +2240,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/bool ->dynamic($attribute, Response::MODEL_ATTRIBUTE_BOOLEAN); }); -Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datetime/:key') +App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datetime/:key') ->desc('Update dateTime attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -2301,10 +2262,8 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/date ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) { $attribute = updateAttribute( - authorization: $authorization, databaseId: $databaseId, collectionId: $collectionId, key: $key, @@ -2321,7 +2280,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/date ->dynamic($attribute, Response::MODEL_ATTRIBUTE_DATETIME); }); -Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/relationship') +App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/relationship') ->desc('Update relationship attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -2342,7 +2301,6 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('authorization') ->action(function ( string $databaseId, string $collectionId, @@ -2351,11 +2309,9 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key ?string $newKey, Response $response, Database $dbForProject, - Event $queueForEvents, - Authorization $authorization + Event $queueForEvents ) { $attribute = updateAttribute( - $authorization, $databaseId, $collectionId, $key, @@ -2380,7 +2336,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key ->dynamic($attribute, Response::MODEL_ATTRIBUTE_RELATIONSHIP); }); -Http::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key') +App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key') ->alias('/v1/database/collections/:collectionId/attributes/:key', ['databaseId' => 'default']) ->desc('Delete attribute') ->groups(['api', 'database', 'schema']) @@ -2402,10 +2358,9 @@ Http::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:ke ->inject('queueForDatabase') ->inject('queueForEvents') ->inject('queueForUsage') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Usage $queueForUsage, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Usage $queueForUsage) { - $db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($db->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -2494,7 +2449,7 @@ Http::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:ke $response->noContent(); }); -Http::post('/v1/databases/:databaseId/collections/:collectionId/indexes') +App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') ->alias('/v1/database/collections/:collectionId/indexes', ['databaseId' => 'default']) ->desc('Create index') ->groups(['api', 'database']) @@ -2519,10 +2474,9 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/indexes') ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, string $type, array $attributes, array $orders, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, string $type, array $attributes, array $orders, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { - $db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($db->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -2665,7 +2619,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/indexes') ->dynamic($index, Response::MODEL_INDEX); }); -Http::get('/v1/databases/:databaseId/collections/:collectionId/indexes') +App::get('/v1/databases/:databaseId/collections/:collectionId/indexes') ->alias('/v1/database/collections/:collectionId/indexes', ['databaseId' => 'default']) ->desc('List indexes') ->groups(['api', 'database']) @@ -2682,10 +2636,9 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/indexes') ->param('queries', [], new Indexes(), '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(', ', Indexes::ALLOWED_ATTRIBUTES), true) ->inject('response') ->inject('dbForProject') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject) { /** @var Document $database */ - $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -2715,7 +2668,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/indexes') if ($cursor) { $indexId = $cursor->getValue(); - $cursorDocument = $authorization->skip(fn () => $dbForProject->find('indexes', [ + $cursorDocument = Authorization::skip(fn () => $dbForProject->find('indexes', [ Query::equal('collectionInternalId', [$collection->getInternalId()]), Query::equal('databaseInternalId', [$database->getInternalId()]), Query::equal('key', [$indexId]), @@ -2736,7 +2689,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/indexes') ]), Response::MODEL_INDEX_LIST); }); -Http::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') +App::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') ->alias('/v1/database/collections/:collectionId/indexes/:key', ['databaseId' => 'default']) ->desc('Get index') ->groups(['api', 'database']) @@ -2753,10 +2706,9 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') ->param('key', null, new Key(), 'Index Key.') ->inject('response') ->inject('dbForProject') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject) { - $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -2776,7 +2728,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') }); -Http::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') +App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') ->alias('/v1/database/collections/:collectionId/indexes/:key', ['databaseId' => 'default']) ->desc('Delete index') ->groups(['api', 'database']) @@ -2797,10 +2749,9 @@ Http::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { - $db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($db->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -2841,7 +2792,7 @@ Http::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') $response->noContent(); }); -Http::post('/v1/databases/:databaseId/collections/:collectionId/documents') +App::post('/v1/databases/:databaseId/collections/:collectionId/documents') ->alias('/v1/database/collections/:collectionId/documents', ['databaseId' => 'default']) ->desc('Create document') ->groups(['api', 'database']) @@ -2872,8 +2823,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/documents') ->inject('queueForEvents') ->inject('queueForUsage') ->inject('mode') - ->inject('authorization') - ->action(function (string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, Usage $queueForUsage, string $mode, Authorization $authorization) { + ->action(function (string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, Usage $queueForUsage, string $mode) { $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array @@ -2885,16 +2835,16 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/documents') throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, '$id is not allowed for creating new documents, try update instead'); } - $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); - $isAPIKey = Auth::isAppUser($authorization->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } - $collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); + $collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::COLLECTION_NOT_FOUND); @@ -2932,8 +2882,8 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/documents') $permission->getIdentifier(), $permission->getDimension() ))->toString(); - if (!$authorization->isRole($role)) { - throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $authorization->getRoles()) . ')'); + if (!Authorization::isRole($role)) { + throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', Authorization::getRoles()) . ')'); } } } @@ -2944,16 +2894,17 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/documents') $data['$permissions'] = $permissions; $document = new Document($data); - $checkPermissions = function (Document $collection, Document $document, string $permission) use (&$checkPermissions, $dbForProject, $database, $authorization) { + $checkPermissions = function (Document $collection, Document $document, string $permission) use (&$checkPermissions, $dbForProject, $database) { $documentSecurity = $collection->getAttribute('documentSecurity', false); + $validator = new Authorization($permission); - $valid = $authorization->isValid(new Input($permission, $collection->getPermissionsByType($permission))); + $valid = $validator->isValid($collection->getPermissionsByType($permission)); if (($permission === Database::PERMISSION_UPDATE && !$documentSecurity) || !$valid) { throw new Exception(Exception::USER_UNAUTHORIZED); } if ($permission === Database::PERMISSION_UPDATE) { - $valid = $valid || $authorization->isValid($document->getUpdate()); + $valid = $valid || $validator->isValid($document->getUpdate()); if ($documentSecurity && !$valid) { throw new Exception(Exception::USER_UNAUTHORIZED); } @@ -2980,7 +2931,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/documents') } $relatedCollectionId = $relationship->getAttribute('relatedCollection'); - $relatedCollection = $authorization->skip( + $relatedCollection = Authorization::skip( fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId) ); @@ -2994,7 +2945,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/documents') $relation = new Document($relation); } if ($relation instanceof Document) { - $current = $authorization->skip( + $current = Authorization::skip( fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId(), $relation->getId()) ); @@ -3034,7 +2985,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/documents') } // Add $collectionId and $databaseId for all documents - $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database, $authorization) { + $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) { $document->setAttribute('$databaseId', $database->getId()); $document->setAttribute('$collectionId', $collection->getId()); @@ -3054,7 +3005,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/documents') } $relatedCollectionId = $relationship->getAttribute('relatedCollection'); - $relatedCollection = $authorization->skip( + $relatedCollection = Authorization::skip( fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId) ); @@ -3093,7 +3044,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/documents') ->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection }); -Http::get('/v1/databases/:databaseId/collections/:collectionId/documents') +App::get('/v1/databases/:databaseId/collections/:collectionId/documents') ->alias('/v1/database/collections/:collectionId/documents', ['databaseId' => 'default']) ->desc('List documents') ->groups(['api', 'database']) @@ -3112,17 +3063,16 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents') ->inject('response') ->inject('dbForProject') ->inject('mode') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, string $mode, Authorization $authorization) { - $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); - $isAPIKey = Auth::isAppUser($authorization->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, string $mode) { + $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } - $collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); + $collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::COLLECTION_NOT_FOUND); @@ -3146,7 +3096,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents') if ($cursor) { $documentId = $cursor->getValue(); - $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId)); + $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId)); if ($cursorDocument->isEmpty()) { throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Document '{$documentId}' for the 'cursor' value not found."); @@ -3165,7 +3115,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents') } // Add $collectionId and $databaseId for all documents - $processDocument = (function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database, $authorization): bool { + $processDocument = (function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database): bool { if ($document->isEmpty()) { return false; } @@ -3192,7 +3142,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents') } $relatedCollectionId = $relationship->getAttribute('relatedCollection'); - $relatedCollection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId)); + $relatedCollection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId)); foreach ($relations as $index => $doc) { if ($doc instanceof Document) { @@ -3249,7 +3199,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents') ]), Response::MODEL_DOCUMENT_LIST); }); -Http::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId') +App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId') ->alias('/v1/database/collections/:collectionId/documents/:documentId', ['databaseId' => 'default']) ->desc('Get document') ->groups(['api', 'database']) @@ -3270,18 +3220,17 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents/:docume ->inject('response') ->inject('dbForProject') ->inject('mode') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, string $mode, Authorization $authorization) { - $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + ->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, string $mode) { + $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); - $isAPIKey = Auth::isAppUser($authorization->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } - $collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); + $collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::COLLECTION_NOT_FOUND); @@ -3301,7 +3250,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents/:docume } // Add $collectionId and $databaseId for all documents - $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database, $authorization) { + $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) { if ($document->isEmpty()) { return; } @@ -3325,7 +3274,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents/:docume } $relatedCollectionId = $relationship->getAttribute('relatedCollection'); - $relatedCollection = $authorization->skip( + $relatedCollection = Authorization::skip( fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId) ); @@ -3342,7 +3291,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents/:docume $response->dynamic($document, Response::MODEL_DOCUMENT); }); -Http::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId/logs') +App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId/logs') ->alias('/v1/database/collections/:collectionId/documents/:documentId/logs', ['databaseId' => 'default']) ->desc('List document logs') ->groups(['api', 'database']) @@ -3362,10 +3311,9 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents/:docume ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { - $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -3447,7 +3395,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents/:docume ]), Response::MODEL_LOG_LIST); }); -Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId') +App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId') ->alias('/v1/database/collections/:collectionId/documents/:documentId', ['databaseId' => 'default']) ->desc('Update document') ->groups(['api', 'database']) @@ -3477,8 +3425,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu ->inject('dbForProject') ->inject('queueForEvents') ->inject('mode') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Authorization $authorization) { + ->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, string $mode) { $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array @@ -3486,16 +3433,16 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu throw new Exception(Exception::DOCUMENT_MISSING_PAYLOAD); } - $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); - $isAPIKey = Auth::isAppUser($authorization->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } - $collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); + $collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::COLLECTION_NOT_FOUND); @@ -3503,7 +3450,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu // Read permission should not be required for update /** @var Document $document */ - $document = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId)); + $document = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId)); if ($document->isEmpty()) { throw new Exception(Exception::DOCUMENT_NOT_FOUND); @@ -3517,7 +3464,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu ]); // Users can only manage their own roles, API keys and Admin users can manage any - $roles = $authorization->getRoles(); + $roles = Authorization::getRoles(); if (!$isAPIKey && !$isPrivilegedUser && !\is_null($permissions)) { foreach (Database::PERMISSIONS as $type) { foreach ($permissions as $permission) { @@ -3530,7 +3477,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu $permission->getIdentifier(), $permission->getDimension() ))->toString(); - if (!$authorization->isRole($role)) { + if (!Authorization::isRole($role)) { throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')'); } } @@ -3545,7 +3492,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu $data['$permissions'] = $permissions; $newDocument = new Document($data); - $setCollection = (function (Document $collection, Document $document) use (&$setCollection, $dbForProject, $database, $authorization) { + $setCollection = (function (Document $collection, Document $document) use (&$setCollection, $dbForProject, $database) { $relationships = \array_filter( $collection->getAttribute('attributes', []), fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP @@ -3567,7 +3514,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu } $relatedCollectionId = $relationship->getAttribute('relatedCollection'); - $relatedCollection = $authorization->skip( + $relatedCollection = Authorization::skip( fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId) ); @@ -3582,7 +3529,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu $relation = new Document($relation); } if ($relation instanceof Document) { - $oldDocument = $authorization->skip(fn () => $dbForProject->getDocument( + $oldDocument = Authorization::skip(fn () => $dbForProject->getDocument( 'database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId(), $relation->getId() )); @@ -3631,7 +3578,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu } // Add $collectionId and $databaseId for all documents - $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database, $authorization) { + $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) { $document->setAttribute('$databaseId', $database->getId()); $document->setAttribute('$collectionId', $collection->getId()); @@ -3651,7 +3598,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu } $relatedCollectionId = $relationship->getAttribute('relatedCollection'); - $relatedCollection = $authorization->skip( + $relatedCollection = Authorization::skip( fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId) ); @@ -3684,7 +3631,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu ->setPayload($response->getPayload(), sensitive: $relationships); }); -Http::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId') +App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId') ->alias('/v1/database/collections/:collectionId/documents/:documentId', ['databaseId' => 'default']) ->desc('Delete document') ->groups(['api', 'database']) @@ -3713,25 +3660,24 @@ Http::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:doc ->inject('queueForEvents') ->inject('queueForUsage') ->inject('mode') - ->inject('authorization') - ->action(function (string $databaseId, string $collectionId, string $documentId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Usage $queueForUsage, string $mode, Authorization $authorization) { - $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + ->action(function (string $databaseId, string $collectionId, string $documentId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Usage $queueForUsage, string $mode) { + $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); - $isAPIKey = Auth::isAppUser($authorization->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } - $collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); + $collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::COLLECTION_NOT_FOUND); } // Read permission should not be required for delete - $document = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId)); + $document = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId)); if ($document->isEmpty()) { throw new Exception(Exception::DOCUMENT_NOT_FOUND); @@ -3745,7 +3691,7 @@ Http::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:doc }); // Add $collectionId and $databaseId for all documents - $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database, $authorization) { + $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) { $document->setAttribute('$databaseId', $database->getId()); $document->setAttribute('$collectionId', $collection->getId()); @@ -3765,7 +3711,7 @@ Http::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:doc } $relatedCollectionId = $relationship->getAttribute('relatedCollection'); - $relatedCollection = $authorization->skip( + $relatedCollection = Authorization::skip( fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId) ); @@ -3805,7 +3751,7 @@ Http::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:doc $response->noContent(); }); -Http::get('/v1/databases/usage') +App::get('/v1/databases/usage') ->desc('Get databases usage stats') ->groups(['api', 'database', 'usage']) ->label('scope', 'collections.read') @@ -3818,8 +3764,7 @@ Http::get('/v1/databases/usage') ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), '`Date range.', true) ->inject('response') ->inject('dbForProject') - ->inject('authorization') - ->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) { + ->action(function (string $range, Response $response, Database $dbForProject) { $periods = Config::getParam('usage', []); $stats = $usage = []; @@ -3831,7 +3776,7 @@ Http::get('/v1/databases/usage') METRIC_DATABASES_STORAGE ]; - $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) { + Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) { foreach ($metrics as $metric) { $result = $dbForProject->findOne('stats', [ Query::equal('metric', [$metric]), @@ -3887,7 +3832,7 @@ Http::get('/v1/databases/usage') ]), Response::MODEL_USAGE_DATABASES); }); -Http::get('/v1/databases/:databaseId/usage') +App::get('/v1/databases/:databaseId/usage') ->desc('Get database usage stats') ->groups(['api', 'database', 'usage']) ->label('scope', 'collections.read') @@ -3901,8 +3846,7 @@ Http::get('/v1/databases/:databaseId/usage') ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), '`Date range.', true) ->inject('response') ->inject('dbForProject') - ->inject('authorization') - ->action(function (string $databaseId, string $range, Response $response, Database $dbForProject, Authorization $authorization) { + ->action(function (string $databaseId, string $range, Response $response, Database $dbForProject) { $database = $dbForProject->getDocument('databases', $databaseId); @@ -3919,7 +3863,7 @@ Http::get('/v1/databases/:databaseId/usage') str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_STORAGE) ]; - $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) { + Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) { foreach ($metrics as $metric) { $result = $dbForProject->findOne('stats', [ Query::equal('metric', [$metric]), @@ -3974,7 +3918,7 @@ Http::get('/v1/databases/:databaseId/usage') ]), Response::MODEL_USAGE_DATABASE); }); -Http::get('/v1/databases/:databaseId/collections/:collectionId/usage') +App::get('/v1/databases/:databaseId/collections/:collectionId/usage') ->alias('/v1/database/:collectionId/usage', ['databaseId' => 'default']) ->desc('Get collection usage stats') ->groups(['api', 'database', 'usage']) @@ -3990,8 +3934,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/usage') ->param('collectionId', '', new UID(), 'Collection ID.') ->inject('response') ->inject('dbForProject') - ->inject('authorization') - ->action(function (string $databaseId, string $range, string $collectionId, Response $response, Database $dbForProject, Authorization $authorization) { + ->action(function (string $databaseId, string $range, string $collectionId, Response $response, Database $dbForProject) { $database = $dbForProject->getDocument('databases', $databaseId); $collectionDocument = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId); @@ -4008,7 +3951,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/usage') str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collectionDocument->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS), ]; - $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) { + Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) { foreach ($metrics as $metric) { $result = $dbForProject->findOne('stats', [ Query::equal('metric', [$metric]), diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 77311c8a90..9c3e6782b4 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -2,7 +2,6 @@ use Ahc\Jwt\JWT; use Appwrite\Auth\Auth; -use Appwrite\Auth\Authentication; use Appwrite\Event\Build; use Appwrite\Event\Delete; use Appwrite\Event\Event; @@ -21,11 +20,11 @@ use Appwrite\Utopia\Database\Validator\CustomId; use Appwrite\Utopia\Database\Validator\Queries\Deployments; use Appwrite\Utopia\Database\Validator\Queries\Executions; use Appwrite\Utopia\Database\Validator\Queries\Functions; -use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use Appwrite\Utopia\Response\Model\Rule; use Executor\Executor; use MaxMind\Db\Reader; +use Utopia\App; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Database\Database; @@ -38,25 +37,24 @@ use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; -use Utopia\Database\Validator\Authorization\Input; use Utopia\Database\Validator\Datetime as DatetimeValidator; use Utopia\Database\Validator\Roles; use Utopia\Database\Validator\UID; -use Utopia\Http\Http; -use Utopia\Http\Validator\AnyOf; -use Utopia\Http\Validator\ArrayList; -use Utopia\Http\Validator\Assoc; -use Utopia\Http\Validator\Boolean; -use Utopia\Http\Validator\Nullable; -use Utopia\Http\Validator\Range; -use Utopia\Http\Validator\Text; -use Utopia\Http\Validator\WhiteList; 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\AnyOf; +use Utopia\Validator\ArrayList; +use Utopia\Validator\Assoc; +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; use Utopia\VCS\Exception\RepositoryNotFound; @@ -135,7 +133,7 @@ $redeployVcs = function (Request $request, Document $function, Document $project ->setTemplate($template); }; -Http::post('/v1/functions') +App::post('/v1/functions') ->groups(['api', 'functions']) ->desc('Create function') ->label('scope', 'functions.write') @@ -173,8 +171,8 @@ Http::post('/v1/functions') ->param('specification', APP_FUNCTION_SPECIFICATION_DEFAULT, fn (array $plan) => new RuntimeSpecification( $plan, Config::getParam('runtime-specifications', []), - System::getEnv('_APP_FUNCTIONS_CPUS', APP_FUNCTION_CPUS_DEFAULT), - System::getEnv('_APP_FUNCTIONS_MEMORY', APP_FUNCTION_MEMORY_DEFAULT) + App::getEnv('_APP_FUNCTIONS_CPUS', APP_FUNCTION_CPUS_DEFAULT), + App::getEnv('_APP_FUNCTIONS_MEMORY', APP_FUNCTION_MEMORY_DEFAULT) ), 'Runtime specification for the function and builds.', true, ['plan']) ->inject('request') ->inject('response') @@ -185,8 +183,7 @@ Http::post('/v1/functions') ->inject('queueForBuilds') ->inject('dbForConsole') ->inject('gitHub') - ->inject('authorization') - ->action(function (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, Authorization $authorization) use ($redeployVcs) { + ->action(function (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) use ($redeployVcs) { $functionId = ($functionId == 'unique()') ? ID::unique() : $functionId; $allowList = \array_filter(\explode(',', System::getEnv('_APP_FUNCTIONS_RUNTIMES', ''))); @@ -250,7 +247,7 @@ Http::post('/v1/functions') 'specification' => $specification ])); - $schedule = $authorization->skip( + $schedule = Authorization::skip( fn () => $dbForConsole->createDocument('schedules', new Document([ 'region' => System::getEnv('_APP_REGION', 'default'), // Todo replace with projects region 'resourceType' => 'function', @@ -332,7 +329,7 @@ Http::post('/v1/functions') $routeSubdomain = ID::unique(); $domain = "{$routeSubdomain}.{$functionsDomain}"; - $rule = $authorization->skip( + $rule = Authorization::skip( fn () => $dbForConsole->createDocument('rules', new Document([ '$id' => $ruleId, 'projectId' => $project->getId(), @@ -399,7 +396,7 @@ Http::post('/v1/functions') ->dynamic($function, Response::MODEL_FUNCTION); }); -Http::get('/v1/functions') +App::get('/v1/functions') ->groups(['api', 'functions']) ->desc('List functions') ->label('scope', 'functions.read') @@ -453,7 +450,7 @@ Http::get('/v1/functions') ]), Response::MODEL_FUNCTION_LIST); }); -Http::get('/v1/functions/runtimes') +App::get('/v1/functions/runtimes') ->groups(['api', 'functions']) ->desc('List runtimes') ->label('scope', 'functions.read') @@ -486,7 +483,7 @@ Http::get('/v1/functions/runtimes') ]), Response::MODEL_RUNTIME_LIST); }); -Http::get('/v1/functions/specifications') +App::get('/v1/functions/specifications') ->groups(['api', 'functions']) ->desc('List available function runtime specifications') ->label('scope', 'functions.read') @@ -522,7 +519,7 @@ Http::get('/v1/functions/specifications') ]), Response::MODEL_SPECIFICATION_LIST); }); -Http::get('/v1/functions/:functionId') +App::get('/v1/functions/:functionId') ->groups(['api', 'functions']) ->desc('Get function') ->label('scope', 'functions.read') @@ -546,7 +543,7 @@ Http::get('/v1/functions/:functionId') $response->dynamic($function, Response::MODEL_FUNCTION); }); -Http::get('/v1/functions/:functionId/usage') +App::get('/v1/functions/:functionId/usage') ->desc('Get function usage') ->groups(['api', 'functions', 'usage']) ->label('scope', 'functions.read') @@ -560,8 +557,7 @@ Http::get('/v1/functions/:functionId/usage') ->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true) ->inject('response') ->inject('dbForProject') - ->inject('authorization') - ->action(function (string $functionId, string $range, Response $response, Database $dbForProject, Authorization $authorization) { + ->action(function (string $functionId, string $range, Response $response, Database $dbForProject) { $function = $dbForProject->getDocument('functions', $functionId); @@ -584,7 +580,7 @@ Http::get('/v1/functions/:functionId/usage') str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS) ]; - $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) { + Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) { foreach ($metrics as $metric) { $result = $dbForProject->findOne('stats', [ Query::equal('metric', [$metric]), @@ -651,7 +647,7 @@ Http::get('/v1/functions/:functionId/usage') ]), Response::MODEL_USAGE_FUNCTION); }); -Http::get('/v1/functions/usage') +App::get('/v1/functions/usage') ->desc('Get functions usage') ->groups(['api', 'functions']) ->label('scope', 'functions.read') @@ -664,8 +660,7 @@ Http::get('/v1/functions/usage') ->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true) ->inject('response') ->inject('dbForProject') - ->inject('authorization') - ->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) { + ->action(function (string $range, Response $response, Database $dbForProject) { $periods = Config::getParam('usage', []); $stats = $usage = []; @@ -683,7 +678,7 @@ Http::get('/v1/functions/usage') METRIC_EXECUTIONS_MB_SECONDS, ]; - $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) { + Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) { foreach ($metrics as $metric) { $result = $dbForProject->findOne('stats', [ Query::equal('metric', [$metric]), @@ -751,7 +746,7 @@ Http::get('/v1/functions/usage') ]), Response::MODEL_USAGE_FUNCTIONS); }); -Http::put('/v1/functions/:functionId') +App::put('/v1/functions/:functionId') ->groups(['api', 'functions']) ->desc('Update function') ->label('scope', 'functions.write') @@ -785,8 +780,8 @@ Http::put('/v1/functions/:functionId') ->param('specification', APP_FUNCTION_SPECIFICATION_DEFAULT, fn (array $plan) => new RuntimeSpecification( $plan, Config::getParam('runtime-specifications', []), - System::getEnv('_APP_FUNCTIONS_CPUS', APP_FUNCTION_CPUS_DEFAULT), - System::getEnv('_APP_FUNCTIONS_MEMORY', APP_FUNCTION_MEMORY_DEFAULT) + App::getEnv('_APP_FUNCTIONS_CPUS', APP_FUNCTION_CPUS_DEFAULT), + App::getEnv('_APP_FUNCTIONS_MEMORY', APP_FUNCTION_MEMORY_DEFAULT) ), 'Runtime specification for the function and builds.', true, ['plan']) ->inject('request') ->inject('response') @@ -796,8 +791,7 @@ Http::put('/v1/functions/:functionId') ->inject('queueForBuilds') ->inject('dbForConsole') ->inject('gitHub') - ->inject('authorization') - ->action(function (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, Authorization $authorization) use ($redeployVcs) { + ->action(function (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) use ($redeployVcs) { // TODO: If only branch changes, re-deploy $function = $dbForProject->getDocument('functions', $functionId); @@ -900,7 +894,7 @@ Http::put('/v1/functions/:functionId') // Enforce Cold Start if spec limits change. if ($function->getAttribute('specification') !== $specification && !empty($function->getAttribute('deployment'))) { - $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST')); + $executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST')); try { $executor->deleteRuntime($project->getId(), $function->getAttribute('deployment')); } catch (\Throwable $th) { @@ -947,14 +941,14 @@ Http::put('/v1/functions/:functionId') ->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)); + Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); $queueForEvents->setParam('functionId', $function->getId()); $response->dynamic($function, Response::MODEL_FUNCTION); }); -Http::get('/v1/functions/:functionId/deployments/:deploymentId/download') +App::get('/v1/functions/:functionId/deployments/:deploymentId/download') ->groups(['api', 'functions']) ->desc('Download deployment') ->label('scope', 'functions.read') @@ -1039,7 +1033,7 @@ Http::get('/v1/functions/:functionId/deployments/:deploymentId/download') } }); -Http::patch('/v1/functions/:functionId/deployments/:deploymentId') +App::patch('/v1/functions/:functionId/deployments/:deploymentId') ->groups(['api', 'functions']) ->desc('Update deployment') ->label('scope', 'functions.write') @@ -1059,8 +1053,7 @@ Http::patch('/v1/functions/:functionId/deployments/:deploymentId') ->inject('dbForProject') ->inject('queueForEvents') ->inject('dbForConsole') - ->inject('authorization') - ->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Event $queueForEvents, Database $dbForConsole, Authorization $authorization) { + ->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Event $queueForEvents, Database $dbForConsole) { $function = $dbForProject->getDocument('functions', $functionId); $deployment = $dbForProject->getDocument('deployments', $deploymentId); @@ -1093,7 +1086,7 @@ Http::patch('/v1/functions/:functionId/deployments/:deploymentId') ->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)); + Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); $queueForEvents ->setParam('functionId', $function->getId()) @@ -1102,7 +1095,7 @@ Http::patch('/v1/functions/:functionId/deployments/:deploymentId') $response->dynamic($function, Response::MODEL_FUNCTION); }); -Http::delete('/v1/functions/:functionId') +App::delete('/v1/functions/:functionId') ->groups(['api', 'functions']) ->desc('Delete function') ->label('scope', 'functions.write') @@ -1121,8 +1114,7 @@ Http::delete('/v1/functions/:functionId') ->inject('queueForDeletes') ->inject('queueForEvents') ->inject('dbForConsole') - ->inject('authorization') - ->action(function (string $functionId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Database $dbForConsole, Authorization $authorization) { + ->action(function (string $functionId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Database $dbForConsole) { $function = $dbForProject->getDocument('functions', $functionId); @@ -1139,7 +1131,7 @@ Http::delete('/v1/functions/:functionId') $schedule ->setAttribute('resourceUpdatedAt', DateTime::now()) ->setAttribute('active', false); - $authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); + Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); $queueForDeletes ->setType(DELETE_TYPE_DOCUMENT) @@ -1150,7 +1142,7 @@ Http::delete('/v1/functions/:functionId') $response->noContent(); }); -Http::post('/v1/functions/:functionId/deployments') +App::post('/v1/functions/:functionId/deployments') ->groups(['api', 'functions']) ->desc('Create deployment') ->label('scope', 'functions.write') @@ -1369,7 +1361,7 @@ Http::post('/v1/functions/:functionId/deployments') ->dynamic($deployment, Response::MODEL_DEPLOYMENT); }); -Http::get('/v1/functions/:functionId/deployments') +App::get('/v1/functions/:functionId/deployments') ->groups(['api', 'functions']) ->desc('List deployments') ->label('scope', 'functions.read') @@ -1446,7 +1438,7 @@ Http::get('/v1/functions/:functionId/deployments') ]), Response::MODEL_DEPLOYMENT_LIST); }); -Http::get('/v1/functions/:functionId/deployments/:deploymentId') +App::get('/v1/functions/:functionId/deployments/:deploymentId') ->groups(['api', 'functions']) ->desc('Get deployment') ->label('scope', 'functions.read') @@ -1489,7 +1481,7 @@ Http::get('/v1/functions/:functionId/deployments/:deploymentId') $response->dynamic($deployment, Response::MODEL_DEPLOYMENT); }); -Http::delete('/v1/functions/:functionId/deployments/:deploymentId') +App::delete('/v1/functions/:functionId/deployments/:deploymentId') ->groups(['api', 'functions']) ->desc('Delete deployment') ->label('scope', 'functions.write') @@ -1553,7 +1545,7 @@ Http::delete('/v1/functions/:functionId/deployments/:deploymentId') $response->noContent(); }); -Http::post('/v1/functions/:functionId/deployments/:deploymentId/build') +App::post('/v1/functions/:functionId/deployments/:deploymentId/build') ->alias('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId') ->groups(['api', 'functions']) ->desc('Rebuild deployment') @@ -1576,8 +1568,7 @@ Http::post('/v1/functions/:functionId/deployments/:deploymentId/build') ->inject('queueForEvents') ->inject('queueForBuilds') ->inject('deviceForFunctions') - ->inject('authorization') - ->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Device $deviceForFunctions, Authorization $authorization) { + ->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Device $deviceForFunctions) { $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { @@ -1623,7 +1614,7 @@ Http::post('/v1/functions/:functionId/deployments/:deploymentId/build') $response->noContent(); }); -Http::patch('/v1/functions/:functionId/deployments/:deploymentId/build') +App::patch('/v1/functions/:functionId/deployments/:deploymentId/build') ->groups(['api', 'functions']) ->desc('Cancel deployment') ->label('scope', 'functions.write') @@ -1641,8 +1632,7 @@ Http::patch('/v1/functions/:functionId/deployments/:deploymentId/build') ->inject('dbForProject') ->inject('project') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Document $project, Event $queueForEvents) { $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { @@ -1655,7 +1645,7 @@ Http::patch('/v1/functions/:functionId/deployments/:deploymentId/build') throw new Exception(Exception::DEPLOYMENT_NOT_FOUND); } - $build = $authorization->skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''))); + $build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''))); if ($build->isEmpty()) { $buildId = ID::unique(); @@ -1697,7 +1687,7 @@ Http::patch('/v1/functions/:functionId/deployments/:deploymentId/build') $dbForProject->purgeCachedDocument('deployments', $deployment->getId()); try { - $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST')); + $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 @@ -1713,7 +1703,7 @@ Http::patch('/v1/functions/:functionId/deployments/:deploymentId/build') $response->dynamic($build, Response::MODEL_BUILD); }); -Http::post('/v1/functions/:functionId/executions') +App::post('/v1/functions/:functionId/executions') ->groups(['api', 'functions']) ->desc('Create execution') ->label('scope', 'execution.write') @@ -1743,9 +1733,7 @@ Http::post('/v1/functions/:functionId/executions') ->inject('queueForUsage') ->inject('queueForFunctions') ->inject('geodb') - ->inject('authorization') - ->inject('authentication') - ->action(function (string $functionId, string $body, mixed $async, string $path, string $method, mixed $headers, ?string $scheduledAt, Response $response, Request $request, Document $project, Database $dbForProject, Database $dbForConsole, Document $user, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb, Authorization $authorization, Authentication $authentication) { + ->action(function (string $functionId, string $body, mixed $async, string $path, string $method, mixed $headers, ?string $scheduledAt, Response $response, Request $request, Document $project, Database $dbForProject, Database $dbForConsole, Document $user, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb) { $async = \strval($async) === 'true' || \strval($async) === '1'; if (!$async && !is_null($scheduledAt)) { @@ -1775,10 +1763,10 @@ Http::post('/v1/functions/:functionId/executions') throw new Exception($validator->getDescription(), 400); } - $function = $authorization->skip(fn () => $dbForProject->getDocument('functions', $functionId)); + $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); - $isAPIKey = Auth::isAppUser($authorization->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); if ($function->isEmpty() || (!$function->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::FUNCTION_NOT_FOUND); @@ -1794,7 +1782,7 @@ Http::post('/v1/functions/:functionId/executions') throw new Exception(Exception::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $function->getAttribute('runtime', '') . '" is not supported'); } - $deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', ''))); + $deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', ''))); if ($deployment->getAttribute('resourceId') !== $function->getId()) { throw new Exception(Exception::DEPLOYMENT_NOT_FOUND, 'Deployment not found. Create a deployment before trying to execute a function'); @@ -1805,7 +1793,7 @@ Http::post('/v1/functions/:functionId/executions') } /** Check if build has completed */ - $build = $authorization->skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''))); + $build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''))); if ($build->isEmpty()) { throw new Exception(Exception::BUILD_NOT_FOUND); } @@ -1814,8 +1802,10 @@ Http::post('/v1/functions/:functionId/executions') throw new Exception(Exception::BUILD_NOT_READY); } - if (!$authorization->isValid(new Input('execute', $function->getAttribute('execute')))) { // Check if user has write access to execute function - throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription()); + $validator = new Authorization('execute'); + + if (!$validator->isValid($function->getAttribute('execute'))) { // Check if user has write access to execute function + throw new Exception(Exception::USER_UNAUTHORIZED, $validator->getDescription()); } $jwt = ''; // initialize @@ -1825,7 +1815,7 @@ Http::post('/v1/functions/:functionId/executions') foreach ($sessions as $session) { /** @var Utopia\Database\Document $session */ - if ($session->getAttribute('secret') == Auth::hash($authentication->getSecret())) { // If current session delete the cookies too + if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too $current = $session; } } @@ -1909,9 +1899,8 @@ Http::post('/v1/functions/:functionId/executions') ->setContext('function', $function); if ($async) { - if (is_null($scheduledAt)) { - $execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution)); + $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution)); $queueForFunctions ->setType('http') ->setExecution($execution) @@ -1952,7 +1941,7 @@ Http::post('/v1/functions/:functionId/executions') ->setAttribute('scheduleInternalId', $schedule->getInternalId()) ->setAttribute('scheduledAt', $scheduledAt); - $execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution)); + $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution)); } return $response @@ -2038,7 +2027,8 @@ Http::post('/v1/functions/:functionId/executions') runtimeEntrypoint: $command, cpus: $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT, memory: $spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT, - logging: $function->getAttribute('logging', true) + logging: $function->getAttribute('logging', true), + requestTimeout: 30 ); $headersFiltered = []; @@ -2079,10 +2069,10 @@ Http::post('/v1/functions/:functionId/executions') ->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))) ; - $execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution)); + $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution)); } - $roles = $authorization->getRoles(); + $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -2115,7 +2105,7 @@ Http::post('/v1/functions/:functionId/executions') ->dynamic($execution, Response::MODEL_EXECUTION); }); -Http::get('/v1/functions/:functionId/executions') +App::get('/v1/functions/:functionId/executions') ->groups(['api', 'functions']) ->desc('List executions') ->label('scope', 'execution.read') @@ -2132,12 +2122,11 @@ Http::get('/v1/functions/:functionId/executions') ->inject('response') ->inject('dbForProject') ->inject('mode') - ->inject('authorization') - ->action(function (string $functionId, array $queries, string $search, Response $response, Database $dbForProject, string $mode, Authorization $authorization) { - $function = $authorization->skip(fn () => $dbForProject->getDocument('functions', $functionId)); + ->action(function (string $functionId, array $queries, string $search, Response $response, Database $dbForProject, string $mode) { + $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); - $isAPIKey = Auth::isAppUser($authorization->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); if ($function->isEmpty() || (!$function->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::FUNCTION_NOT_FOUND); @@ -2180,7 +2169,7 @@ Http::get('/v1/functions/:functionId/executions') $results = $dbForProject->find('executions', $queries); $total = $dbForProject->count('executions', $filterQueries, APP_LIMIT_COUNT); - $roles = $authorization->getRoles(); + $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); if (!$isPrivilegedUser && !$isAppUser) { @@ -2197,7 +2186,7 @@ Http::get('/v1/functions/:functionId/executions') ]), Response::MODEL_EXECUTION_LIST); }); -Http::get('/v1/functions/:functionId/executions/:executionId') +App::get('/v1/functions/:functionId/executions/:executionId') ->groups(['api', 'functions']) ->desc('Get execution') ->label('scope', 'execution.read') @@ -2213,12 +2202,11 @@ Http::get('/v1/functions/:functionId/executions/:executionId') ->inject('response') ->inject('dbForProject') ->inject('mode') - ->inject('authorization') - ->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, string $mode, Authorization $authorization) { - $function = $authorization->skip(fn () => $dbForProject->getDocument('functions', $functionId)); + ->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, string $mode) { + $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); - $isAPIKey = Auth::isAppUser($authorization->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); if ($function->isEmpty() || (!$function->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::FUNCTION_NOT_FOUND); @@ -2234,7 +2222,7 @@ Http::get('/v1/functions/:functionId/executions/:executionId') throw new Exception(Exception::EXECUTION_NOT_FOUND); } - $roles = $authorization->getRoles(); + $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); if (!$isPrivilegedUser && !$isAppUser) { @@ -2245,7 +2233,7 @@ Http::get('/v1/functions/:functionId/executions/:executionId') $response->dynamic($execution, Response::MODEL_EXECUTION); }); -Http::delete('/v1/functions/:functionId/executions/:executionId') +App::delete('/v1/functions/:functionId/executions/:executionId') ->groups(['api', 'functions']) ->desc('Delete execution') ->label('scope', 'execution.write') @@ -2264,8 +2252,7 @@ Http::delete('/v1/functions/:functionId/executions/:executionId') ->inject('dbForProject') ->inject('dbForConsole') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, Database $dbForConsole, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, Database $dbForConsole, Event $queueForEvents) { $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { @@ -2297,12 +2284,12 @@ Http::delete('/v1/functions/:functionId/executions/:executionId') Query::equal('active', [true]), ]); - if (!$schedule->isEmpty()) { + if ($schedule && !$schedule->isEmpty()) { $schedule ->setAttribute('resourceUpdatedAt', DateTime::now()) ->setAttribute('active', false); - $authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); + Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); } } @@ -2316,7 +2303,7 @@ Http::delete('/v1/functions/:functionId/executions/:executionId') // Variables -Http::post('/v1/functions/:functionId/variables') +App::post('/v1/functions/:functionId/variables') ->desc('Create variable') ->groups(['api', 'functions']) ->label('scope', 'functions.write') @@ -2335,8 +2322,7 @@ Http::post('/v1/functions/:functionId/variables') ->inject('response') ->inject('dbForProject') ->inject('dbForConsole') - ->inject('authorization') - ->action(function (string $functionId, string $key, string $value, Response $response, Database $dbForProject, Database $dbForConsole, Authorization $authorization) { + ->action(function (string $functionId, string $key, string $value, Response $response, Database $dbForProject, Database $dbForConsole) { $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { @@ -2374,14 +2360,14 @@ Http::post('/v1/functions/:functionId/variables') ->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)); + Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($variable, Response::MODEL_VARIABLE); }); -Http::get('/v1/functions/:functionId/variables') +App::get('/v1/functions/:functionId/variables') ->desc('List variables') ->groups(['api', 'functions']) ->label('scope', 'functions.read') @@ -2408,7 +2394,7 @@ Http::get('/v1/functions/:functionId/variables') ]), Response::MODEL_VARIABLE_LIST); }); -Http::get('/v1/functions/:functionId/variables/:variableId') +App::get('/v1/functions/:functionId/variables/:variableId') ->desc('Get variable') ->groups(['api', 'functions']) ->label('scope', 'functions.read') @@ -2447,7 +2433,7 @@ Http::get('/v1/functions/:functionId/variables/:variableId') $response->dynamic($variable, Response::MODEL_VARIABLE); }); -Http::put('/v1/functions/:functionId/variables/:variableId') +App::put('/v1/functions/:functionId/variables/:variableId') ->desc('Update variable') ->groups(['api', 'functions']) ->label('scope', 'functions.write') @@ -2467,8 +2453,7 @@ Http::put('/v1/functions/:functionId/variables/:variableId') ->inject('response') ->inject('dbForProject') ->inject('dbForConsole') - ->inject('authorization') - ->action(function (string $functionId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject, Database $dbForConsole, Authorization $authorization) { + ->action(function (string $functionId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject, Database $dbForConsole) { $function = $dbForProject->getDocument('functions', $functionId); @@ -2504,12 +2489,12 @@ Http::put('/v1/functions/:functionId/variables/:variableId') ->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)); + Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); $response->dynamic($variable, Response::MODEL_VARIABLE); }); -Http::delete('/v1/functions/:functionId/variables/:variableId') +App::delete('/v1/functions/:functionId/variables/:variableId') ->desc('Delete variable') ->groups(['api', 'functions']) ->label('scope', 'functions.write') @@ -2526,8 +2511,7 @@ Http::delete('/v1/functions/:functionId/variables/:variableId') ->inject('response') ->inject('dbForProject') ->inject('dbForConsole') - ->inject('authorization') - ->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject, Database $dbForConsole, Authorization $authorization) { + ->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject, Database $dbForConsole) { $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { @@ -2553,12 +2537,12 @@ Http::delete('/v1/functions/:functionId/variables/:variableId') ->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)); + Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); $response->noContent(); }); -Http::get('/v1/functions/templates') +App::get('/v1/functions/templates') ->groups(['api']) ->desc('List function templates') ->label('scope', 'public') @@ -2596,7 +2580,7 @@ Http::get('/v1/functions/templates') ]), Response::MODEL_TEMPLATE_FUNCTION_LIST); }); -Http::get('/v1/functions/templates/:templateId') +App::get('/v1/functions/templates/:templateId') ->desc('Get function template') ->label('scope', 'public') ->label('sdk.namespace', 'functions') @@ -2611,10 +2595,9 @@ Http::get('/v1/functions/templates/:templateId') ->action(function (string $templateId, Response $response) { $templates = Config::getParam('function-templates', []); - $array = \array_filter($templates, function ($template) use ($templateId) { + $template = array_shift(\array_filter($templates, function ($template) use ($templateId) { return $template['id'] === $templateId; - }); - $template = array_shift($array); + })); if (empty($template)) { throw new Exception(Exception::FUNCTION_TEMPLATE_NOT_FOUND); diff --git a/app/controllers/api/graphql.php b/app/controllers/api/graphql.php index 0e7ddc783d..f79f433b5c 100644 --- a/app/controllers/api/graphql.php +++ b/app/controllers/api/graphql.php @@ -14,28 +14,27 @@ use GraphQL\Validator\Rules\DisableIntrospection; use GraphQL\Validator\Rules\QueryComplexity; use GraphQL\Validator\Rules\QueryDepth; use Swoole\Coroutine\WaitGroup; +use Utopia\App; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -use Utopia\Http\Http; -use Utopia\Http\Validator\JSON; -use Utopia\Http\Validator\Text; use Utopia\System\System; +use Utopia\Validator\JSON; +use Utopia\Validator\Text; -Http::init() +App::init() ->groups(['graphql']) ->inject('project') - ->inject('authorization') - ->action(function (Document $project, Authorization $authorization) { + ->action(function (Document $project) { if ( array_key_exists('graphql', $project->getAttribute('apis', [])) && !$project->getAttribute('apis', [])['graphql'] - && !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles())) + && !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles())) ) { throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED); } }); -Http::get('/v1/graphql') +App::get('/v1/graphql') ->desc('GraphQL endpoint') ->groups(['graphql']) ->label('scope', 'graphql') @@ -75,7 +74,7 @@ Http::get('/v1/graphql') ->json($output); }); -Http::post('/v1/graphql/mutation') +App::post('/v1/graphql/mutation') ->desc('GraphQL endpoint') ->groups(['graphql']) ->label('scope', 'graphql') @@ -120,7 +119,7 @@ Http::post('/v1/graphql/mutation') ->json($output); }); -Http::post('/v1/graphql') +App::post('/v1/graphql') ->desc('GraphQL endpoint') ->groups(['graphql']) ->label('scope', 'graphql') @@ -157,6 +156,7 @@ Http::post('/v1/graphql') if (\str_starts_with($type, 'multipart/form-data')) { $query = parseMultipart($query, $request); } + $output = execute($schema, $promiseAdapter, $query); $response @@ -205,7 +205,7 @@ function execute( $validations[] = new QueryComplexity($maxComplexity); $validations[] = new QueryDepth($maxDepth); } - if (Http::getMode() === Http::MODE_TYPE_PRODUCTION) { + if (App::getMode() === App::MODE_TYPE_PRODUCTION) { $flags = DebugFlag::NONE; } @@ -306,10 +306,9 @@ function processResult($result, $debugFlags): array ); } -Http::shutdown() +App::shutdown() ->groups(['schema']) ->inject('project') - ->inject('schemaVariable') - ->action(function (Document $project, Schema $schemaVariable) { - $schemaVariable->setDirty($project->getId()); + ->action(function (Document $project) { + Schema::setDirty($project->getId()); }); diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index 47d80dbf71..f4581df8e4 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -3,19 +3,12 @@ use Appwrite\ClamAV\Network; use Appwrite\Event\Event; use Appwrite\Extend\Exception; -use Appwrite\Utopia\Queue\Connections; use Appwrite\Utopia\Response; +use Utopia\App; use Utopia\Config\Config; -use Utopia\Database\Adapter\MariaDB; -use Utopia\Database\Adapter\MySQL; use Utopia\Database\Document; use Utopia\Domains\Validator\PublicDomain; -use Utopia\Http\Http; -use Utopia\Http\Validator\Domain; -use Utopia\Http\Validator\Integer; -use Utopia\Http\Validator\Multiple; -use Utopia\Http\Validator\Text; -use Utopia\Http\Validator\WhiteList; +use Utopia\Pools\Group; use Utopia\Queue\Client; use Utopia\Queue\Connection; use Utopia\Registry\Registry; @@ -23,8 +16,13 @@ use Utopia\Storage\Device; use Utopia\Storage\Device\Local; use Utopia\Storage\Storage; use Utopia\System\System; +use Utopia\Validator\Domain; +use Utopia\Validator\Integer; +use Utopia\Validator\Multiple; +use Utopia\Validator\Text; +use Utopia\Validator\WhiteList; -Http::get('/v1/health') +App::get('/v1/health') ->desc('Get HTTP') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -47,7 +45,7 @@ Http::get('/v1/health') $response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS); }); -Http::get('/v1/health/version') +App::get('/v1/health/version') ->desc('Get version') ->groups(['api', 'health']) ->label('scope', 'public') @@ -59,7 +57,7 @@ Http::get('/v1/health/version') $response->dynamic(new Document([ 'version' => APP_VERSION_STABLE ]), Response::MODEL_HEALTH_VERSION); }); -Http::get('/v1/health/db') +App::get('/v1/health/db') ->desc('Get DB') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -72,34 +70,21 @@ Http::get('/v1/health/db') ->label('sdk.response.model', Response::MODEL_HEALTH_STATUS) ->inject('response') ->inject('pools') - ->inject('connections') - ->action(function (Response $response, array $pools, Connections $connections) { + ->action(function (Response $response, Group $pools) { $output = []; $configs = [ - 'console' => Config::getParam('pools-console'), - 'database' => Config::getParam('pools-database'), + 'Console.DB' => Config::getParam('pools-console'), + 'Projects.DB' => Config::getParam('pools-database'), ]; foreach ($configs as $key => $config) { foreach ($config as $database) { - $checkStart = \microtime(true); - try { + $adapter = $pools->get($database)->pop()->getResource(); - $pool = $pools['pools-'.$key.'-'.$database]['pool']; - $dsn = $pools['pools-'.$key.'-'.$database]['dsn']; - - $connection = $pool->get(); - $connections->add($connection, $pool); - $adapter = match ($dsn->getScheme()) { - 'mariadb' => new MariaDB($connection), - 'mysql' => new MySQL($connection), - default => null - }; - $adapter->setDatabase($dsn->getPath()); - + $checkStart = \microtime(true); if ($adapter->ping()) { $output[] = new Document([ @@ -126,7 +111,7 @@ Http::get('/v1/health/db') ]), Response::MODEL_HEALTH_STATUS_LIST); }); -Http::get('/v1/health/cache') +App::get('/v1/health/cache') ->desc('Get cache') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -139,8 +124,7 @@ Http::get('/v1/health/cache') ->label('sdk.response.model', Response::MODEL_HEALTH_STATUS) ->inject('response') ->inject('pools') - ->inject('connections') - ->action(function (Response $response, array $pools, Connections $connections) { + ->action(function (Response $response, Group $pools) { $output = []; @@ -150,142 +134,8 @@ Http::get('/v1/health/cache') foreach ($configs as $key => $config) { foreach ($config as $database) { - $checkStart = \microtime(true); try { - $pool = $pools['pools-cache-' . $database]['pool']; - $dsn = $pools['pools-cache-' . $database]['dsn']; - $connection = $pool->get(); - $connections->add($connection, $pool); - - $adapter = new Connection\Redis($dsn->getHost(), $dsn->getPort()); - - - if ($adapter->ping()) { - $output[] = new Document([ - 'name' => $key . " ($database)", - 'status' => 'pass', - 'ping' => \round((\microtime(true) - $checkStart) / 1000) - ]); - } else { - $output[] = new Document([ - 'name' => $key . " ($database)", - 'status' => 'fail', - 'ping' => \round((\microtime(true) - $checkStart) / 1000) - ]); - } - } catch (\Throwable $th) { - $output[] = new Document([ - 'name' => $key . " ($database)", - 'status' => 'fail', - 'ping' => \round((\microtime(true) - $checkStart) / 1000) - ]); - } finally { - $connections->reclaim(); - } - } - } - - $response->dynamic(new Document([ - 'statuses' => $output, - 'total' => count($output), - ]), Response::MODEL_HEALTH_STATUS_LIST); - }); - -Http::get('/v1/health/queue') - ->desc('Get queue') - ->groups(['api', 'health']) - ->label('scope', 'health.read') - ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) - ->label('sdk.namespace', 'health') - ->label('sdk.method', 'getQueue') - ->label('sdk.description', '/docs/references/health/get-queue.md') - ->label('sdk.response.code', Response::STATUS_CODE_OK) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_HEALTH_STATUS) - ->inject('response') - ->inject('pools') - ->inject('connections') - ->action(function (Response $response, array $pools, Connections $connections) { - - $output = []; - - $configs = [ - 'Queue' => Config::getParam('pools-queue'), - ]; - - foreach ($configs as $key => $config) { - $checkStart = \microtime(true); - - foreach ($config as $database) { - try { - $pool = $pools['pools-queue-' . $database]['pool']; - $dsn = $pools['pools-queue-' . $database]['dsn']; - $connection = $pool->get(); - $connections->add($connection, $pool); - - $adapter = new Connection\Redis($dsn->getHost(), $dsn->getPort()); - if ($adapter->ping()) { - $output[] = new Document([ - 'name' => $key . " ($database)", - 'status' => 'pass', - 'ping' => \round((\microtime(true) - $checkStart) / 1000) - ]); - } else { - $output[] = new Document([ - 'name' => $key . " ($database)", - 'status' => 'fail', - 'ping' => \round((\microtime(true) - $checkStart) / 1000) - ]); - } - } catch (\Throwable $th) { - $output[] = new Document([ - 'name' => $key . " ($database)", - 'status' => 'fail', - 'ping' => \round((\microtime(true) - $checkStart) / 1000) - ]); - } finally { - $connections->reclaim(); - } - } - } - - $response->dynamic(new Document([ - 'statuses' => $output, - 'total' => count($output), - ]), Response::MODEL_HEALTH_STATUS_LIST); - }); - -Http::get('/v1/health/pubsub') - ->desc('Get pubsub') - ->groups(['api', 'health']) - ->label('scope', 'health.read') - ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) - ->label('sdk.namespace', 'health') - ->label('sdk.method', 'getPubSub') - ->label('sdk.description', '/docs/references/health/get-pubsub.md') - ->label('sdk.response.code', Response::STATUS_CODE_OK) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_HEALTH_STATUS) - ->inject('response') - ->inject('pools') - ->inject('connections') - ->action(function (Response $response, array $pools, Connections $connections) { - - $output = []; - - $configs = [ - 'PubSub' => Config::getParam('pools-pubsub'), - ]; - - foreach ($configs as $key => $config) { - foreach ($config as $database) { - try { - $pool = $pools['pools-pubsub-' . $database]['pool']; - - $connection = $pool->get(); - $connections->add($connection, $pool); - - $adapter = new Connection\Redis($connection); + $adapter = $pools->get($database)->pop()->getResource(); $checkStart = \microtime(true); @@ -308,8 +158,6 @@ Http::get('/v1/health/pubsub') 'status' => 'fail', 'ping' => \round((\microtime(true) - $checkStart) / 1000) ]); - } finally { - $connections->reclaim(); } } } @@ -320,7 +168,121 @@ Http::get('/v1/health/pubsub') ]), Response::MODEL_HEALTH_STATUS_LIST); }); -Http::get('/v1/health/time') +App::get('/v1/health/queue') + ->desc('Get queue') + ->groups(['api', 'health']) + ->label('scope', 'health.read') + ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'health') + ->label('sdk.method', 'getQueue') + ->label('sdk.description', '/docs/references/health/get-queue.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_HEALTH_STATUS) + ->inject('response') + ->inject('pools') + ->action(function (Response $response, Group $pools) { + + $output = []; + + $configs = [ + 'Queue' => Config::getParam('pools-queue'), + ]; + + foreach ($configs as $key => $config) { + foreach ($config as $database) { + try { + $adapter = $pools->get($database)->pop()->getResource(); + + $checkStart = \microtime(true); + + if ($adapter->ping()) { + $output[] = new Document([ + 'name' => $key . " ($database)", + 'status' => 'pass', + 'ping' => \round((\microtime(true) - $checkStart) / 1000) + ]); + } else { + $output[] = new Document([ + 'name' => $key . " ($database)", + 'status' => 'fail', + 'ping' => \round((\microtime(true) - $checkStart) / 1000) + ]); + } + } catch (\Throwable $th) { + $output[] = new Document([ + 'name' => $key . " ($database)", + 'status' => 'fail', + 'ping' => \round((\microtime(true) - $checkStart) / 1000) + ]); + } + } + } + + $response->dynamic(new Document([ + 'statuses' => $output, + 'total' => count($output), + ]), Response::MODEL_HEALTH_STATUS_LIST); + }); + +App::get('/v1/health/pubsub') + ->desc('Get pubsub') + ->groups(['api', 'health']) + ->label('scope', 'health.read') + ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'health') + ->label('sdk.method', 'getPubSub') + ->label('sdk.description', '/docs/references/health/get-pubsub.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_HEALTH_STATUS) + ->inject('response') + ->inject('pools') + ->action(function (Response $response, Group $pools) { + + $output = []; + + $configs = [ + 'PubSub' => Config::getParam('pools-pubsub'), + ]; + + foreach ($configs as $key => $config) { + foreach ($config as $database) { + try { + $adapter = $pools->get($database)->pop()->getResource(); + + $checkStart = \microtime(true); + + if ($adapter->ping()) { + $output[] = new Document([ + 'name' => $key . " ($database)", + 'status' => 'pass', + 'ping' => \round((\microtime(true) - $checkStart) / 1000) + ]); + } else { + $output[] = new Document([ + 'name' => $key . " ($database)", + 'status' => 'fail', + 'ping' => \round((\microtime(true) - $checkStart) / 1000) + ]); + } + } catch (\Throwable $th) { + $output[] = new Document([ + 'name' => $key . " ($database)", + 'status' => 'fail', + 'ping' => \round((\microtime(true) - $checkStart) / 1000) + ]); + } + } + } + + $response->dynamic(new Document([ + 'statuses' => $output, + 'total' => count($output), + ]), Response::MODEL_HEALTH_STATUS_LIST); + }); + +App::get('/v1/health/time') ->desc('Get time') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -377,7 +339,7 @@ Http::get('/v1/health/time') $response->dynamic(new Document($output), Response::MODEL_HEALTH_TIME); }); -Http::get('/v1/health/queue/webhooks') +App::get('/v1/health/queue/webhooks') ->desc('Get webhooks queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -404,7 +366,7 @@ Http::get('/v1/health/queue/webhooks') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); -Http::get('/v1/health/queue/logs') +App::get('/v1/health/queue/logs') ->desc('Get logs queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -431,7 +393,7 @@ Http::get('/v1/health/queue/logs') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); -Http::get('/v1/health/certificate') +App::get('/v1/health/certificate') ->desc('Get the SSL certificate for a domain') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -481,7 +443,7 @@ Http::get('/v1/health/certificate') ]), Response::MODEL_HEALTH_CERTIFICATE); }, ['response']); -Http::get('/v1/health/queue/certificates') +App::get('/v1/health/queue/certificates') ->desc('Get certificates queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -508,7 +470,7 @@ Http::get('/v1/health/queue/certificates') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); -Http::get('/v1/health/queue/builds') +App::get('/v1/health/queue/builds') ->desc('Get builds queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -535,7 +497,7 @@ Http::get('/v1/health/queue/builds') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); -Http::get('/v1/health/queue/databases') +App::get('/v1/health/queue/databases') ->desc('Get databases queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -563,7 +525,7 @@ Http::get('/v1/health/queue/databases') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); -Http::get('/v1/health/queue/deletes') +App::get('/v1/health/queue/deletes') ->desc('Get deletes queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -590,7 +552,7 @@ Http::get('/v1/health/queue/deletes') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); -Http::get('/v1/health/queue/mails') +App::get('/v1/health/queue/mails') ->desc('Get mails queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -617,7 +579,7 @@ Http::get('/v1/health/queue/mails') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); -Http::get('/v1/health/queue/messaging') +App::get('/v1/health/queue/messaging') ->desc('Get messaging queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -644,7 +606,7 @@ Http::get('/v1/health/queue/messaging') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); -Http::get('/v1/health/queue/migrations') +App::get('/v1/health/queue/migrations') ->desc('Get migrations queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -671,7 +633,7 @@ Http::get('/v1/health/queue/migrations') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); -Http::get('/v1/health/queue/functions') +App::get('/v1/health/queue/functions') ->desc('Get functions queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -698,7 +660,7 @@ Http::get('/v1/health/queue/functions') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); -Http::get('/v1/health/queue/usage') +App::get('/v1/health/queue/usage') ->desc('Get usage queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -725,7 +687,7 @@ Http::get('/v1/health/queue/usage') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }); -Http::get('/v1/health/queue/usage-dump') +App::get('/v1/health/queue/usage-dump') ->desc('Get usage dump queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -752,7 +714,7 @@ Http::get('/v1/health/queue/usage-dump') $response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE); }); -Http::get('/v1/health/storage/local') +App::get('/v1/health/storage/local') ->desc('Get local storage') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -795,7 +757,7 @@ Http::get('/v1/health/storage/local') $response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS); }); -Http::get('/v1/health/storage') +App::get('/v1/health/storage') ->desc('Get storage') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -836,7 +798,7 @@ Http::get('/v1/health/storage') $response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS); }); -Http::get('/v1/health/anti-virus') +App::get('/v1/health/anti-virus') ->desc('Get antivirus') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -875,7 +837,7 @@ Http::get('/v1/health/anti-virus') $response->dynamic(new Document($output), Response::MODEL_HEALTH_ANTIVIRUS); }); -Http::get('/v1/health/queue/failed/:name') +App::get('/v1/health/queue/failed/:name') ->desc('Get number of failed queue jobs') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -916,7 +878,7 @@ Http::get('/v1/health/queue/failed/:name') $response->dynamic(new Document([ 'size' => $failed ]), Response::MODEL_HEALTH_QUEUE); }); -Http::get('/v1/health/stats') // Currently only used internally +App::get('/v1/health/stats') // Currently only used internally ->desc('Get system stats') ->groups(['api', 'health']) ->label('scope', 'root') diff --git a/app/controllers/api/locale.php b/app/controllers/api/locale.php index fcaf0c03cb..2917bc8416 100644 --- a/app/controllers/api/locale.php +++ b/app/controllers/api/locale.php @@ -3,12 +3,12 @@ use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use MaxMind\Db\Reader; +use Utopia\App; use Utopia\Config\Config; use Utopia\Database\Document; -use Utopia\Http\Http; use Utopia\Locale\Locale; -Http::get('/v1/locale') +App::get('/v1/locale') ->desc('Get user locale') ->groups(['api', 'locale']) ->label('scope', 'locale.read') @@ -68,8 +68,8 @@ Http::get('/v1/locale') $response->dynamic(new Document($output), Response::MODEL_LOCALE); }); -Http::get('/v1/locale/codes') - ->desc('List Locale Codes') +App::get('/v1/locale/codes') + ->desc('List locale codes') ->groups(['api', 'locale']) ->label('scope', 'locale.read') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) @@ -90,7 +90,7 @@ Http::get('/v1/locale/codes') ]), Response::MODEL_LOCALE_CODE_LIST); }); -Http::get('/v1/locale/countries') +App::get('/v1/locale/countries') ->desc('List countries') ->groups(['api', 'locale']) ->label('scope', 'locale.read') @@ -123,7 +123,7 @@ Http::get('/v1/locale/countries') $response->dynamic(new Document(['countries' => $output, 'total' => \count($output)]), Response::MODEL_COUNTRY_LIST); }); -Http::get('/v1/locale/countries/eu') +App::get('/v1/locale/countries/eu') ->desc('List EU countries') ->groups(['api', 'locale']) ->label('scope', 'locale.read') @@ -158,7 +158,7 @@ Http::get('/v1/locale/countries/eu') $response->dynamic(new Document(['countries' => $output, 'total' => \count($output)]), Response::MODEL_COUNTRY_LIST); }); -Http::get('/v1/locale/countries/phones') +App::get('/v1/locale/countries/phones') ->desc('List countries phone codes') ->groups(['api', 'locale']) ->label('scope', 'locale.read') @@ -192,7 +192,7 @@ Http::get('/v1/locale/countries/phones') $response->dynamic(new Document(['phones' => $output, 'total' => \count($output)]), Response::MODEL_PHONE_LIST); }); -Http::get('/v1/locale/continents') +App::get('/v1/locale/continents') ->desc('List continents') ->groups(['api', 'locale']) ->label('scope', 'locale.read') @@ -224,7 +224,7 @@ Http::get('/v1/locale/continents') $response->dynamic(new Document(['continents' => $output, 'total' => \count($output)]), Response::MODEL_CONTINENT_LIST); }); -Http::get('/v1/locale/currencies') +App::get('/v1/locale/currencies') ->desc('List currencies') ->groups(['api', 'locale']) ->label('scope', 'locale.read') @@ -247,7 +247,7 @@ Http::get('/v1/locale/currencies') }); -Http::get('/v1/locale/languages') +App::get('/v1/locale/languages') ->desc('List languages') ->groups(['api', 'locale']) ->label('scope', 'locale.read') diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 8beb38c7ca..7da0348a8f 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -20,6 +20,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Targets; use Appwrite\Utopia\Database\Validator\Queries\Topics; use Appwrite\Utopia\Response; use MaxMind\Db\Reader; +use Utopia\App; use Utopia\Audit\Audit; use Utopia\Database\Database; use Utopia\Database\DateTime; @@ -29,27 +30,25 @@ use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; -use Utopia\Database\Validator\Authorization\Input; use Utopia\Database\Validator\Datetime as DatetimeValidator; use Utopia\Database\Validator\Queries; use Utopia\Database\Validator\Query\Limit; use Utopia\Database\Validator\Query\Offset; use Utopia\Database\Validator\Roles; use Utopia\Database\Validator\UID; -use Utopia\Http\Http; -use Utopia\Http\Validator\ArrayList; -use Utopia\Http\Validator\Boolean; -use Utopia\Http\Validator\Integer; -use Utopia\Http\Validator\JSON; -use Utopia\Http\Validator\Range; -use Utopia\Http\Validator\Text; -use Utopia\Http\Validator\WhiteList; use Utopia\Locale\Locale; use Utopia\System\System; +use Utopia\Validator\ArrayList; +use Utopia\Validator\Boolean; +use Utopia\Validator\Integer; +use Utopia\Validator\JSON; +use Utopia\Validator\Range; +use Utopia\Validator\Text; +use Utopia\Validator\WhiteList; use function Swoole\Coroutine\batch; -Http::post('/v1/messaging/providers/mailgun') +App::post('/v1/messaging/providers/mailgun') ->desc('Create Mailgun provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.create') @@ -136,7 +135,7 @@ Http::post('/v1/messaging/providers/mailgun') ->dynamic($provider, Response::MODEL_PROVIDER); }); -Http::post('/v1/messaging/providers/sendgrid') +App::post('/v1/messaging/providers/sendgrid') ->desc('Create Sendgrid provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.create') @@ -211,7 +210,7 @@ Http::post('/v1/messaging/providers/sendgrid') ->dynamic($provider, Response::MODEL_PROVIDER); }); -Http::post('/v1/messaging/providers/smtp') +App::post('/v1/messaging/providers/smtp') ->desc('Create SMTP provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.create') @@ -299,7 +298,7 @@ Http::post('/v1/messaging/providers/smtp') ->dynamic($provider, Response::MODEL_PROVIDER); }); -Http::post('/v1/messaging/providers/msg91') +App::post('/v1/messaging/providers/msg91') ->desc('Create Msg91 provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.create') @@ -375,7 +374,7 @@ Http::post('/v1/messaging/providers/msg91') ->dynamic($provider, Response::MODEL_PROVIDER); }); -Http::post('/v1/messaging/providers/telesign') +App::post('/v1/messaging/providers/telesign') ->desc('Create Telesign provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.create') @@ -452,7 +451,7 @@ Http::post('/v1/messaging/providers/telesign') ->dynamic($provider, Response::MODEL_PROVIDER); }); -Http::post('/v1/messaging/providers/textmagic') +App::post('/v1/messaging/providers/textmagic') ->desc('Create Textmagic provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.create') @@ -529,7 +528,7 @@ Http::post('/v1/messaging/providers/textmagic') ->dynamic($provider, Response::MODEL_PROVIDER); }); -Http::post('/v1/messaging/providers/twilio') +App::post('/v1/messaging/providers/twilio') ->desc('Create Twilio provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.create') @@ -606,7 +605,7 @@ Http::post('/v1/messaging/providers/twilio') ->dynamic($provider, Response::MODEL_PROVIDER); }); -Http::post('/v1/messaging/providers/vonage') +App::post('/v1/messaging/providers/vonage') ->desc('Create Vonage provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.create') @@ -683,7 +682,7 @@ Http::post('/v1/messaging/providers/vonage') ->dynamic($provider, Response::MODEL_PROVIDER); }); -Http::post('/v1/messaging/providers/fcm') +App::post('/v1/messaging/providers/fcm') ->desc('Create FCM provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.create') @@ -746,7 +745,7 @@ Http::post('/v1/messaging/providers/fcm') ->dynamic($provider, Response::MODEL_PROVIDER); }); -Http::post('/v1/messaging/providers/apns') +App::post('/v1/messaging/providers/apns') ->desc('Create APNS provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.create') @@ -832,7 +831,7 @@ Http::post('/v1/messaging/providers/apns') ->dynamic($provider, Response::MODEL_PROVIDER); }); -Http::get('/v1/messaging/providers') +App::get('/v1/messaging/providers') ->desc('List providers') ->groups(['api', 'messaging']) ->label('scope', 'providers.read') @@ -847,8 +846,7 @@ Http::get('/v1/messaging/providers') ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->inject('dbForProject') ->inject('response') - ->inject('authorization') - ->action(function (array $queries, string $search, Database $dbForProject, Response $response, Authorization $authorization) { + ->action(function (array $queries, string $search, Database $dbForProject, Response $response) { try { $queries = Query::parseQueries($queries); } catch (QueryException $e) { @@ -869,7 +867,7 @@ Http::get('/v1/messaging/providers') if ($cursor) { $providerId = $cursor->getValue(); - $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('providers', $providerId)); + $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId)); if ($cursorDocument->isEmpty()) { throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Provider '{$providerId}' for the 'cursor' value not found."); @@ -884,7 +882,7 @@ Http::get('/v1/messaging/providers') ]), Response::MODEL_PROVIDER_LIST); }); -Http::get('/v1/messaging/providers/:providerId/logs') +App::get('/v1/messaging/providers/:providerId/logs') ->desc('List provider logs') ->groups(['api', 'messaging']) ->label('scope', 'providers.read') @@ -972,7 +970,7 @@ Http::get('/v1/messaging/providers/:providerId/logs') ]), Response::MODEL_LOG_LIST); }); -Http::get('/v1/messaging/providers/:providerId') +App::get('/v1/messaging/providers/:providerId') ->desc('Get provider') ->groups(['api', 'messaging']) ->label('scope', 'providers.read') @@ -996,7 +994,7 @@ Http::get('/v1/messaging/providers/:providerId') $response->dynamic($provider, Response::MODEL_PROVIDER); }); -Http::patch('/v1/messaging/providers/mailgun/:providerId') +App::patch('/v1/messaging/providers/mailgun/:providerId') ->desc('Update Mailgun provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.update') @@ -1102,7 +1100,7 @@ Http::patch('/v1/messaging/providers/mailgun/:providerId') ->dynamic($provider, Response::MODEL_PROVIDER); }); -Http::patch('/v1/messaging/providers/sendgrid/:providerId') +App::patch('/v1/messaging/providers/sendgrid/:providerId') ->desc('Update Sendgrid provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.update') @@ -1193,7 +1191,7 @@ Http::patch('/v1/messaging/providers/sendgrid/:providerId') ->dynamic($provider, Response::MODEL_PROVIDER); }); -Http::patch('/v1/messaging/providers/smtp/:providerId') +App::patch('/v1/messaging/providers/smtp/:providerId') ->desc('Update SMTP provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.update') @@ -1315,7 +1313,7 @@ Http::patch('/v1/messaging/providers/smtp/:providerId') ->dynamic($provider, Response::MODEL_PROVIDER); }); -Http::patch('/v1/messaging/providers/msg91/:providerId') +App::patch('/v1/messaging/providers/msg91/:providerId') ->desc('Update Msg91 provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.update') @@ -1395,7 +1393,7 @@ Http::patch('/v1/messaging/providers/msg91/:providerId') ->dynamic($provider, Response::MODEL_PROVIDER); }); -Http::patch('/v1/messaging/providers/telesign/:providerId') +App::patch('/v1/messaging/providers/telesign/:providerId') ->desc('Update Telesign provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.update') @@ -1477,7 +1475,7 @@ Http::patch('/v1/messaging/providers/telesign/:providerId') ->dynamic($provider, Response::MODEL_PROVIDER); }); -Http::patch('/v1/messaging/providers/textmagic/:providerId') +App::patch('/v1/messaging/providers/textmagic/:providerId') ->desc('Update Textmagic provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.update') @@ -1559,7 +1557,7 @@ Http::patch('/v1/messaging/providers/textmagic/:providerId') ->dynamic($provider, Response::MODEL_PROVIDER); }); -Http::patch('/v1/messaging/providers/twilio/:providerId') +App::patch('/v1/messaging/providers/twilio/:providerId') ->desc('Update Twilio provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.update') @@ -1641,7 +1639,7 @@ Http::patch('/v1/messaging/providers/twilio/:providerId') ->dynamic($provider, Response::MODEL_PROVIDER); }); -Http::patch('/v1/messaging/providers/vonage/:providerId') +App::patch('/v1/messaging/providers/vonage/:providerId') ->desc('Update Vonage provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.update') @@ -1723,7 +1721,7 @@ Http::patch('/v1/messaging/providers/vonage/:providerId') ->dynamic($provider, Response::MODEL_PROVIDER); }); -Http::patch('/v1/messaging/providers/fcm/:providerId') +App::patch('/v1/messaging/providers/fcm/:providerId') ->desc('Update FCM provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.update') @@ -1792,7 +1790,7 @@ Http::patch('/v1/messaging/providers/fcm/:providerId') }); -Http::patch('/v1/messaging/providers/apns/:providerId') +App::patch('/v1/messaging/providers/apns/:providerId') ->desc('Update APNS provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.update') @@ -1887,7 +1885,7 @@ Http::patch('/v1/messaging/providers/apns/:providerId') ->dynamic($provider, Response::MODEL_PROVIDER); }); -Http::delete('/v1/messaging/providers/:providerId') +App::delete('/v1/messaging/providers/:providerId') ->desc('Delete provider') ->groups(['api', 'messaging']) ->label('audits.event', 'provider.delete') @@ -1922,7 +1920,7 @@ Http::delete('/v1/messaging/providers/:providerId') ->noContent(); }); -Http::post('/v1/messaging/topics') +App::post('/v1/messaging/topics') ->desc('Create topic') ->groups(['api', 'messaging']) ->label('audits.event', 'topic.create') @@ -1965,7 +1963,7 @@ Http::post('/v1/messaging/topics') ->dynamic($topic, Response::MODEL_TOPIC); }); -Http::get('/v1/messaging/topics') +App::get('/v1/messaging/topics') ->desc('List topics') ->groups(['api', 'messaging']) ->label('scope', 'topics.read') @@ -1980,8 +1978,7 @@ Http::get('/v1/messaging/topics') ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->inject('dbForProject') ->inject('response') - ->inject('authorization') - ->action(function (array $queries, string $search, Database $dbForProject, Response $response, Authorization $authorization) { + ->action(function (array $queries, string $search, Database $dbForProject, Response $response) { try { $queries = Query::parseQueries($queries); } catch (QueryException $e) { @@ -2002,7 +1999,7 @@ Http::get('/v1/messaging/topics') if ($cursor) { $topicId = $cursor->getValue(); - $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId)); + $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); if ($cursorDocument->isEmpty()) { throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Topic '{$topicId}' for the 'cursor' value not found."); @@ -2017,7 +2014,7 @@ Http::get('/v1/messaging/topics') ]), Response::MODEL_TOPIC_LIST); }); -Http::get('/v1/messaging/topics/:topicId/logs') +App::get('/v1/messaging/topics/:topicId/logs') ->desc('List topic logs') ->groups(['api', 'messaging']) ->label('scope', 'topics.read') @@ -2034,8 +2031,7 @@ Http::get('/v1/messaging/topics/:topicId/logs') ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->inject('authorization') - ->action(function (string $topicId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) { + ->action(function (string $topicId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { $topic = $dbForProject->getDocument('topics', $topicId); if ($topic->isEmpty()) { @@ -2107,7 +2103,7 @@ Http::get('/v1/messaging/topics/:topicId/logs') ]), Response::MODEL_LOG_LIST); }); -Http::get('/v1/messaging/topics/:topicId') +App::get('/v1/messaging/topics/:topicId') ->desc('Get topic') ->groups(['api', 'messaging']) ->label('scope', 'topics.read') @@ -2132,7 +2128,7 @@ Http::get('/v1/messaging/topics/:topicId') ->dynamic($topic, Response::MODEL_TOPIC); }); -Http::patch('/v1/messaging/topics/:topicId') +App::patch('/v1/messaging/topics/:topicId') ->desc('Update topic') ->groups(['api', 'messaging']) ->label('audits.event', 'topic.update') @@ -2176,7 +2172,7 @@ Http::patch('/v1/messaging/topics/:topicId') ->dynamic($topic, Response::MODEL_TOPIC); }); -Http::delete('/v1/messaging/topics/:topicId') +App::delete('/v1/messaging/topics/:topicId') ->desc('Delete topic') ->groups(['api', 'messaging']) ->label('audits.event', 'topic.delete') @@ -2216,7 +2212,7 @@ Http::delete('/v1/messaging/topics/:topicId') ->noContent(); }); -Http::post('/v1/messaging/topics/:topicId/subscribers') +App::post('/v1/messaging/topics/:topicId/subscribers') ->desc('Create subscriber') ->groups(['api', 'messaging']) ->label('audits.event', 'subscriber.create') @@ -2236,27 +2232,28 @@ Http::post('/v1/messaging/topics/:topicId/subscribers') ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->inject('authorization') - ->action(function (string $subscriberId, string $topicId, string $targetId, Event $queueForEvents, Database $dbForProject, Response $response, Authorization $authorization) { + ->action(function (string $subscriberId, string $topicId, string $targetId, Event $queueForEvents, Database $dbForProject, Response $response) { $subscriberId = $subscriberId == 'unique()' ? ID::unique() : $subscriberId; - $topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId)); + $topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); if ($topic->isEmpty()) { throw new Exception(Exception::TOPIC_NOT_FOUND); } - if (!$authorization->isValid(new Input('subscribe', $topic->getAttribute('subscribe')))) { - throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription()); + $validator = new Authorization('subscribe'); + + if (!$validator->isValid($topic->getAttribute('subscribe'))) { + throw new Exception(Exception::USER_UNAUTHORIZED, $validator->getDescription()); } - $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId)); + $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId)); if ($target->isEmpty()) { throw new Exception(Exception::USER_TARGET_NOT_FOUND); } - $user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId'))); + $user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId'))); $subscriber = new Document([ '$id' => $subscriberId, @@ -2289,7 +2286,7 @@ Http::post('/v1/messaging/topics/:topicId/subscribers') default => throw new Exception(Exception::TARGET_PROVIDER_INVALID_TYPE), }; - $authorization->skip(fn () => $dbForProject->increaseDocumentAttribute( + Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute( 'topics', $topicId, $totalAttribute, @@ -2311,7 +2308,7 @@ Http::post('/v1/messaging/topics/:topicId/subscribers') ->dynamic($subscriber, Response::MODEL_SUBSCRIBER); }); -Http::get('/v1/messaging/topics/:topicId/subscribers') +App::get('/v1/messaging/topics/:topicId/subscribers') ->desc('List subscribers') ->groups(['api', 'messaging']) ->label('scope', 'subscribers.read') @@ -2327,8 +2324,7 @@ Http::get('/v1/messaging/topics/:topicId/subscribers') ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->inject('dbForProject') ->inject('response') - ->inject('authorization') - ->action(function (string $topicId, array $queries, string $search, Database $dbForProject, Response $response, Authorization $authorization) { + ->action(function (string $topicId, array $queries, string $search, Database $dbForProject, Response $response) { try { $queries = Query::parseQueries($queries); } catch (QueryException $e) { @@ -2339,7 +2335,7 @@ Http::get('/v1/messaging/topics/:topicId/subscribers') $queries[] = Query::search('search', $search); } - $topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId)); + $topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); if ($topic->isEmpty()) { throw new Exception(Exception::TOPIC_NOT_FOUND); @@ -2357,7 +2353,7 @@ Http::get('/v1/messaging/topics/:topicId/subscribers') if ($cursor) { $subscriberId = $cursor->getValue(); - $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('subscribers', $subscriberId)); + $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('subscribers', $subscriberId)); if ($cursorDocument->isEmpty()) { throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Subscriber '{$subscriberId}' for the 'cursor' value not found."); @@ -2368,10 +2364,10 @@ Http::get('/v1/messaging/topics/:topicId/subscribers') $subscribers = $dbForProject->find('subscribers', $queries); - $subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject, $authorization) { - return function () use ($subscriber, $dbForProject, $authorization) { - $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId'))); - $user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId'))); + $subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject) { + return function () use ($subscriber, $dbForProject) { + $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId'))); + $user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId'))); return $subscriber ->setAttribute('target', $target) @@ -2386,7 +2382,7 @@ Http::get('/v1/messaging/topics/:topicId/subscribers') ]), Response::MODEL_SUBSCRIBER_LIST); }); -Http::get('/v1/messaging/subscribers/:subscriberId/logs') +App::get('/v1/messaging/subscribers/:subscriberId/logs') ->desc('List subscriber logs') ->groups(['api', 'messaging']) ->label('scope', 'subscribers.read') @@ -2403,8 +2399,7 @@ Http::get('/v1/messaging/subscribers/:subscriberId/logs') ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->inject('authorization') - ->action(function (string $subscriberId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) { + ->action(function (string $subscriberId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { $subscriber = $dbForProject->getDocument('subscribers', $subscriberId); if ($subscriber->isEmpty()) { @@ -2476,7 +2471,7 @@ Http::get('/v1/messaging/subscribers/:subscriberId/logs') ]), Response::MODEL_LOG_LIST); }); -Http::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId') +App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId') ->desc('Get subscriber') ->groups(['api', 'messaging']) ->label('scope', 'subscribers.read') @@ -2491,9 +2486,8 @@ Http::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId') ->param('subscriberId', '', new UID(), 'Subscriber ID.') ->inject('dbForProject') ->inject('response') - ->inject('authorization') - ->action(function (string $topicId, string $subscriberId, Database $dbForProject, Response $response, Authorization $authorization) { - $topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId)); + ->action(function (string $topicId, string $subscriberId, Database $dbForProject, Response $response) { + $topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); if ($topic->isEmpty()) { throw new Exception(Exception::TOPIC_NOT_FOUND); @@ -2505,8 +2499,8 @@ Http::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId') throw new Exception(Exception::SUBSCRIBER_NOT_FOUND); } - $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId'))); - $user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId'))); + $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId'))); + $user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId'))); $subscriber ->setAttribute('target', $target) @@ -2516,7 +2510,7 @@ Http::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId') ->dynamic($subscriber, Response::MODEL_SUBSCRIBER); }); -Http::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId') +App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId') ->desc('Delete subscriber') ->groups(['api', 'messaging']) ->label('audits.event', 'subscriber.delete') @@ -2535,9 +2529,8 @@ Http::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId') ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->inject('authorization') - ->action(function (string $topicId, string $subscriberId, Event $queueForEvents, Database $dbForProject, Response $response, Authorization $authorization) { - $topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId)); + ->action(function (string $topicId, string $subscriberId, Event $queueForEvents, Database $dbForProject, Response $response) { + $topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); if ($topic->isEmpty()) { throw new Exception(Exception::TOPIC_NOT_FOUND); @@ -2560,7 +2553,7 @@ Http::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId') default => throw new Exception(Exception::TARGET_PROVIDER_INVALID_TYPE), }; - $authorization->skip(fn () => $dbForProject->decreaseDocumentAttribute( + Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute( 'topics', $topicId, $totalAttribute, @@ -2576,7 +2569,7 @@ Http::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId') ->noContent(); }); -Http::post('/v1/messaging/messages/email') +App::post('/v1/messaging/messages/email') ->desc('Create email') ->groups(['api', 'messaging']) ->label('audits.event', 'message.create') @@ -2728,7 +2721,7 @@ Http::post('/v1/messaging/messages/email') ->dynamic($message, Response::MODEL_MESSAGE); }); -Http::post('/v1/messaging/messages/sms') +App::post('/v1/messaging/messages/sms') ->desc('Create SMS') ->groups(['api', 'messaging']) ->label('audits.event', 'message.create') @@ -2844,7 +2837,7 @@ Http::post('/v1/messaging/messages/sms') ->dynamic($message, Response::MODEL_MESSAGE); }); -Http::post('/v1/messaging/messages/push') +App::post('/v1/messaging/messages/push') ->desc('Create push notification') ->groups(['api', 'messaging']) ->label('audits.event', 'message.create') @@ -3020,7 +3013,7 @@ Http::post('/v1/messaging/messages/push') ->dynamic($message, Response::MODEL_MESSAGE); }); -Http::get('/v1/messaging/messages') +App::get('/v1/messaging/messages') ->desc('List messages') ->groups(['api', 'messaging']) ->label('scope', 'messages.read') @@ -3035,8 +3028,7 @@ Http::get('/v1/messaging/messages') ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->inject('dbForProject') ->inject('response') - ->inject('authorization') - ->action(function (array $queries, string $search, Database $dbForProject, Response $response, Authorization $authorization) { + ->action(function (array $queries, string $search, Database $dbForProject, Response $response) { try { $queries = Query::parseQueries($queries); } catch (QueryException $e) { @@ -3057,7 +3049,7 @@ Http::get('/v1/messaging/messages') if ($cursor) { $messageId = $cursor->getValue(); - $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('messages', $messageId)); + $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('messages', $messageId)); if ($cursorDocument->isEmpty()) { throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Message '{$messageId}' for the 'cursor' value not found."); @@ -3072,7 +3064,7 @@ Http::get('/v1/messaging/messages') ]), Response::MODEL_MESSAGE_LIST); }); -Http::get('/v1/messaging/messages/:messageId/logs') +App::get('/v1/messaging/messages/:messageId/logs') ->desc('List message logs') ->groups(['api', 'messaging']) ->label('scope', 'messages.read') @@ -3089,8 +3081,7 @@ Http::get('/v1/messaging/messages/:messageId/logs') ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->inject('authorization') - ->action(function (string $messageId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) { + ->action(function (string $messageId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { $message = $dbForProject->getDocument('messages', $messageId); if ($message->isEmpty()) { @@ -3162,7 +3153,7 @@ Http::get('/v1/messaging/messages/:messageId/logs') ]), Response::MODEL_LOG_LIST); }); -Http::get('/v1/messaging/messages/:messageId/targets') +App::get('/v1/messaging/messages/:messageId/targets') ->desc('List message targets') ->groups(['api', 'messaging']) ->label('scope', 'messages.read') @@ -3227,7 +3218,7 @@ Http::get('/v1/messaging/messages/:messageId/targets') ]), Response::MODEL_TARGET_LIST); }); -Http::get('/v1/messaging/messages/:messageId') +App::get('/v1/messaging/messages/:messageId') ->desc('Get message') ->groups(['api', 'messaging']) ->label('scope', 'messages.read') @@ -3251,7 +3242,7 @@ Http::get('/v1/messaging/messages/:messageId') $response->dynamic($message, Response::MODEL_MESSAGE); }); -Http::patch('/v1/messaging/messages/email/:messageId') +App::patch('/v1/messaging/messages/email/:messageId') ->desc('Update email') ->groups(['api', 'messaging']) ->label('audits.event', 'message.update') @@ -3451,7 +3442,7 @@ Http::patch('/v1/messaging/messages/email/:messageId') ->dynamic($message, Response::MODEL_MESSAGE); }); -Http::patch('/v1/messaging/messages/sms/:messageId') +App::patch('/v1/messaging/messages/sms/:messageId') ->desc('Update SMS') ->groups(['api', 'messaging']) ->label('audits.event', 'message.update') @@ -3606,7 +3597,7 @@ Http::patch('/v1/messaging/messages/sms/:messageId') ->dynamic($message, Response::MODEL_MESSAGE); }); -Http::patch('/v1/messaging/messages/push/:messageId') +App::patch('/v1/messaging/messages/push/:messageId') ->desc('Update push notification') ->groups(['api', 'messaging']) ->label('audits.event', 'message.update') @@ -3844,7 +3835,7 @@ Http::patch('/v1/messaging/messages/push/:messageId') ->dynamic($message, Response::MODEL_MESSAGE); }); -Http::delete('/v1/messaging/messages/:messageId') +App::delete('/v1/messaging/messages/:messageId') ->desc('Delete message') ->groups(['api', 'messaging']) ->label('audits.event', 'message.delete') diff --git a/app/controllers/api/migrations.php b/app/controllers/api/migrations.php index 5e08e197ae..bb89d4a26f 100644 --- a/app/controllers/api/migrations.php +++ b/app/controllers/api/migrations.php @@ -9,6 +9,7 @@ use Appwrite\Role; use Appwrite\Utopia\Database\Validator\Queries\Migrations; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; +use Utopia\App; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; @@ -16,22 +17,21 @@ use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Database\Validator\UID; -use Utopia\Http\Http; -use Utopia\Http\Validator\ArrayList; -use Utopia\Http\Validator\Host; -use Utopia\Http\Validator\Integer; -use Utopia\Http\Validator\Text; -use Utopia\Http\Validator\URL; -use Utopia\Http\Validator\WhiteList; use Utopia\Migration\Sources\Appwrite; use Utopia\Migration\Sources\Firebase; use Utopia\Migration\Sources\NHost; use Utopia\Migration\Sources\Supabase; use Utopia\System\System; +use Utopia\Validator\ArrayList; +use Utopia\Validator\Host; +use Utopia\Validator\Integer; +use Utopia\Validator\Text; +use Utopia\Validator\URL; +use Utopia\Validator\WhiteList; include_once __DIR__ . '/../shared/api.php'; -Http::post('/v1/migrations/appwrite') +App::post('/v1/migrations/appwrite') ->groups(['api', 'migrations']) ->desc('Migrate Appwrite data') ->label('scope', 'migrations.write') @@ -86,7 +86,7 @@ Http::post('/v1/migrations/appwrite') ->dynamic($migration, Response::MODEL_MIGRATION); }); -Http::post('/v1/migrations/firebase/oauth') +App::post('/v1/migrations/firebase/oauth') ->groups(['api', 'migrations']) ->desc('Migrate Firebase data (OAuth)') ->label('scope', 'migrations.write') @@ -120,7 +120,7 @@ Http::post('/v1/migrations/firebase/oauth') Query::equal('provider', ['firebase']), Query::equal('userInternalId', [$user->getInternalId()]), ]); - if ($identity->isEmpty()) { + if ($identity === false || $identity->isEmpty()) { throw new Exception(Exception::USER_IDENTITY_NOT_FOUND); } @@ -189,7 +189,7 @@ Http::post('/v1/migrations/firebase/oauth') ->dynamic($migration, Response::MODEL_MIGRATION); }); -Http::post('/v1/migrations/firebase') +App::post('/v1/migrations/firebase') ->groups(['api', 'migrations']) ->desc('Migrate Firebase data (Service Account)') ->label('scope', 'migrations.write') @@ -250,7 +250,7 @@ Http::post('/v1/migrations/firebase') ->dynamic($migration, Response::MODEL_MIGRATION); }); -Http::post('/v1/migrations/supabase') +App::post('/v1/migrations/supabase') ->groups(['api', 'migrations']) ->desc('Migrate Supabase data') ->label('scope', 'migrations.write') @@ -311,7 +311,7 @@ Http::post('/v1/migrations/supabase') ->dynamic($migration, Response::MODEL_MIGRATION); }); -Http::post('/v1/migrations/nhost') +App::post('/v1/migrations/nhost') ->groups(['api', 'migrations']) ->desc('Migrate NHost data') ->label('scope', 'migrations.write') @@ -374,7 +374,7 @@ Http::post('/v1/migrations/nhost') ->dynamic($migration, Response::MODEL_MIGRATION); }); -Http::get('/v1/migrations') +App::get('/v1/migrations') ->groups(['api', 'migrations']) ->desc('List migrations') ->label('scope', 'migrations.read') @@ -427,7 +427,7 @@ Http::get('/v1/migrations') ]), Response::MODEL_MIGRATION_LIST); }); -Http::get('/v1/migrations/:migrationId') +App::get('/v1/migrations/:migrationId') ->groups(['api', 'migrations']) ->desc('Get migration') ->label('scope', 'migrations.read') @@ -451,7 +451,7 @@ Http::get('/v1/migrations/:migrationId') $response->dynamic($migration, Response::MODEL_MIGRATION); }); -Http::get('/v1/migrations/appwrite/report') +App::get('/v1/migrations/appwrite/report') ->groups(['api', 'migrations']) ->desc('Generate a report on Appwrite data') ->label('scope', 'migrations.write') @@ -493,7 +493,7 @@ Http::get('/v1/migrations/appwrite/report') ->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT); }); -Http::get('/v1/migrations/firebase/report') +App::get('/v1/migrations/firebase/report') ->groups(['api', 'migrations']) ->desc('Generate a report on Firebase data') ->label('scope', 'migrations.write') @@ -540,7 +540,7 @@ Http::get('/v1/migrations/firebase/report') ->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT); }); -Http::get('/v1/migrations/firebase/report/oauth') +App::get('/v1/migrations/firebase/report/oauth') ->groups(['api', 'migrations']) ->desc('Generate a report on Firebase data using OAuth') ->label('scope', 'migrations.write') @@ -569,7 +569,7 @@ Http::get('/v1/migrations/firebase/report/oauth') Query::equal('userInternalId', [$user->getInternalId()]), ]); - if ($identity->isEmpty()) { + if ($identity === false || $identity->isEmpty()) { throw new Exception(Exception::USER_IDENTITY_NOT_FOUND); } @@ -631,7 +631,7 @@ Http::get('/v1/migrations/firebase/report/oauth') ->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT); }); -Http::get('/v1/migrations/firebase/connect') +App::get('/v1/migrations/firebase/connect') ->desc('Authorize with Firebase') ->groups(['api', 'migrations']) ->label('scope', 'migrations.write') @@ -673,7 +673,7 @@ Http::get('/v1/migrations/firebase/connect') ->redirect($url); }); -Http::get('/v1/migrations/firebase/redirect') +App::get('/v1/migrations/firebase/redirect') ->desc('Capture and receive data on Firebase authorization') ->groups(['api', 'migrations']) ->label('scope', 'public') @@ -744,7 +744,7 @@ Http::get('/v1/migrations/firebase/redirect') Query::equal('providerEmail', [$email]), ]); - if (!$identity->isEmpty()) { + if ($identity !== false && !$identity->isEmpty()) { if ($identity->getAttribute('userInternalId', '') !== $user->getInternalId()) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } @@ -785,8 +785,8 @@ Http::get('/v1/migrations/firebase/redirect') ->redirect($redirect); }); -Http::get('/v1/migrations/firebase/projects') - ->desc('List Firebase Projects') +App::get('/v1/migrations/firebase/projects') + ->desc('List Firebase projects') ->groups(['api', 'migrations']) ->label('scope', 'migrations.read') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) @@ -813,7 +813,7 @@ Http::get('/v1/migrations/firebase/projects') Query::equal('userInternalId', [$user->getInternalId()]), ]); - if ($identity->isEmpty()) { + if ($identity === false || $identity->isEmpty()) { throw new Exception(Exception::USER_IDENTITY_NOT_FOUND); } @@ -874,8 +874,8 @@ Http::get('/v1/migrations/firebase/projects') ]), Response::MODEL_MIGRATION_FIREBASE_PROJECT_LIST); }); -Http::get('/v1/migrations/firebase/deauthorize') - ->desc('Revoke Appwrite\'s authorization to access Firebase Projects') +App::get('/v1/migrations/firebase/deauthorize') + ->desc('Revoke Appwrite\'s authorization to access Firebase projects') ->groups(['api', 'migrations']) ->label('scope', 'migrations.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) @@ -893,7 +893,7 @@ Http::get('/v1/migrations/firebase/deauthorize') Query::equal('userInternalId', [$user->getInternalId()]), ]); - if ($identity->isEmpty()) { + if ($identity === false || $identity->isEmpty()) { throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN, 'Not authenticated with Firebase'); //TODO: Replace with USER_IDENTITY_NOT_FOUND } @@ -902,7 +902,7 @@ Http::get('/v1/migrations/firebase/deauthorize') $response->noContent(); }); -Http::get('/v1/migrations/supabase/report') +App::get('/v1/migrations/supabase/report') ->groups(['api', 'migrations']) ->desc('Generate a report on Supabase Data') ->label('scope', 'migrations.write') @@ -945,7 +945,7 @@ Http::get('/v1/migrations/supabase/report') ->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT); }); -Http::get('/v1/migrations/nhost/report') +App::get('/v1/migrations/nhost/report') ->groups(['api', 'migrations']) ->desc('Generate a report on NHost Data') ->label('scope', 'migrations.write') @@ -988,7 +988,7 @@ Http::get('/v1/migrations/nhost/report') ->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT); }); -Http::patch('/v1/migrations/:migrationId') +App::patch('/v1/migrations/:migrationId') ->groups(['api', 'migrations']) ->desc('Retry migration') ->label('scope', 'migrations.write') @@ -1033,7 +1033,7 @@ Http::patch('/v1/migrations/:migrationId') $response->noContent(); }); -Http::delete('/v1/migrations/:migrationId') +App::delete('/v1/migrations/:migrationId') ->groups(['api', 'migrations']) ->desc('Delete migration') ->label('scope', 'migrations.write') diff --git a/app/controllers/api/project.php b/app/controllers/api/project.php index 8a6f4d942e..6053326308 100644 --- a/app/controllers/api/project.php +++ b/app/controllers/api/project.php @@ -2,6 +2,7 @@ use Appwrite\Extend\Exception; use Appwrite\Utopia\Response; +use Utopia\App; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate as DuplicateException; @@ -12,11 +13,10 @@ use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Datetime as DateTimeValidator; use Utopia\Database\Validator\UID; -use Utopia\Http\Http; -use Utopia\Http\Validator\Text; -use Utopia\Http\Validator\WhiteList; +use Utopia\Validator\Text; +use Utopia\Validator\WhiteList; -Http::get('/v1/project/usage') +App::get('/v1/project/usage') ->desc('Get project usage stats') ->groups(['api', 'usage']) ->label('scope', 'projects.read') @@ -31,8 +31,7 @@ Http::get('/v1/project/usage') ->param('period', '1d', new WhiteList(['1h', '1d']), 'Period used', true) ->inject('response') ->inject('dbForProject') - ->inject('authorization') - ->action(function (string $startDate, string $endDate, string $period, Response $response, Database $dbForProject, Authorization $authorization) { + ->action(function (string $startDate, string $endDate, string $period, Response $response, Database $dbForProject) { $stats = $total = $usage = []; $format = 'Y-m-d 00:00:00'; $firstDay = (new DateTime($startDate))->format($format); @@ -79,7 +78,7 @@ Http::get('/v1/project/usage') '1d' => 'Y-m-d\T00:00:00.000P', }; - $authorization->skip(function () use ($dbForProject, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) { + Authorization::skip(function () use ($dbForProject, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) { foreach ($metrics['total'] as $metric) { $result = $dbForProject->findOne('stats', [ Query::equal('metric', [$metric]), @@ -297,6 +296,8 @@ Http::get('/v1/project/usage') 'buildsStorageTotal' => $total[METRIC_BUILDS_STORAGE], 'deploymentsStorageTotal' => $total[METRIC_DEPLOYMENTS_STORAGE], 'executionsBreakdown' => $executionsBreakdown, + 'executionsMbSecondsBreakdown' => $executionsMbSecondsBreakdown, + 'buildsMbSecondsBreakdown' => $buildsMbSecondsBreakdown, 'bucketsBreakdown' => $bucketsBreakdown, 'databasesStorageBreakdown' => $databasesStorageBreakdown, 'executionsMbSecondsBreakdown' => $executionsMbSecondsBreakdown, @@ -307,8 +308,8 @@ Http::get('/v1/project/usage') // Variables -Http::post('/v1/project/variables') - ->desc('Create Variable') +App::post('/v1/project/variables') + ->desc('Create variable') ->groups(['api']) ->label('scope', 'projects.write') ->label('audits.event', 'variable.create') @@ -362,8 +363,8 @@ Http::post('/v1/project/variables') ->dynamic($variable, Response::MODEL_VARIABLE); }); -Http::get('/v1/project/variables') - ->desc('List Variables') +App::get('/v1/project/variables') + ->desc('List variables') ->groups(['api']) ->label('scope', 'projects.read') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) @@ -387,8 +388,8 @@ Http::get('/v1/project/variables') ]), Response::MODEL_VARIABLE_LIST); }); -Http::get('/v1/project/variables/:variableId') - ->desc('Get Variable') +App::get('/v1/project/variables/:variableId') + ->desc('Get variable') ->groups(['api']) ->label('scope', 'projects.read') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) @@ -411,8 +412,8 @@ Http::get('/v1/project/variables/:variableId') $response->dynamic($variable, Response::MODEL_VARIABLE); }); -Http::put('/v1/project/variables/:variableId') - ->desc('Update Variable') +App::put('/v1/project/variables/:variableId') + ->desc('Update variable') ->groups(['api']) ->label('scope', 'projects.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) @@ -457,8 +458,8 @@ Http::put('/v1/project/variables/:variableId') $response->dynamic($variable, Response::MODEL_VARIABLE); }); -Http::delete('/v1/project/variables/:variableId') - ->desc('Delete Variable') +App::delete('/v1/project/variables/:variableId') + ->desc('Delete variable') ->groups(['api']) ->label('scope', 'projects.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 8897752f2e..934793410b 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -13,16 +13,14 @@ use Appwrite\Network\Validator\Origin; use Appwrite\Template\Template; use Appwrite\Utopia\Database\Validator\ProjectId; use Appwrite\Utopia\Database\Validator\Queries\Projects; -use Appwrite\Utopia\Queue\Connections; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use PHPMailer\PHPMailer\PHPMailer; use Utopia\Abuse\Adapters\Database\TimeLimit; +use Utopia\App; use Utopia\Audit\Audit; use Utopia\Cache\Cache; use Utopia\Config\Config; -use Utopia\Database\Adapter\MariaDB; -use Utopia\Database\Adapter\MySQL; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; @@ -32,25 +30,24 @@ 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\Datetime as DatetimeValidator; use Utopia\Database\Validator\UID; use Utopia\Domains\Validator\PublicDomain; use Utopia\DSN\DSN; -use Utopia\Http\Http; -use Utopia\Http\Validator\ArrayList; -use Utopia\Http\Validator\Boolean; -use Utopia\Http\Validator\Hostname; -use Utopia\Http\Validator\Integer; -use Utopia\Http\Validator\Multiple; -use Utopia\Http\Validator\Range; -use Utopia\Http\Validator\Text; -use Utopia\Http\Validator\URL; -use Utopia\Http\Validator\WhiteList; use Utopia\Locale\Locale; +use Utopia\Pools\Group; use Utopia\System\System; +use Utopia\Validator\ArrayList; +use Utopia\Validator\Boolean; +use Utopia\Validator\Hostname; +use Utopia\Validator\Integer; +use Utopia\Validator\Multiple; +use Utopia\Validator\Range; +use Utopia\Validator\Text; +use Utopia\Validator\URL; +use Utopia\Validator\WhiteList; -Http::init() +App::init() ->groups(['projects']) ->inject('project') ->action(function (Document $project) { @@ -59,7 +56,7 @@ Http::init() } }); -Http::post('/v1/projects') +App::post('/v1/projects') ->desc('Create project') ->groups(['api', 'projects']) ->label('audits.event', 'projects.create') @@ -89,9 +86,8 @@ Http::post('/v1/projects') ->inject('cache') ->inject('pools') ->inject('hooks') - ->inject('authorization') - ->inject('connections') - ->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Request $request, Response $response, Database $dbForConsole, Cache $cache, array $pools, Hooks $hooks, Authorization $authorization, Connections $connections) { + ->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Request $request, Response $response, Database $dbForConsole, Cache $cache, Group $pools, Hooks $hooks) { + $team = $dbForConsole->getDocument('teams', $teamId); if ($team->isEmpty()) { @@ -193,21 +189,8 @@ Http::post('/v1/projects') $dsn = new DSN('mysql://' . $dsn); } - $pool = $pools['pools-database-' . $dsn->getHost()]['pool']; - $connectionDsn = $pools['pools-database-' . $dsn->getHost()]['dsn']; - $connection = $pool->get(); - $connections->add($connection, $pool); - - $adapter = match ($connectionDsn->getScheme()) { - 'mariadb' => new MariaDB($connection), - 'mysql' => new MySQL($connection), - default => null - }; - - $adapter->setDatabase($connectionDsn->getPath()); - + $adapter = $pools->get($dsn->getHost())->pop()->getResource(); $dbForProject = new Database($adapter, $cache); - $dbForProject->setAuthorization($authorization); if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $dbForProject @@ -225,8 +208,10 @@ Http::post('/v1/projects') $audit = new Audit($dbForProject); $audit->setup(); + $abuse = new TimeLimit('', 0, 1, $dbForProject); $abuse->setup(); + /** @var array $collections */ $collections = Config::getParam('collections', [])['projects'] ?? []; @@ -249,17 +234,17 @@ Http::post('/v1/projects') // Collection already exists } } - $connections->reclaim(); + // Hook allowing instant project mirroring during migration // Outside of migration, hook is not registered and has no effect - $hooks->trigger('afterProjectCreation', [$project, $pools, $cache]); + $hooks->trigger('afterProjectCreation', [ $project, $pools, $cache ]); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($project, Response::MODEL_PROJECT); }); -Http::get('/v1/projects') +App::get('/v1/projects') ->desc('List projects') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -274,6 +259,7 @@ Http::get('/v1/projects') ->inject('response') ->inject('dbForConsole') ->action(function (array $queries, string $search, Response $response, Database $dbForConsole) { + try { $queries = Query::parseQueries($queries); } catch (QueryException $e) { @@ -311,7 +297,7 @@ Http::get('/v1/projects') ]), Response::MODEL_PROJECT_LIST); }); -Http::get('/v1/projects/:projectId') +App::get('/v1/projects/:projectId') ->desc('Get project') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -325,6 +311,7 @@ Http::get('/v1/projects/:projectId') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -334,7 +321,7 @@ Http::get('/v1/projects/:projectId') $response->dynamic($project, Response::MODEL_PROJECT); }); -Http::patch('/v1/projects/:projectId') +App::patch('/v1/projects/:projectId') ->desc('Update project') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -358,34 +345,31 @@ Http::patch('/v1/projects/:projectId') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $name, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } - $project = $dbForConsole->updateDocument( - 'projects', - $project->getId(), - $project - ->setAttribute('name', $name) - ->setAttribute('description', $description) - ->setAttribute('logo', $logo) - ->setAttribute('url', $url) - ->setAttribute('legalName', $legalName) - ->setAttribute('legalCountry', $legalCountry) - ->setAttribute('legalState', $legalState) - ->setAttribute('legalCity', $legalCity) - ->setAttribute('legalAddress', $legalAddress) - ->setAttribute('legalTaxId', $legalTaxId) - ->setAttribute('search', implode(' ', [$projectId, $name])) - ); + $project = $dbForConsole->updateDocument('projects', $project->getId(), $project + ->setAttribute('name', $name) + ->setAttribute('description', $description) + ->setAttribute('logo', $logo) + ->setAttribute('url', $url) + ->setAttribute('legalName', $legalName) + ->setAttribute('legalCountry', $legalCountry) + ->setAttribute('legalState', $legalState) + ->setAttribute('legalCity', $legalCity) + ->setAttribute('legalAddress', $legalAddress) + ->setAttribute('legalTaxId', $legalTaxId) + ->setAttribute('search', implode(' ', [$projectId, $name]))); $response->dynamic($project, Response::MODEL_PROJECT); }); -Http::patch('/v1/projects/:projectId/team') - ->desc('Update Project Team') +App::patch('/v1/projects/:projectId/team') + ->desc('Update project team') ->groups(['api', 'projects']) ->label('scope', 'projects.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) @@ -399,6 +383,7 @@ Http::patch('/v1/projects/:projectId/team') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $teamId, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); $team = $dbForConsole->getDocument('teams', $teamId); @@ -451,7 +436,7 @@ Http::patch('/v1/projects/:projectId/team') $response->dynamic($project, Response::MODEL_PROJECT); }); -Http::patch('/v1/projects/:projectId/service') +App::patch('/v1/projects/:projectId/service') ->desc('Update service status') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -467,6 +452,7 @@ Http::patch('/v1/projects/:projectId/service') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $service, bool $status, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -481,7 +467,7 @@ Http::patch('/v1/projects/:projectId/service') $response->dynamic($project, Response::MODEL_PROJECT); }); -Http::patch('/v1/projects/:projectId/service/all') +App::patch('/v1/projects/:projectId/service/all') ->desc('Update all service status') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -496,6 +482,7 @@ Http::patch('/v1/projects/:projectId/service/all') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, bool $status, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -514,7 +501,7 @@ Http::patch('/v1/projects/:projectId/service/all') $response->dynamic($project, Response::MODEL_PROJECT); }); -Http::patch('/v1/projects/:projectId/api') +App::patch('/v1/projects/:projectId/api') ->desc('Update API status') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -530,6 +517,7 @@ Http::patch('/v1/projects/:projectId/api') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $api, bool $status, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -544,7 +532,7 @@ Http::patch('/v1/projects/:projectId/api') $response->dynamic($project, Response::MODEL_PROJECT); }); -Http::patch('/v1/projects/:projectId/api/all') +App::patch('/v1/projects/:projectId/api/all') ->desc('Update all API status') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -559,6 +547,7 @@ Http::patch('/v1/projects/:projectId/api/all') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, bool $status, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -577,7 +566,7 @@ Http::patch('/v1/projects/:projectId/api/all') $response->dynamic($project, Response::MODEL_PROJECT); }); -Http::patch('/v1/projects/:projectId/oauth2') +App::patch('/v1/projects/:projectId/oauth2') ->desc('Update project OAuth2') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -595,6 +584,7 @@ Http::patch('/v1/projects/:projectId/oauth2') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $provider, ?string $appId, ?string $secret, ?bool $enabled, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -620,7 +610,7 @@ Http::patch('/v1/projects/:projectId/oauth2') $response->dynamic($project, Response::MODEL_PROJECT); }); -Http::patch('/v1/projects/:projectId/auth/session-alerts') +App::patch('/v1/projects/:projectId/auth/session-alerts') ->desc('Update project sessions emails') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -651,7 +641,7 @@ Http::patch('/v1/projects/:projectId/auth/session-alerts') $response->dynamic($project, Response::MODEL_PROJECT); }); -Http::patch('/v1/projects/:projectId/auth/limit') +App::patch('/v1/projects/:projectId/auth/limit') ->desc('Update project users limit') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -666,6 +656,7 @@ Http::patch('/v1/projects/:projectId/auth/limit') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -675,17 +666,13 @@ Http::patch('/v1/projects/:projectId/auth/limit') $auths = $project->getAttribute('auths', []); $auths['limit'] = $limit; - $dbForConsole->updateDocument( - 'projects', - $project->getId(), - $project - ->setAttribute('auths', $auths) - ); + $dbForConsole->updateDocument('projects', $project->getId(), $project + ->setAttribute('auths', $auths)); $response->dynamic($project, Response::MODEL_PROJECT); }); -Http::patch('/v1/projects/:projectId/auth/duration') +App::patch('/v1/projects/:projectId/auth/duration') ->desc('Update project authentication duration') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -700,6 +687,7 @@ Http::patch('/v1/projects/:projectId/auth/duration') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, int $duration, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -709,17 +697,13 @@ Http::patch('/v1/projects/:projectId/auth/duration') $auths = $project->getAttribute('auths', []); $auths['duration'] = $duration; - $dbForConsole->updateDocument( - 'projects', - $project->getId(), - $project - ->setAttribute('auths', $auths) - ); + $dbForConsole->updateDocument('projects', $project->getId(), $project + ->setAttribute('auths', $auths)); $response->dynamic($project, Response::MODEL_PROJECT); }); -Http::patch('/v1/projects/:projectId/auth/:method') +App::patch('/v1/projects/:projectId/auth/:method') ->desc('Update project auth method status. Use this endpoint to enable or disable a given auth method for this project.') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -735,9 +719,10 @@ Http::patch('/v1/projects/:projectId/auth/:method') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $method, bool $status, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); - $authConfig = Config::getParam('auth')[$method] ?? []; - $authKey = $authConfig['key'] ?? ''; + $auth = Config::getParam('auth')[$method] ?? []; + $authKey = $auth['key'] ?? ''; $status = ($status === '1' || $status === 'true' || $status === 1 || $status === true); if ($project->isEmpty()) { @@ -752,7 +737,7 @@ Http::patch('/v1/projects/:projectId/auth/:method') $response->dynamic($project, Response::MODEL_PROJECT); }); -Http::patch('/v1/projects/:projectId/auth/password-history') +App::patch('/v1/projects/:projectId/auth/password-history') ->desc('Update authentication password history. Use this endpoint to set the number of password history to save and 0 to disable password history.') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -767,6 +752,7 @@ Http::patch('/v1/projects/:projectId/auth/password-history') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -776,17 +762,13 @@ Http::patch('/v1/projects/:projectId/auth/password-history') $auths = $project->getAttribute('auths', []); $auths['passwordHistory'] = $limit; - $dbForConsole->updateDocument( - 'projects', - $project->getId(), - $project - ->setAttribute('auths', $auths) - ); + $dbForConsole->updateDocument('projects', $project->getId(), $project + ->setAttribute('auths', $auths)); $response->dynamic($project, Response::MODEL_PROJECT); }); -Http::patch('/v1/projects/:projectId/auth/password-dictionary') +App::patch('/v1/projects/:projectId/auth/password-dictionary') ->desc('Update authentication password dictionary status. Use this endpoint to enable or disable the dicitonary check for user password') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -801,6 +783,7 @@ Http::patch('/v1/projects/:projectId/auth/password-dictionary') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, bool $enabled, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -810,17 +793,13 @@ Http::patch('/v1/projects/:projectId/auth/password-dictionary') $auths = $project->getAttribute('auths', []); $auths['passwordDictionary'] = $enabled; - $dbForConsole->updateDocument( - 'projects', - $project->getId(), - $project - ->setAttribute('auths', $auths) - ); + $dbForConsole->updateDocument('projects', $project->getId(), $project + ->setAttribute('auths', $auths)); $response->dynamic($project, Response::MODEL_PROJECT); }); -Http::patch('/v1/projects/:projectId/auth/personal-data') +App::patch('/v1/projects/:projectId/auth/personal-data') ->desc('Enable or disable checking user passwords for similarity with their personal data.') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -835,6 +814,7 @@ Http::patch('/v1/projects/:projectId/auth/personal-data') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, bool $enabled, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -844,17 +824,13 @@ Http::patch('/v1/projects/:projectId/auth/personal-data') $auths = $project->getAttribute('auths', []); $auths['personalDataCheck'] = $enabled; - $dbForConsole->updateDocument( - 'projects', - $project->getId(), - $project - ->setAttribute('auths', $auths) - ); + $dbForConsole->updateDocument('projects', $project->getId(), $project + ->setAttribute('auths', $auths)); $response->dynamic($project, Response::MODEL_PROJECT); }); -Http::patch('/v1/projects/:projectId/auth/max-sessions') +App::patch('/v1/projects/:projectId/auth/max-sessions') ->desc('Update project user sessions limit') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -869,6 +845,7 @@ Http::patch('/v1/projects/:projectId/auth/max-sessions') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -878,17 +855,13 @@ Http::patch('/v1/projects/:projectId/auth/max-sessions') $auths = $project->getAttribute('auths', []); $auths['maxSessions'] = $limit; - $dbForConsole->updateDocument( - 'projects', - $project->getId(), - $project - ->setAttribute('auths', $auths) - ); + $dbForConsole->updateDocument('projects', $project->getId(), $project + ->setAttribute('auths', $auths)); $response->dynamic($project, Response::MODEL_PROJECT); }); -Http::patch('/v1/projects/:projectId/auth/mock-numbers') +App::patch('/v1/projects/:projectId/auth/mock-numbers') ->desc('Update the mock numbers for the project') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -927,7 +900,7 @@ Http::patch('/v1/projects/:projectId/auth/mock-numbers') $response->dynamic($project, Response::MODEL_PROJECT); }); -Http::delete('/v1/projects/:projectId') +App::delete('/v1/projects/:projectId') ->desc('Delete project') ->groups(['api', 'projects']) ->label('audits.event', 'projects.delete') @@ -963,7 +936,7 @@ Http::delete('/v1/projects/:projectId') // Webhooks -Http::post('/v1/projects/:projectId/webhooks') +App::post('/v1/projects/:projectId/webhooks') ->desc('Create webhook') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -984,13 +957,14 @@ Http::post('/v1/projects/:projectId/webhooks') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $name, bool $enabled, array $events, string $url, bool $security, string $httpUser, string $httpPass, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } - $security = (bool)filter_var($security, FILTER_VALIDATE_BOOLEAN); + $security = (bool) filter_var($security, FILTER_VALIDATE_BOOLEAN); $webhook = new Document([ '$id' => ID::unique(), @@ -1020,7 +994,7 @@ Http::post('/v1/projects/:projectId/webhooks') ->dynamic($webhook, Response::MODEL_WEBHOOK); }); -Http::get('/v1/projects/:projectId/webhooks') +App::get('/v1/projects/:projectId/webhooks') ->desc('List webhooks') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -1034,6 +1008,7 @@ Http::get('/v1/projects/:projectId/webhooks') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1051,7 +1026,7 @@ Http::get('/v1/projects/:projectId/webhooks') ]), Response::MODEL_WEBHOOK_LIST); }); -Http::get('/v1/projects/:projectId/webhooks/:webhookId') +App::get('/v1/projects/:projectId/webhooks/:webhookId') ->desc('Get webhook') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -1066,6 +1041,7 @@ Http::get('/v1/projects/:projectId/webhooks/:webhookId') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1077,14 +1053,14 @@ Http::get('/v1/projects/:projectId/webhooks/:webhookId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($webhook->isEmpty()) { + if ($webhook === false || $webhook->isEmpty()) { throw new Exception(Exception::WEBHOOK_NOT_FOUND); } $response->dynamic($webhook, Response::MODEL_WEBHOOK); }); -Http::put('/v1/projects/:projectId/webhooks/:webhookId') +App::put('/v1/projects/:projectId/webhooks/:webhookId') ->desc('Update webhook') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1106,6 +1082,7 @@ Http::put('/v1/projects/:projectId/webhooks/:webhookId') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $webhookId, string $name, bool $enabled, array $events, string $url, bool $security, string $httpUser, string $httpPass, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1119,7 +1096,7 @@ Http::put('/v1/projects/:projectId/webhooks/:webhookId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($webhook->isEmpty()) { + if ($webhook === false || $webhook->isEmpty()) { throw new Exception(Exception::WEBHOOK_NOT_FOUND); } @@ -1142,7 +1119,7 @@ Http::put('/v1/projects/:projectId/webhooks/:webhookId') $response->dynamic($webhook, Response::MODEL_WEBHOOK); }); -Http::patch('/v1/projects/:projectId/webhooks/:webhookId/signature') +App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature') ->desc('Update webhook signature key') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1157,6 +1134,7 @@ Http::patch('/v1/projects/:projectId/webhooks/:webhookId/signature') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1168,7 +1146,7 @@ Http::patch('/v1/projects/:projectId/webhooks/:webhookId/signature') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($webhook->isEmpty()) { + if ($webhook === false || $webhook->isEmpty()) { throw new Exception(Exception::WEBHOOK_NOT_FOUND); } @@ -1180,7 +1158,7 @@ Http::patch('/v1/projects/:projectId/webhooks/:webhookId/signature') $response->dynamic($webhook, Response::MODEL_WEBHOOK); }); -Http::delete('/v1/projects/:projectId/webhooks/:webhookId') +App::delete('/v1/projects/:projectId/webhooks/:webhookId') ->desc('Delete webhook') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1194,6 +1172,7 @@ Http::delete('/v1/projects/:projectId/webhooks/:webhookId') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1205,7 +1184,7 @@ Http::delete('/v1/projects/:projectId/webhooks/:webhookId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($webhook->isEmpty()) { + if ($webhook === false || $webhook->isEmpty()) { throw new Exception(Exception::WEBHOOK_NOT_FOUND); } @@ -1218,7 +1197,7 @@ Http::delete('/v1/projects/:projectId/webhooks/:webhookId') // Keys -Http::post('/v1/projects/:projectId/keys') +App::post('/v1/projects/:projectId/keys') ->desc('Create key') ->groups(['api', 'projects']) ->label('scope', 'keys.write') @@ -1235,6 +1214,7 @@ Http::post('/v1/projects/:projectId/keys') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1267,7 +1247,7 @@ Http::post('/v1/projects/:projectId/keys') ->dynamic($key, Response::MODEL_KEY); }); -Http::get('/v1/projects/:projectId/keys') +App::get('/v1/projects/:projectId/keys') ->desc('List keys') ->groups(['api', 'projects']) ->label('scope', 'keys.read') @@ -1281,6 +1261,7 @@ Http::get('/v1/projects/:projectId/keys') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1298,7 +1279,7 @@ Http::get('/v1/projects/:projectId/keys') ]), Response::MODEL_KEY_LIST); }); -Http::get('/v1/projects/:projectId/keys/:keyId') +App::get('/v1/projects/:projectId/keys/:keyId') ->desc('Get key') ->groups(['api', 'projects']) ->label('scope', 'keys.read') @@ -1313,6 +1294,7 @@ Http::get('/v1/projects/:projectId/keys/:keyId') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1324,14 +1306,14 @@ Http::get('/v1/projects/:projectId/keys/:keyId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($key->isEmpty()) { + if ($key === false || $key->isEmpty()) { throw new Exception(Exception::KEY_NOT_FOUND); } $response->dynamic($key, Response::MODEL_KEY); }); -Http::put('/v1/projects/:projectId/keys/:keyId') +App::put('/v1/projects/:projectId/keys/:keyId') ->desc('Update key') ->groups(['api', 'projects']) ->label('scope', 'keys.write') @@ -1349,6 +1331,7 @@ Http::put('/v1/projects/:projectId/keys/:keyId') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $keyId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1360,7 +1343,7 @@ Http::put('/v1/projects/:projectId/keys/:keyId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($key->isEmpty()) { + if ($key === false || $key->isEmpty()) { throw new Exception(Exception::KEY_NOT_FOUND); } @@ -1376,7 +1359,7 @@ Http::put('/v1/projects/:projectId/keys/:keyId') $response->dynamic($key, Response::MODEL_KEY); }); -Http::delete('/v1/projects/:projectId/keys/:keyId') +App::delete('/v1/projects/:projectId/keys/:keyId') ->desc('Delete key') ->groups(['api', 'projects']) ->label('scope', 'keys.write') @@ -1390,6 +1373,7 @@ Http::delete('/v1/projects/:projectId/keys/:keyId') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1401,7 +1385,7 @@ Http::delete('/v1/projects/:projectId/keys/:keyId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($key->isEmpty()) { + if ($key === false || $key->isEmpty()) { throw new Exception(Exception::KEY_NOT_FOUND); } @@ -1414,7 +1398,7 @@ Http::delete('/v1/projects/:projectId/keys/:keyId') // JWT Keys -Http::post('/v1/projects/:projectId/jwts') +App::post('/v1/projects/:projectId/jwts') ->groups(['api', 'projects']) ->desc('Create JWT') ->label('scope', 'projects.write') @@ -1449,7 +1433,7 @@ Http::post('/v1/projects/:projectId/jwts') // Platforms -Http::post('/v1/projects/:projectId/platforms') +App::post('/v1/projects/:projectId/platforms') ->desc('Create platform') ->groups(['api', 'projects']) ->label('audits.event', 'platforms.create') @@ -1500,7 +1484,7 @@ Http::post('/v1/projects/:projectId/platforms') ->dynamic($platform, Response::MODEL_PLATFORM); }); -Http::get('/v1/projects/:projectId/platforms') +App::get('/v1/projects/:projectId/platforms') ->desc('List platforms') ->groups(['api', 'projects']) ->label('scope', 'platforms.read') @@ -1514,6 +1498,7 @@ Http::get('/v1/projects/:projectId/platforms') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1531,7 +1516,7 @@ Http::get('/v1/projects/:projectId/platforms') ]), Response::MODEL_PLATFORM_LIST); }); -Http::get('/v1/projects/:projectId/platforms/:platformId') +App::get('/v1/projects/:projectId/platforms/:platformId') ->desc('Get platform') ->groups(['api', 'projects']) ->label('scope', 'platforms.read') @@ -1546,6 +1531,7 @@ Http::get('/v1/projects/:projectId/platforms/:platformId') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1557,14 +1543,14 @@ Http::get('/v1/projects/:projectId/platforms/:platformId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($platform->isEmpty()) { + if ($platform === false || $platform->isEmpty()) { throw new Exception(Exception::PLATFORM_NOT_FOUND); } $response->dynamic($platform, Response::MODEL_PLATFORM); }); -Http::put('/v1/projects/:projectId/platforms/:platformId') +App::put('/v1/projects/:projectId/platforms/:platformId') ->desc('Update platform') ->groups(['api', 'projects']) ->label('scope', 'platforms.write') @@ -1594,7 +1580,7 @@ Http::put('/v1/projects/:projectId/platforms/:platformId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($platform->isEmpty()) { + if ($platform === false || $platform->isEmpty()) { throw new Exception(Exception::PLATFORM_NOT_FOUND); } @@ -1611,7 +1597,7 @@ Http::put('/v1/projects/:projectId/platforms/:platformId') $response->dynamic($platform, Response::MODEL_PLATFORM); }); -Http::delete('/v1/projects/:projectId/platforms/:platformId') +App::delete('/v1/projects/:projectId/platforms/:platformId') ->desc('Delete platform') ->groups(['api', 'projects']) ->label('audits.event', 'platforms.delete') @@ -1626,6 +1612,7 @@ Http::delete('/v1/projects/:projectId/platforms/:platformId') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1637,7 +1624,7 @@ Http::delete('/v1/projects/:projectId/platforms/:platformId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($platform->isEmpty()) { + if ($platform === false || $platform->isEmpty()) { throw new Exception(Exception::PLATFORM_NOT_FOUND); } @@ -1650,7 +1637,7 @@ Http::delete('/v1/projects/:projectId/platforms/:platformId') // CUSTOM SMTP and Templates -Http::patch('/v1/projects/:projectId/smtp') +App::patch('/v1/projects/:projectId/smtp') ->desc('Update SMTP') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1673,6 +1660,7 @@ Http::patch('/v1/projects/:projectId/smtp') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, bool $enabled, string $senderName, string $senderEmail, string $replyTo, string $host, int $port, string $username, string $password, string $secure, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1739,7 +1727,7 @@ Http::patch('/v1/projects/:projectId/smtp') $response->dynamic($project, Response::MODEL_PROJECT); }); -Http::post('/v1/projects/:projectId/smtp/tests') +App::post('/v1/projects/:projectId/smtp/tests') ->desc('Create SMTP test') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1798,7 +1786,7 @@ Http::post('/v1/projects/:projectId/smtp/tests') return $response->noContent(); }); -Http::get('/v1/projects/:projectId/templates/sms/:type/:locale') +App::get('/v1/projects/:projectId/templates/sms/:type/:locale') ->desc('Get custom SMS template') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1814,6 +1802,7 @@ Http::get('/v1/projects/:projectId/templates/sms/:type/:locale') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) { + throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED); $project = $dbForConsole->getDocument('projects', $projectId); @@ -1823,7 +1812,7 @@ Http::get('/v1/projects/:projectId/templates/sms/:type/:locale') } $templates = $project->getAttribute('templates', []); - $template = $templates['sms.' . $type . '-' . $locale] ?? null; + $template = $templates['sms.' . $type . '-' . $locale] ?? null; if (is_null($template)) { $template = [ @@ -1838,7 +1827,7 @@ Http::get('/v1/projects/:projectId/templates/sms/:type/:locale') }); -Http::get('/v1/projects/:projectId/templates/email/:type/:locale') +App::get('/v1/projects/:projectId/templates/email/:type/:locale') ->desc('Get custom email template') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1854,6 +1843,7 @@ Http::get('/v1/projects/:projectId/templates/email/:type/:locale') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1861,7 +1851,7 @@ Http::get('/v1/projects/:projectId/templates/email/:type/:locale') } $templates = $project->getAttribute('templates', []); - $template = $templates['email.' . $type . '-' . $locale] ?? null; + $template = $templates['email.' . $type . '-' . $locale] ?? null; $localeObj = new Locale($locale); if (is_null($template)) { @@ -1869,7 +1859,7 @@ Http::get('/v1/projects/:projectId/templates/email/:type/:locale') $message ->setParam('{{hello}}', $localeObj->getText("emails.{$type}.hello")) ->setParam('{{footer}}', $localeObj->getText("emails.{$type}.footer")) - ->setParam('{{body}}', $localeObj->getText('emails.' . $type . '.body'), escape: false) + ->setParam('{{body}}', $localeObj->getText('emails.' . $type . '.body'), escapeHtml: false) ->setParam('{{thanks}}', $localeObj->getText("emails.{$type}.thanks")) ->setParam('{{signature}}', $localeObj->getText("emails.{$type}.signature")) ->setParam('{{direction}}', $localeObj->getText('settings.direction')); @@ -1889,7 +1879,7 @@ Http::get('/v1/projects/:projectId/templates/email/:type/:locale') $response->dynamic(new Document($template), Response::MODEL_EMAIL_TEMPLATE); }); -Http::patch('/v1/projects/:projectId/templates/sms/:type/:locale') +App::patch('/v1/projects/:projectId/templates/sms/:type/:locale') ->desc('Update custom SMS template') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1906,6 +1896,7 @@ Http::patch('/v1/projects/:projectId/templates/sms/:type/:locale') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $type, string $locale, string $message, Response $response, Database $dbForConsole) { + throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED); $project = $dbForConsole->getDocument('projects', $projectId); @@ -1928,7 +1919,7 @@ Http::patch('/v1/projects/:projectId/templates/sms/:type/:locale') ]), Response::MODEL_SMS_TEMPLATE); }); -Http::patch('/v1/projects/:projectId/templates/email/:type/:locale') +App::patch('/v1/projects/:projectId/templates/email/:type/:locale') ->desc('Update custom email templates') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1949,6 +1940,7 @@ Http::patch('/v1/projects/:projectId/templates/email/:type/:locale') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $type, string $locale, string $subject, string $message, string $senderName, string $senderEmail, string $replyTo, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -1977,7 +1969,7 @@ Http::patch('/v1/projects/:projectId/templates/email/:type/:locale') ]), Response::MODEL_EMAIL_TEMPLATE); }); -Http::delete('/v1/projects/:projectId/templates/sms/:type/:locale') +App::delete('/v1/projects/:projectId/templates/sms/:type/:locale') ->desc('Reset custom SMS template') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1993,6 +1985,7 @@ Http::delete('/v1/projects/:projectId/templates/sms/:type/:locale') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) { + throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED); $project = $dbForConsole->getDocument('projects', $projectId); @@ -2002,7 +1995,7 @@ Http::delete('/v1/projects/:projectId/templates/sms/:type/:locale') } $templates = $project->getAttribute('templates', []); - $template = $templates['sms.' . $type . '-' . $locale] ?? null; + $template = $templates['sms.' . $type . '-' . $locale] ?? null; if (is_null($template)) { throw new Exception(Exception::PROJECT_TEMPLATE_DEFAULT_DELETION); @@ -2019,7 +2012,7 @@ Http::delete('/v1/projects/:projectId/templates/sms/:type/:locale') ]), Response::MODEL_SMS_TEMPLATE); }); -Http::delete('/v1/projects/:projectId/templates/email/:type/:locale') +App::delete('/v1/projects/:projectId/templates/email/:type/:locale') ->desc('Reset custom email template') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -2035,6 +2028,7 @@ Http::delete('/v1/projects/:projectId/templates/email/:type/:locale') ->inject('response') ->inject('dbForConsole') ->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) { + $project = $dbForConsole->getDocument('projects', $projectId); if ($project->isEmpty()) { @@ -2042,7 +2036,7 @@ Http::delete('/v1/projects/:projectId/templates/email/:type/:locale') } $templates = $project->getAttribute('templates', []); - $template = $templates['email.' . $type . '-' . $locale] ?? null; + $template = $templates['email.' . $type . '-' . $locale] ?? null; if (is_null($template)) { throw new Exception(Exception::PROJECT_TEMPLATE_DEFAULT_DELETION); diff --git a/app/controllers/api/proxy.php b/app/controllers/api/proxy.php index d4283691ae..84484a7209 100644 --- a/app/controllers/api/proxy.php +++ b/app/controllers/api/proxy.php @@ -7,6 +7,7 @@ use Appwrite\Extend\Exception; use Appwrite\Network\Validator\CNAME; use Appwrite\Utopia\Database\Validator\Queries\Rules; use Appwrite\Utopia\Response; +use Utopia\App; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Query as QueryException; @@ -14,14 +15,13 @@ use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Database\Validator\UID; use Utopia\Domains\Domain; -use Utopia\Http\Http; -use Utopia\Http\Validator\Domain as ValidatorDomain; -use Utopia\Http\Validator\Text; -use Utopia\Http\Validator\WhiteList; use Utopia\Logger\Log; use Utopia\System\System; +use Utopia\Validator\Domain as ValidatorDomain; +use Utopia\Validator\Text; +use Utopia\Validator\WhiteList; -Http::post('/v1/proxy/rules') +App::post('/v1/proxy/rules') ->groups(['api', 'proxy']) ->desc('Create rule') ->label('scope', 'rules.write') @@ -63,7 +63,7 @@ Http::post('/v1/proxy/rules') Query::equal('domain', [$domain]), ]); - if (!$document->isEmpty()) { + if ($document && !$document->isEmpty()) { if ($document->getAttribute('projectId') === $project->getId()) { $resourceType = $document->getAttribute('resourceType'); $resourceId = $document->getAttribute('resourceId'); @@ -147,7 +147,7 @@ Http::post('/v1/proxy/rules') ->dynamic($rule, Response::MODEL_PROXY_RULE); }); -Http::get('/v1/proxy/rules') +App::get('/v1/proxy/rules') ->groups(['api', 'proxy']) ->desc('List rules') ->label('scope', 'rules.read') @@ -210,7 +210,7 @@ Http::get('/v1/proxy/rules') ]), Response::MODEL_PROXY_RULE_LIST); }); -Http::get('/v1/proxy/rules/:ruleId') +App::get('/v1/proxy/rules/:ruleId') ->groups(['api', 'proxy']) ->desc('Get rule') ->label('scope', 'rules.read') @@ -239,7 +239,7 @@ Http::get('/v1/proxy/rules/:ruleId') $response->dynamic($rule, Response::MODEL_PROXY_RULE); }); -Http::delete('/v1/proxy/rules/:ruleId') +App::delete('/v1/proxy/rules/:ruleId') ->groups(['api', 'proxy']) ->desc('Delete rule') ->label('scope', 'rules.write') @@ -276,8 +276,8 @@ Http::delete('/v1/proxy/rules/:ruleId') $response->noContent(); }); -Http::patch('/v1/proxy/rules/:ruleId/verification') - ->desc('Update Rule Verification Status') +App::patch('/v1/proxy/rules/:ruleId/verification') + ->desc('Update rule verification status') ->groups(['api', 'proxy']) ->label('scope', 'rules.write') ->label('event', 'rules.[ruleId].update') diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index d7357ada19..4e30832a67 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -11,8 +11,8 @@ use Appwrite\OpenSSL\OpenSSL; use Appwrite\Utopia\Database\Validator\CustomId; use Appwrite\Utopia\Database\Validator\Queries\Buckets; use Appwrite\Utopia\Database\Validator\Queries\Files; -use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; +use Utopia\App; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; @@ -23,16 +23,8 @@ use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; -use Utopia\Database\Validator\Authorization\Input; use Utopia\Database\Validator\Permissions; use Utopia\Database\Validator\UID; -use Utopia\Http\Http; -use Utopia\Http\Validator\ArrayList; -use Utopia\Http\Validator\Boolean; -use Utopia\Http\Validator\HexColor; -use Utopia\Http\Validator\Range; -use Utopia\Http\Validator\Text; -use Utopia\Http\Validator\WhiteList; use Utopia\Image\Image; use Utopia\Storage\Compression\Algorithms\GZIP; use Utopia\Storage\Compression\Algorithms\Zstd; @@ -43,9 +35,16 @@ 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\ArrayList; +use Utopia\Validator\Boolean; +use Utopia\Validator\HexColor; +use Utopia\Validator\Range; +use Utopia\Validator\Text; +use Utopia\Validator\WhiteList; -Http::post('/v1/storage/buckets') +App::post('/v1/storage/buckets') ->desc('Create bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.write') @@ -143,7 +142,7 @@ Http::post('/v1/storage/buckets') ->dynamic($bucket, Response::MODEL_BUCKET); }); -Http::get('/v1/storage/buckets') +App::get('/v1/storage/buckets') ->desc('List buckets') ->groups(['api', 'storage']) ->label('scope', 'buckets.read') @@ -197,7 +196,7 @@ Http::get('/v1/storage/buckets') ]), Response::MODEL_BUCKET_LIST); }); -Http::get('/v1/storage/buckets/:bucketId') +App::get('/v1/storage/buckets/:bucketId') ->desc('Get bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.read') @@ -222,7 +221,7 @@ Http::get('/v1/storage/buckets/:bucketId') $response->dynamic($bucket, Response::MODEL_BUCKET); }); -Http::put('/v1/storage/buckets/:bucketId') +App::put('/v1/storage/buckets/:bucketId') ->desc('Update bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.write') @@ -289,7 +288,7 @@ Http::put('/v1/storage/buckets/:bucketId') $response->dynamic($bucket, Response::MODEL_BUCKET); }); -Http::delete('/v1/storage/buckets/:bucketId') +App::delete('/v1/storage/buckets/:bucketId') ->desc('Delete bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.write') @@ -330,7 +329,7 @@ Http::delete('/v1/storage/buckets/:bucketId') $response->noContent(); }); -Http::post('/v1/storage/buckets/:bucketId/files') +App::post('/v1/storage/buckets/:bucketId/files') ->alias('/v1/storage/files', ['bucketId' => 'default']) ->desc('Create file') ->groups(['api', 'storage']) @@ -362,19 +361,19 @@ Http::post('/v1/storage/buckets/:bucketId/files') ->inject('mode') ->inject('deviceForFiles') ->inject('deviceForLocal') - ->inject('authorization') - ->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, string $mode, Device $deviceForFiles, Device $deviceForLocal, Authorization $authorization) { + ->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, string $mode, Device $deviceForFiles, Device $deviceForLocal) { - $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - $isAPIKey = Auth::isAppUser($authorization->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } - if (!$authorization->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) { + $validator = new Authorization(Database::PERMISSION_CREATE); + if (!$validator->isValid($bucket->getCreate())) { throw new Exception(Exception::USER_UNAUTHORIZED); } @@ -398,7 +397,7 @@ Http::post('/v1/storage/buckets/:bucketId/files') } // Users can only manage their own roles, API keys and Admin users can manage any - $roles = $authorization->getRoles(); + $roles = Authorization::getRoles(); if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) { foreach (Database::PERMISSIONS as $type) { foreach ($permissions as $permission) { @@ -411,7 +410,7 @@ Http::post('/v1/storage/buckets/:bucketId/files') $permission->getIdentifier(), $permission->getDimension() ))->toString(); - if (!$authorization->isRole($role)) { + if (!Authorization::isRole($role)) { throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')'); } } @@ -631,10 +630,11 @@ Http::post('/v1/storage/buckets/:bucketId/files') * However as with chunk upload even if we are updating, we are essentially creating a file * adding it's new chunk so we validate create permission instead of update */ - if (!$authorization->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) { + $validator = new Authorization(Database::PERMISSION_CREATE); + if (!$validator->isValid($bucket->getCreate())) { throw new Exception(Exception::USER_UNAUTHORIZED); } - $file = $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); + $file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); } } else { if ($file->isEmpty()) { @@ -669,10 +669,11 @@ Http::post('/v1/storage/buckets/:bucketId/files') * However as with chunk upload even if we are updating, we are essentially creating a file * adding it's new chunk so we validate create permission instead of update */ - if (!$authorization->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) { + $validator = new Authorization(Database::PERMISSION_CREATE); + if (!$validator->isValid($bucket->getCreate())) { throw new Exception(Exception::USER_UNAUTHORIZED); } - $file = $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); + $file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); } } @@ -689,7 +690,7 @@ Http::post('/v1/storage/buckets/:bucketId/files') ->dynamic($file, Response::MODEL_FILE); }); -Http::get('/v1/storage/buckets/:bucketId/files') +App::get('/v1/storage/buckets/:bucketId/files') ->alias('/v1/storage/files', ['bucketId' => 'default']) ->desc('List files') ->groups(['api', 'storage']) @@ -707,19 +708,19 @@ Http::get('/v1/storage/buckets/:bucketId/files') ->inject('response') ->inject('dbForProject') ->inject('mode') - ->inject('authorization') - ->action(function (string $bucketId, array $queries, string $search, Response $response, Database $dbForProject, string $mode, Authorization $authorization) { - $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + ->action(function (string $bucketId, array $queries, string $search, Response $response, Database $dbForProject, string $mode) { + $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - $isAPIKey = Auth::isAppUser($authorization->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } $fileSecurity = $bucket->getAttribute('fileSecurity', false); - $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead())); + $validator = new Authorization(Database::PERMISSION_READ); + $valid = $validator->isValid($bucket->getRead()); if (!$fileSecurity && !$valid) { throw new Exception(Exception::USER_UNAUTHORIZED); } @@ -748,7 +749,7 @@ Http::get('/v1/storage/buckets/:bucketId/files') if ($fileSecurity && !$valid) { $cursorDocument = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } else { - $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); } if ($cursorDocument->isEmpty()) { @@ -764,8 +765,8 @@ Http::get('/v1/storage/buckets/:bucketId/files') $files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries); $total = $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT); } else { - $files = $authorization->skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries)); - $total = $authorization->skip(fn () => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT)); + $files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries)); + $total = Authorization::skip(fn () => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT)); } $response->dynamic(new Document([ @@ -774,7 +775,7 @@ Http::get('/v1/storage/buckets/:bucketId/files') ]), Response::MODEL_FILE_LIST); }); -Http::get('/v1/storage/buckets/:bucketId/files/:fileId') +App::get('/v1/storage/buckets/:bucketId/files/:fileId') ->alias('/v1/storage/files/:fileId', ['bucketId' => 'default']) ->desc('Get file') ->groups(['api', 'storage']) @@ -791,19 +792,19 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId') ->inject('response') ->inject('dbForProject') ->inject('mode') - ->inject('authorization') - ->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, string $mode, Authorization $authorization) { - $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + ->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, string $mode) { + $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - $isAPIKey = Auth::isAppUser($authorization->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } $fileSecurity = $bucket->getAttribute('fileSecurity', false); - $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead())); + $validator = new Authorization(Database::PERMISSION_READ); + $valid = $validator->isValid($bucket->getRead()); if (!$fileSecurity && !$valid) { throw new Exception(Exception::USER_UNAUTHORIZED); } @@ -811,7 +812,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId') if ($fileSecurity && !$valid) { $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } else { - $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); } if ($file->isEmpty()) { @@ -821,7 +822,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId') $response->dynamic($file, Response::MODEL_FILE); }); -Http::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') +App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') ->alias('/v1/storage/files/:fileId/preview', ['bucketId' => 'default']) ->desc('Get file preview') ->groups(['api', 'storage']) @@ -856,24 +857,24 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') ->inject('mode') ->inject('deviceForFiles') ->inject('deviceForLocal') - ->inject('authorization') - ->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, string $mode, Device $deviceForFiles, Device $deviceForLocal, Authorization $authorization) { + ->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, string $mode, Device $deviceForFiles, Device $deviceForLocal) { if (!\extension_loaded('imagick')) { throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing'); } - $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - $isAPIKey = Auth::isAppUser($authorization->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } $fileSecurity = $bucket->getAttribute('fileSecurity', false); - $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead())); + $validator = new Authorization(Database::PERMISSION_READ); + $valid = $validator->isValid($bucket->getRead()); if (!$fileSecurity && !$valid) { throw new Exception(Exception::USER_UNAUTHORIZED); } @@ -881,7 +882,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') if ($fileSecurity && !$valid) { $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } else { - $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); } if ($file->isEmpty()) { @@ -993,7 +994,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') unset($image); }); -Http::get('/v1/storage/buckets/:bucketId/files/:fileId/download') +App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') ->alias('/v1/storage/files/:fileId/download', ['bucketId' => 'default']) ->desc('Get file for download') ->groups(['api', 'storage']) @@ -1012,20 +1013,20 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/download') ->inject('dbForProject') ->inject('mode') ->inject('deviceForFiles') - ->inject('authorization') - ->action(function (string $bucketId, string $fileId, Request $request, Response $response, Database $dbForProject, string $mode, Device $deviceForFiles, Authorization $authorization) { + ->action(function (string $bucketId, string $fileId, Request $request, Response $response, Database $dbForProject, string $mode, Device $deviceForFiles) { - $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - $isAPIKey = Auth::isAppUser($authorization->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } $fileSecurity = $bucket->getAttribute('fileSecurity', false); - $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead())); + $validator = new Authorization(Database::PERMISSION_READ); + $valid = $validator->isValid($bucket->getRead()); if (!$fileSecurity && !$valid) { throw new Exception(Exception::USER_UNAUTHORIZED); } @@ -1033,7 +1034,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/download') if ($fileSecurity && !$valid) { $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } else { - $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); } if ($file->isEmpty()) { @@ -1133,7 +1134,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/download') } }); -Http::get('/v1/storage/buckets/:bucketId/files/:fileId/view') +App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') ->alias('/v1/storage/files/:fileId/view', ['bucketId' => 'default']) ->desc('Get file for view') ->groups(['api', 'storage']) @@ -1152,19 +1153,19 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/view') ->inject('dbForProject') ->inject('mode') ->inject('deviceForFiles') - ->inject('authorization') - ->action(function (string $bucketId, string $fileId, Response $response, Request $request, Database $dbForProject, string $mode, Device $deviceForFiles, Authorization $authorization) { - $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + ->action(function (string $bucketId, string $fileId, Response $response, Request $request, Database $dbForProject, string $mode, Device $deviceForFiles) { + $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - $isAPIKey = Auth::isAppUser($authorization->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } $fileSecurity = $bucket->getAttribute('fileSecurity', false); - $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead())); + $validator = new Authorization(Database::PERMISSION_READ); + $valid = $validator->isValid($bucket->getRead()); if (!$fileSecurity && !$valid) { throw new Exception(Exception::USER_UNAUTHORIZED); } @@ -1172,7 +1173,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/view') if ($fileSecurity && !$valid) { $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } else { - $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); } if ($file->isEmpty()) { @@ -1285,7 +1286,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/view') } }); -Http::get('/v1/storage/buckets/:bucketId/files/:fileId/push') +App::get('/v1/storage/buckets/:bucketId/files/:fileId/push') ->desc('Get file for push notification') ->groups(['api', 'storage']) ->label('scope', 'public') @@ -1301,9 +1302,8 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/push') ->inject('project') ->inject('mode') ->inject('deviceForFiles') - ->inject('authorization') - ->action(function (string $bucketId, string $fileId, string $jwt, Response $response, Request $request, Database $dbForProject, Document $project, string $mode, Device $deviceForFiles, Authorization $authorization) { - $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + ->action(function (string $bucketId, string $fileId, string $jwt, Response $response, Request $request, Database $dbForProject, Document $project, string $mode, Device $deviceForFiles) { + $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); $decoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0); @@ -1321,14 +1321,14 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/push') throw new Exception(Exception::USER_UNAUTHORIZED); } - $isAPIKey = Auth::isAppUser($authorization->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } - $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); if ($file->isEmpty()) { throw new Exception(Exception::STORAGE_FILE_NOT_FOUND); @@ -1439,7 +1439,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/push') } }); -Http::put('/v1/storage/buckets/:bucketId/files/:fileId') +App::put('/v1/storage/buckets/:bucketId/files/:fileId') ->alias('/v1/storage/files/:fileId', ['bucketId' => 'default']) ->desc('Update file') ->groups(['api', 'storage']) @@ -1466,26 +1466,26 @@ Http::put('/v1/storage/buckets/:bucketId/files/:fileId') ->inject('user') ->inject('mode') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $bucketId, string $fileId, ?string $name, ?array $permissions, Response $response, Database $dbForProject, Document $user, string $mode, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $bucketId, string $fileId, ?string $name, ?array $permissions, Response $response, Database $dbForProject, Document $user, string $mode, Event $queueForEvents) { - $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - $isAPIKey = Auth::isAppUser($authorization->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } $fileSecurity = $bucket->getAttribute('fileSecurity', false); - $valid = $authorization->isValid(new Input(Database::PERMISSION_UPDATE, $bucket->getUpdate())); + $validator = new Authorization(Database::PERMISSION_UPDATE); + $valid = $validator->isValid($bucket->getUpdate()); if (!$fileSecurity && !$valid) { throw new Exception(Exception::USER_UNAUTHORIZED); } // Read permission should not be required for update - $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); if ($file->isEmpty()) { throw new Exception(Exception::STORAGE_FILE_NOT_FOUND); @@ -1499,7 +1499,7 @@ Http::put('/v1/storage/buckets/:bucketId/files/:fileId') ]); // Users can only manage their own roles, API keys and Admin users can manage any - $roles = $authorization->getRoles(); + $roles = Authorization::getRoles(); if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles) && !\is_null($permissions)) { foreach (Database::PERMISSIONS as $type) { foreach ($permissions as $permission) { @@ -1512,7 +1512,7 @@ Http::put('/v1/storage/buckets/:bucketId/files/:fileId') $permission->getIdentifier(), $permission->getDimension() ))->toString(); - if (!$authorization->isRole($role)) { + if (!Authorization::isRole($role)) { throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')'); } } @@ -1532,7 +1532,7 @@ Http::put('/v1/storage/buckets/:bucketId/files/:fileId') if ($fileSecurity && !$valid) { $file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file); } else { - $file = $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); + $file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); } $queueForEvents @@ -1544,8 +1544,8 @@ Http::put('/v1/storage/buckets/:bucketId/files/:fileId') $response->dynamic($file, Response::MODEL_FILE); }); -Http::delete('/v1/storage/buckets/:bucketId/files/:fileId') - ->desc('Delete File') +App::delete('/v1/storage/buckets/:bucketId/files/:fileId') + ->desc('Delete file') ->groups(['api', 'storage']) ->label('scope', 'files.write') ->label('event', 'buckets.[bucketId].files.[fileId].delete') @@ -1568,32 +1568,32 @@ Http::delete('/v1/storage/buckets/:bucketId/files/:fileId') ->inject('mode') ->inject('deviceForFiles') ->inject('queueForDeletes') - ->inject('authorization') - ->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Device $deviceForFiles, Delete $queueForDeletes, Authorization $authorization) { - $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + ->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Device $deviceForFiles, Delete $queueForDeletes) { + $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - $isAPIKey = Auth::isAppUser($authorization->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } $fileSecurity = $bucket->getAttribute('fileSecurity', false); - $valid = $authorization->isValid(new Input(Database::PERMISSION_DELETE, $bucket->getDelete())); + $validator = new Authorization(Database::PERMISSION_DELETE); + $valid = $validator->isValid($bucket->getDelete()); if (!$fileSecurity && !$valid) { throw new Exception(Exception::USER_UNAUTHORIZED); } // Read permission should not be required for delete - $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); if ($file->isEmpty()) { throw new Exception(Exception::STORAGE_FILE_NOT_FOUND); } // Make sure we don't delete the file before the document permission check occurs - if ($fileSecurity && !$valid && !$authorization->isValid(new Input(Database::PERMISSION_DELETE, $file->getDelete()))) { + if ($fileSecurity && !$valid && !$validator->isValid($file->getDelete())) { throw new Exception(Exception::USER_UNAUTHORIZED); } @@ -1617,7 +1617,7 @@ Http::delete('/v1/storage/buckets/:bucketId/files/:fileId') if ($fileSecurity && !$valid) { $deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId); } else { - $deleted = $authorization->skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $deleted = Authorization::skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId)); } if (!$deleted) { @@ -1637,7 +1637,7 @@ Http::delete('/v1/storage/buckets/:bucketId/files/:fileId') $response->noContent(); }); -Http::get('/v1/storage/usage') +App::get('/v1/storage/usage') ->desc('Get storage usage stats') ->groups(['api', 'storage']) ->label('scope', 'files.read') @@ -1650,8 +1650,7 @@ Http::get('/v1/storage/usage') ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true) ->inject('response') ->inject('dbForProject') - ->inject('authorization') - ->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) { + ->action(function (string $range, Response $response, Database $dbForProject) { $periods = Config::getParam('usage', []); $stats = $usage = []; @@ -1663,7 +1662,7 @@ Http::get('/v1/storage/usage') ]; $total = []; - $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) { + Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) { foreach ($metrics as $metric) { $result = $dbForProject->findOne('stats', [ Query::equal('metric', [$metric]), @@ -1717,7 +1716,7 @@ Http::get('/v1/storage/usage') ]), Response::MODEL_USAGE_STORAGE); }); -Http::get('/v1/storage/:bucketId/usage') +App::get('/v1/storage/:bucketId/usage') ->desc('Get bucket usage stats') ->groups(['api', 'storage']) ->label('scope', 'files.read') @@ -1731,8 +1730,7 @@ Http::get('/v1/storage/:bucketId/usage') ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true) ->inject('response') ->inject('dbForProject') - ->inject('authorization') - ->action(function (string $bucketId, string $range, Response $response, Database $dbForProject, Authorization $authorization) { + ->action(function (string $bucketId, string $range, Response $response, Database $dbForProject) { $bucket = $dbForProject->getDocument('buckets', $bucketId); @@ -1748,7 +1746,8 @@ Http::get('/v1/storage/:bucketId/usage') str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_STORAGE), ]; - $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) { + + Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) { foreach ($metrics as $metric) { $result = $dbForProject->findOne('stats', [ Query::equal('metric', [$metric]), diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 8f5fba30ab..146b5d5f81 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -1,7 +1,6 @@ desc('Create team') ->groups(['api', 'teams']) ->label('event', 'teams.[teamId].create') @@ -67,16 +66,15 @@ Http::post('/v1/teams') ->inject('user') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) { - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); - $isAppUser = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $isAppUser = Auth::isAppUser(Authorization::getRoles()); $teamId = $teamId == 'unique()' ? ID::unique() : $teamId; try { - $team = $authorization->skip(fn () => $dbForProject->createDocument('teams', new Document([ + $team = Authorization::skip(fn () => $dbForProject->createDocument('teams', new Document([ '$id' => $teamId, '$permissions' => [ Permission::read(Role::team($teamId)), @@ -135,7 +133,7 @@ Http::post('/v1/teams') ->dynamic($team, Response::MODEL_TEAM); }); -Http::get('/v1/teams') +App::get('/v1/teams') ->desc('List teams') ->groups(['api', 'teams']) ->label('scope', 'teams.read') @@ -193,7 +191,7 @@ Http::get('/v1/teams') ]), Response::MODEL_TEAM_LIST); }); -Http::get('/v1/teams/:teamId') +App::get('/v1/teams/:teamId') ->desc('Get team') ->groups(['api', 'teams']) ->label('scope', 'teams.read') @@ -220,7 +218,7 @@ Http::get('/v1/teams/:teamId') $response->dynamic($team, Response::MODEL_TEAM); }); -Http::get('/v1/teams/:teamId/prefs') +App::get('/v1/teams/:teamId/prefs') ->desc('Get team preferences') ->groups(['api', 'teams']) ->label('scope', 'teams.read') @@ -248,7 +246,7 @@ Http::get('/v1/teams/:teamId/prefs') $response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES); }); -Http::put('/v1/teams/:teamId') +App::put('/v1/teams/:teamId') ->desc('Update name') ->groups(['api', 'teams']) ->label('event', 'teams.[teamId].update') @@ -291,7 +289,7 @@ Http::put('/v1/teams/:teamId') $response->dynamic($team, Response::MODEL_TEAM); }); -Http::put('/v1/teams/:teamId/prefs') +App::put('/v1/teams/:teamId/prefs') ->desc('Update preferences') ->groups(['api', 'teams']) ->label('event', 'teams.[teamId].update.prefs') @@ -327,7 +325,7 @@ Http::put('/v1/teams/:teamId/prefs') $response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES); }); -Http::delete('/v1/teams/:teamId') +App::delete('/v1/teams/:teamId') ->desc('Delete team') ->groups(['api', 'teams']) ->label('event', 'teams.[teamId].delete') @@ -376,7 +374,7 @@ Http::delete('/v1/teams/:teamId') $response->noContent(); }); -Http::post('/v1/teams/:teamId/memberships') +App::post('/v1/teams/:teamId/memberships') ->desc('Create team membership') ->groups(['api', 'teams', 'auth']) ->label('event', 'teams.[teamId].memberships.[membershipId].create') @@ -418,10 +416,9 @@ Http::post('/v1/teams/:teamId/memberships') ->inject('queueForMails') ->inject('queueForMessaging') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, Authorization $authorization) { - $isAPIKey = Auth::isAppUser($authorization->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + ->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents) { + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); $url = htmlentities($url); if (empty($url)) { @@ -433,8 +430,8 @@ Http::post('/v1/teams/:teamId/memberships') if (empty($userId) && empty($email) && empty($phone)) { throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'At least one of userId, email, or phone is required'); } - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); - $isAppUser = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $isAppUser = Auth::isAppUser(Authorization::getRoles()); if (!$isPrivilegedUser && !$isAppUser && empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED); @@ -463,17 +460,17 @@ Http::post('/v1/teams/:teamId/memberships') $name = empty($name) ? $invitee->getAttribute('name', '') : $name; } elseif (!empty($email)) { $invitee = $dbForProject->findOne('users', [Query::equal('email', [$email])]); // Get user by email address - if ($invitee->isEmpty() && !empty($phone) && $invitee->getAttribute('phone', '') !== $phone) { + if (!empty($invitee) && !empty($phone) && $invitee->getAttribute('phone', '') !== $phone) { throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given email and phone doesn\'t match', 409); } } elseif (!empty($phone)) { $invitee = $dbForProject->findOne('users', [Query::equal('phone', [$phone])]); - if ($invitee->isEmpty() && !empty($email) && $invitee->getAttribute('email', '') !== $email) { + if (!empty($invitee) && !empty($email) && $invitee->getAttribute('email', '') !== $email) { throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given phone and email doesn\'t match', 409); } } - if (!isset($invitee) || $invitee->isEmpty()) { // Create new user if no user with same email found + if (empty($invitee)) { // Create new user if no user with same email found $limit = $project->getAttribute('auths', [])['limit'] ?? 0; if (!$isPrivilegedUser && !$isAppUser && $limit !== 0 && $project->getId() !== 'console') { // check users limit, console invites are allways allowed. @@ -494,7 +491,7 @@ Http::post('/v1/teams/:teamId/memberships') try { $userId = ID::unique(); - $invitee = $authorization->skip(fn () => $dbForProject->createDocument('users', new Document([ + $invitee = Authorization::skip(fn () => $dbForProject->createDocument('users', new Document([ '$id' => $userId, '$permissions' => [ Permission::read(Role::any()), @@ -525,13 +522,12 @@ Http::post('/v1/teams/:teamId/memberships') 'memberships' => null, 'search' => implode(' ', [$userId, $email, $name]), ]))); - } catch (Duplicate $th) { throw new Exception(Exception::USER_ALREADY_EXISTS); } } - $isOwner = $authorization->isRole('team:' . $team->getId() . '/owner'); + $isOwner = Authorization::isRole('team:' . $team->getId() . '/owner'); if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server) throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to send invitations for this team'); @@ -563,12 +559,12 @@ Http::post('/v1/teams/:teamId/memberships') if ($isPrivilegedUser || $isAppUser) { // Allow admin to create membership try { - $membership = $authorization->skip(fn () => $dbForProject->createDocument('memberships', $membership)); + $membership = Authorization::skip(fn () => $dbForProject->createDocument('memberships', $membership)); } catch (Duplicate $th) { throw new Exception(Exception::TEAM_INVITE_ALREADY_EXISTS); } - $authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1)); + Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1)); $dbForProject->purgeCachedDocument('users', $invitee->getId()); } else { @@ -590,7 +586,7 @@ Http::post('/v1/teams/:teamId/memberships') $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl'); $message - ->setParam('{{body}}', $body, escape: false) + ->setParam('{{body}}', $body, escapeHtml: false) ->setParam('{{hello}}', $locale->getText("emails.invitation.hello")) ->setParam('{{footer}}', $locale->getText("emails.invitation.footer")) ->setParam('{{thanks}}', $locale->getText("emails.invitation.thanks")) @@ -708,7 +704,7 @@ Http::post('/v1/teams/:teamId/memberships') ); }); -Http::get('/v1/teams/:teamId/memberships') +App::get('/v1/teams/:teamId/memberships') ->desc('List team memberships') ->groups(['api', 'teams']) ->label('scope', 'teams.read') @@ -811,7 +807,7 @@ Http::get('/v1/teams/:teamId/memberships') ]), Response::MODEL_MEMBERSHIP_LIST); }); -Http::get('/v1/teams/:teamId/memberships/:membershipId') +App::get('/v1/teams/:teamId/memberships/:membershipId') ->desc('Get team membership') ->groups(['api', 'teams']) ->label('scope', 'teams.read') @@ -867,7 +863,7 @@ Http::get('/v1/teams/:teamId/memberships/:membershipId') $response->dynamic($membership, Response::MODEL_MEMBERSHIP); }); -Http::patch('/v1/teams/:teamId/memberships/:membershipId') +App::patch('/v1/teams/:teamId/memberships/:membershipId') ->desc('Update membership') ->groups(['api', 'teams']) ->label('event', 'teams.[teamId].memberships.[membershipId].update') @@ -899,8 +895,7 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId') ->inject('user') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) { $team = $dbForProject->getDocument('teams', $teamId); if ($team->isEmpty()) { @@ -917,9 +912,9 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId') throw new Exception(Exception::USER_NOT_FOUND); } - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); - $isAppUser = Auth::isAppUser($authorization->getRoles()); - $isOwner = $authorization->isRole('team:' . $team->getId() . '/owner'); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $isAppUser = Auth::isAppUser(Authorization::getRoles()); + $isOwner = Authorization::isRole('team:' . $team->getId() . '/owner'); if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server) throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to modify roles'); @@ -950,7 +945,7 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId') ); }); -Http::patch('/v1/teams/:teamId/memberships/:membershipId/status') +App::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->desc('Update team membership status') ->groups(['api', 'teams']) ->label('event', 'teams.[teamId].memberships.[membershipId].update.status') @@ -976,9 +971,7 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->inject('project') ->inject('geodb') ->inject('queueForEvents') - ->inject('authorization') - ->inject('authentication') - ->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents, Authorization $authorization, Authentication $authentication) { + ->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents) { $protocol = $request->getProtocol(); $membership = $dbForProject->getDocument('memberships', $membershipId); @@ -987,7 +980,7 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status') throw new Exception(Exception::MEMBERSHIP_NOT_FOUND); } - $team = $authorization->skip(fn () => $dbForProject->getDocument('teams', $teamId)); + $team = Authorization::skip(fn () => $dbForProject->getDocument('teams', $teamId)); if ($team->isEmpty()) { throw new Exception(Exception::TEAM_NOT_FOUND); @@ -1022,11 +1015,11 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->setAttribute('confirm', true) ; - $authorization->skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true))); + Authorization::skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true))); // Log user in - $authorization->addRole(Role::user($user->getId())->toString()); + Authorization::setRole(Role::user($user->getId())->toString()); $detector = new Detector($request->getUserAgent('UNKNOWN')); $record = $geodb->get($request->getIP()); @@ -1056,13 +1049,13 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status') $dbForProject->purgeCachedDocument('users', $user->getId()); - $authorization->addRole(Role::user($userId)->toString()); + Authorization::setRole(Role::user($userId)->toString()); $membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership); $dbForProject->purgeCachedDocument('users', $user->getId()); - $authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1)); + Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1)); $queueForEvents ->setParam('userId', $user->getId()) @@ -1072,13 +1065,13 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status') if (!Config::getParam('domainVerification')) { $response - ->addHeader('X-Fallback-Cookies', \json_encode([$authentication->getCookieName() => Auth::encodeSession($user->getId(), $secret)])) + ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])) ; } $response - ->addCookie($authentication->getCookieName() . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie($authentication->getCookieName(), Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->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( @@ -1090,7 +1083,7 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status') ); }); -Http::delete('/v1/teams/:teamId/memberships/:membershipId') +App::delete('/v1/teams/:teamId/memberships/:membershipId') ->desc('Delete team membership') ->groups(['api', 'teams']) ->label('event', 'teams.[teamId].memberships.[membershipId].delete') @@ -1108,8 +1101,7 @@ Http::delete('/v1/teams/:teamId/memberships/:membershipId') ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('authorization') - ->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { + ->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject, Event $queueForEvents) { $membership = $dbForProject->getDocument('memberships', $membershipId); @@ -1144,7 +1136,7 @@ Http::delete('/v1/teams/:teamId/memberships/:membershipId') $dbForProject->purgeCachedDocument('users', $user->getId()); if ($membership->getAttribute('confirm')) { // Count only confirmed members - $authorization->skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0)); + Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0)); } $queueForEvents @@ -1157,7 +1149,7 @@ Http::delete('/v1/teams/:teamId/memberships/:membershipId') $response->noContent(); }); -Http::get('/v1/teams/:teamId/logs') +App::get('/v1/teams/:teamId/logs') ->desc('List team logs') ->groups(['api', 'teams']) ->label('scope', 'teams.read') @@ -1174,8 +1166,7 @@ Http::get('/v1/teams/:teamId/logs') ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->inject('authorization') - ->action(function (string $teamId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) { + ->action(function (string $teamId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { $team = $dbForProject->getDocument('teams', $teamId); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 9f33ee38db..571df4fdb2 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -22,6 +22,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Users; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use MaxMind\Db\Reader; +use Utopia\App; use Utopia\Audit\Audit; use Utopia\Config\Config; use Utopia\Database\Database; @@ -38,16 +39,15 @@ use Utopia\Database\Validator\Queries; use Utopia\Database\Validator\Query\Limit; use Utopia\Database\Validator\Query\Offset; use Utopia\Database\Validator\UID; -use Utopia\Http\Http; -use Utopia\Http\Validator\ArrayList; -use Utopia\Http\Validator\Assoc; -use Utopia\Http\Validator\Boolean; -use Utopia\Http\Validator\Integer; -use Utopia\Http\Validator\Range; -use Utopia\Http\Validator\Text; -use Utopia\Http\Validator\WhiteList; use Utopia\Locale\Locale; use Utopia\System\System; +use Utopia\Validator\ArrayList; +use Utopia\Validator\Assoc; +use Utopia\Validator\Boolean; +use Utopia\Validator\Integer; +use Utopia\Validator\Range; +use Utopia\Validator\Text; +use Utopia\Validator\WhiteList; /** TODO: Remove function when we move to using utopia/platform */ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks): Document @@ -63,7 +63,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e $identityWithMatchingEmail = $dbForProject->findOne('identities', [ Query::equal('providerEmail', [$email]), ]); - if (!$identityWithMatchingEmail->isEmpty()) { + if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } } @@ -140,7 +140,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e $existingTarget = $dbForProject->findOne('targets', [ Query::equal('identifier', [$email]), ]); - if (!$existingTarget->isEmpty()) { + if ($existingTarget) { $user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND); } } @@ -164,7 +164,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e $existingTarget = $dbForProject->findOne('targets', [ Query::equal('identifier', [$phone]), ]); - if (!$existingTarget->isEmpty()) { + if ($existingTarget) { $user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND); } } @@ -180,7 +180,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e return $user; } -Http::post('/v1/users') +App::post('/v1/users') ->desc('Create user') ->groups(['api', 'users']) ->label('event', 'users.[userId].create') @@ -211,7 +211,7 @@ Http::post('/v1/users') ->dynamic($user, Response::MODEL_USER); }); -Http::post('/v1/users/bcrypt') +App::post('/v1/users/bcrypt') ->desc('Create user with bcrypt password') ->groups(['api', 'users']) ->label('event', 'users.[userId].create') @@ -242,7 +242,7 @@ Http::post('/v1/users/bcrypt') ->dynamic($user, Response::MODEL_USER); }); -Http::post('/v1/users/md5') +App::post('/v1/users/md5') ->desc('Create user with MD5 password') ->groups(['api', 'users']) ->label('event', 'users.[userId].create') @@ -273,7 +273,7 @@ Http::post('/v1/users/md5') ->dynamic($user, Response::MODEL_USER); }); -Http::post('/v1/users/argon2') +App::post('/v1/users/argon2') ->desc('Create user with Argon2 password') ->groups(['api', 'users']) ->label('event', 'users.[userId].create') @@ -304,7 +304,7 @@ Http::post('/v1/users/argon2') ->dynamic($user, Response::MODEL_USER); }); -Http::post('/v1/users/sha') +App::post('/v1/users/sha') ->desc('Create user with SHA password') ->groups(['api', 'users']) ->label('event', 'users.[userId].create') @@ -342,7 +342,7 @@ Http::post('/v1/users/sha') ->dynamic($user, Response::MODEL_USER); }); -Http::post('/v1/users/phpass') +App::post('/v1/users/phpass') ->desc('Create user with PHPass password') ->groups(['api', 'users']) ->label('event', 'users.[userId].create') @@ -373,7 +373,7 @@ Http::post('/v1/users/phpass') ->dynamic($user, Response::MODEL_USER); }); -Http::post('/v1/users/scrypt') +App::post('/v1/users/scrypt') ->desc('Create user with Scrypt password') ->groups(['api', 'users']) ->label('event', 'users.[userId].create') @@ -417,7 +417,7 @@ Http::post('/v1/users/scrypt') ->dynamic($user, Response::MODEL_USER); }); -Http::post('/v1/users/scrypt-modified') +App::post('/v1/users/scrypt-modified') ->desc('Create user with Scrypt modified password') ->groups(['api', 'users']) ->label('event', 'users.[userId].create') @@ -451,7 +451,7 @@ Http::post('/v1/users/scrypt-modified') ->dynamic($user, Response::MODEL_USER); }); -Http::post('/v1/users/:userId/targets') +App::post('/v1/users/:userId/targets') ->desc('Create user target') ->groups(['api', 'users']) ->label('audits.event', 'target.create') @@ -540,7 +540,7 @@ Http::post('/v1/users/:userId/targets') ->dynamic($target, Response::MODEL_TARGET); }); -Http::get('/v1/users') +App::get('/v1/users') ->desc('List users') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -594,7 +594,7 @@ Http::get('/v1/users') ]), Response::MODEL_USER_LIST); }); -Http::get('/v1/users/:userId') +App::get('/v1/users/:userId') ->desc('Get user') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -619,7 +619,7 @@ Http::get('/v1/users/:userId') $response->dynamic($user, Response::MODEL_USER); }); -Http::get('/v1/users/:userId/prefs') +App::get('/v1/users/:userId/prefs') ->desc('Get user preferences') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -646,7 +646,7 @@ Http::get('/v1/users/:userId/prefs') $response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES); }); -Http::get('/v1/users/:userId/targets/:targetId') +App::get('/v1/users/:userId/targets/:targetId') ->desc('Get user target') ->groups(['api', 'users']) ->label('scope', 'targets.read') @@ -678,7 +678,7 @@ Http::get('/v1/users/:userId/targets/:targetId') $response->dynamic($target, Response::MODEL_TARGET); }); -Http::get('/v1/users/:userId/sessions') +App::get('/v1/users/:userId/sessions') ->desc('List user sessions') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -719,7 +719,7 @@ Http::get('/v1/users/:userId/sessions') ]), Response::MODEL_SESSION_LIST); }); -Http::get('/v1/users/:userId/memberships') +App::get('/v1/users/:userId/memberships') ->desc('List user memberships') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -758,7 +758,7 @@ Http::get('/v1/users/:userId/memberships') ]), Response::MODEL_MEMBERSHIP_LIST); }); -Http::get('/v1/users/:userId/logs') +App::get('/v1/users/:userId/logs') ->desc('List user logs') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -775,8 +775,7 @@ Http::get('/v1/users/:userId/logs') ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->inject('authorization') - ->action(function (string $userId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) { + ->action(function (string $userId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { $user = $dbForProject->getDocument('users', $userId); @@ -848,7 +847,7 @@ Http::get('/v1/users/:userId/logs') ]), Response::MODEL_LOG_LIST); }); -Http::get('/v1/users/:userId/targets') +App::get('/v1/users/:userId/targets') ->desc('List user targets') ->groups(['api', 'users']) ->label('scope', 'targets.read') @@ -903,7 +902,7 @@ Http::get('/v1/users/:userId/targets') ]), Response::MODEL_TARGET_LIST); }); -Http::get('/v1/users/identities') +App::get('/v1/users/identities') ->desc('List identities') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -957,7 +956,7 @@ Http::get('/v1/users/identities') ]), Response::MODEL_IDENTITY_LIST); }); -Http::patch('/v1/users/:userId/status') +App::patch('/v1/users/:userId/status') ->desc('Update user status') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.status') @@ -993,7 +992,7 @@ Http::patch('/v1/users/:userId/status') $response->dynamic($user, Response::MODEL_USER); }); -Http::put('/v1/users/:userId/labels') +App::put('/v1/users/:userId/labels') ->desc('Update user labels') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.labels') @@ -1030,7 +1029,7 @@ Http::put('/v1/users/:userId/labels') $response->dynamic($user, Response::MODEL_USER); }); -Http::patch('/v1/users/:userId/verification/phone') +App::patch('/v1/users/:userId/verification/phone') ->desc('Update phone verification') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.verification') @@ -1065,7 +1064,7 @@ Http::patch('/v1/users/:userId/verification/phone') $response->dynamic($user, Response::MODEL_USER); }); -Http::patch('/v1/users/:userId/name') +App::patch('/v1/users/:userId/name') ->desc('Update name') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.name') @@ -1102,7 +1101,7 @@ Http::patch('/v1/users/:userId/name') $response->dynamic($user, Response::MODEL_USER); }); -Http::patch('/v1/users/:userId/password') +App::patch('/v1/users/:userId/password') ->desc('Update password') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.password') @@ -1179,7 +1178,7 @@ Http::patch('/v1/users/:userId/password') $response->dynamic($user, Response::MODEL_USER); }); -Http::patch('/v1/users/:userId/email') +App::patch('/v1/users/:userId/email') ->desc('Update email') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.email') @@ -1215,7 +1214,7 @@ Http::patch('/v1/users/:userId/email') Query::equal('providerEmail', [$email]), Query::notEqual('userInternalId', $user->getInternalId()), ]); - if (!$identityWithMatchingEmail->isEmpty()) { + if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } @@ -1223,7 +1222,7 @@ Http::patch('/v1/users/:userId/email') Query::equal('identifier', [$email]), ]); - if (!$target->isEmpty()) { + if ($target instanceof Document && !$target->isEmpty()) { throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); } } @@ -1274,7 +1273,7 @@ Http::patch('/v1/users/:userId/email') $response->dynamic($user, Response::MODEL_USER); }); -Http::patch('/v1/users/:userId/phone') +App::patch('/v1/users/:userId/phone') ->desc('Update phone') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.phone') @@ -1313,7 +1312,7 @@ Http::patch('/v1/users/:userId/phone') Query::equal('identifier', [$number]), ]); - if (!$target->isEmpty()) { + if ($target instanceof Document && !$target->isEmpty()) { throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); } } @@ -1357,7 +1356,7 @@ Http::patch('/v1/users/:userId/phone') $response->dynamic($user, Response::MODEL_USER); }); -Http::patch('/v1/users/:userId/verification') +App::patch('/v1/users/:userId/verification') ->desc('Update email verification') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.verification') @@ -1392,7 +1391,7 @@ Http::patch('/v1/users/:userId/verification') $response->dynamic($user, Response::MODEL_USER); }); -Http::patch('/v1/users/:userId/prefs') +App::patch('/v1/users/:userId/prefs') ->desc('Update user preferences') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.prefs') @@ -1425,7 +1424,7 @@ Http::patch('/v1/users/:userId/prefs') $response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES); }); -Http::patch('/v1/users/:userId/targets/:targetId') +App::patch('/v1/users/:userId/targets/:targetId') ->desc('Update user target') ->groups(['api', 'users']) ->label('audits.event', 'target.update') @@ -1519,7 +1518,7 @@ Http::patch('/v1/users/:userId/targets/:targetId') ->dynamic($target, Response::MODEL_TARGET); }); -Http::patch('/v1/users/:userId/mfa') +App::patch('/v1/users/:userId/mfa') ->desc('Update MFA') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.mfa') @@ -1557,7 +1556,7 @@ Http::patch('/v1/users/:userId/mfa') $response->dynamic($user, Response::MODEL_USER); }); -Http::get('/v1/users/:userId/mfa/factors') +App::get('/v1/users/:userId/mfa/factors') ->desc('List factors') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -1590,7 +1589,7 @@ Http::get('/v1/users/:userId/mfa/factors') $response->dynamic($factors, Response::MODEL_MFA_FACTORS); }); -Http::get('/v1/users/:userId/mfa/recovery-codes') +App::get('/v1/users/:userId/mfa/recovery-codes') ->desc('Get MFA recovery codes') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -1625,7 +1624,7 @@ Http::get('/v1/users/:userId/mfa/recovery-codes') $response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES); }); -Http::patch('/v1/users/:userId/mfa/recovery-codes') +App::patch('/v1/users/:userId/mfa/recovery-codes') ->desc('Create MFA recovery codes') ->groups(['api', 'users']) ->label('event', 'users.[userId].create.mfa.recovery-codes') @@ -1671,7 +1670,7 @@ Http::patch('/v1/users/:userId/mfa/recovery-codes') $response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES); }); -Http::put('/v1/users/:userId/mfa/recovery-codes') +App::put('/v1/users/:userId/mfa/recovery-codes') ->desc('Regenerate MFA recovery codes') ->groups(['api', 'users']) ->label('event', 'users.[userId].update.mfa.recovery-codes') @@ -1716,7 +1715,7 @@ Http::put('/v1/users/:userId/mfa/recovery-codes') $response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES); }); -Http::delete('/v1/users/:userId/mfa/authenticators/:type') +App::delete('/v1/users/:userId/mfa/authenticators/:type') ->desc('Delete authenticator') ->groups(['api', 'users']) ->label('event', 'users.[userId].delete.mfa') @@ -1758,7 +1757,7 @@ Http::delete('/v1/users/:userId/mfa/authenticators/:type') $response->noContent(); }); -Http::post('/v1/users/:userId/sessions') +App::post('/v1/users/:userId/sessions') ->desc('Create session') ->groups(['api', 'users']) ->label('event', 'users.[userId].sessions.[sessionId].create') @@ -1828,7 +1827,7 @@ Http::post('/v1/users/:userId/sessions') ->dynamic($session, Response::MODEL_SESSION); }); -Http::post('/v1/users/:userId/tokens') +App::post('/v1/users/:userId/tokens') ->desc('Create token') ->groups(['api', 'users']) ->label('event', 'users.[userId].tokens.[tokenId].create') @@ -1885,7 +1884,7 @@ Http::post('/v1/users/:userId/tokens') ->dynamic($token, Response::MODEL_TOKEN); }); -Http::delete('/v1/users/:userId/sessions/:sessionId') +App::delete('/v1/users/:userId/sessions/:sessionId') ->desc('Delete user session') ->groups(['api', 'users']) ->label('event', 'users.[userId].sessions.[sessionId].delete') @@ -1928,7 +1927,7 @@ Http::delete('/v1/users/:userId/sessions/:sessionId') $response->noContent(); }); -Http::delete('/v1/users/:userId/sessions') +App::delete('/v1/users/:userId/sessions') ->desc('Delete user sessions') ->groups(['api', 'users']) ->label('event', 'users.[userId].sessions.delete') @@ -1970,7 +1969,7 @@ Http::delete('/v1/users/:userId/sessions') $response->noContent(); }); -Http::delete('/v1/users/:userId') +App::delete('/v1/users/:userId') ->desc('Delete user') ->groups(['api', 'users']) ->label('event', 'users.[userId].delete') @@ -2012,7 +2011,7 @@ Http::delete('/v1/users/:userId') $response->noContent(); }); -Http::delete('/v1/users/:userId/targets/:targetId') +App::delete('/v1/users/:userId/targets/:targetId') ->desc('Delete user target') ->groups(['api', 'users']) ->label('audits.event', 'target.delete') @@ -2063,7 +2062,7 @@ Http::delete('/v1/users/:userId/targets/:targetId') $response->noContent(); }); -Http::delete('/v1/users/identities/:identityId') +App::delete('/v1/users/identities/:identityId') ->desc('Delete identity') ->groups(['api', 'users']) ->label('event', 'users.[userId].identities.[identityId].delete') @@ -2098,7 +2097,7 @@ Http::delete('/v1/users/identities/:identityId') return $response->noContent(); }); -Http::post('/v1/users/:userId/jwts') +App::post('/v1/users/:userId/jwts') ->desc('Create user JWT') ->groups(['api', 'users']) ->label('scope', 'users.write') @@ -2148,7 +2147,7 @@ Http::post('/v1/users/:userId/jwts') ])]), Response::MODEL_JWT); }); -Http::get('/v1/users/usage') +App::get('/v1/users/usage') ->desc('Get users usage stats') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -2161,8 +2160,8 @@ Http::get('/v1/users/usage') ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true) ->inject('response') ->inject('dbForProject') - ->inject('authorization') - ->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) { + ->inject('register') + ->action(function (string $range, Response $response, Database $dbForProject) { $periods = Config::getParam('usage', []); $stats = $usage = []; @@ -2172,7 +2171,7 @@ Http::get('/v1/users/usage') METRIC_SESSIONS, ]; - $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) { + Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) { foreach ($metrics as $count => $metric) { $result = $dbForProject->findOne('stats', [ Query::equal('metric', [$metric]), diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 1f97e4f2d5..f3381490ec 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -8,6 +8,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Installations; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use Appwrite\Vcs\Comment; +use Utopia\App; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Database\Database; @@ -31,17 +32,17 @@ use Utopia\Detector\Adapter\Python; use Utopia\Detector\Adapter\Ruby; use Utopia\Detector\Adapter\Swift; use Utopia\Detector\Detector; -use Utopia\Http\Http; -use Utopia\Http\Validator\Boolean; -use Utopia\Http\Validator\Host; -use Utopia\Http\Validator\Text; use Utopia\System\System; +use Utopia\Validator\Boolean; +use Utopia\Validator\Host; +use Utopia\Validator\Text; use Utopia\VCS\Adapter\Git\GitHub; use Utopia\VCS\Exception\RepositoryNotFound; use function Swoole\Coroutine\batch; -$createGitDeployments = function (GitHub $github, string $providerInstallationId, array $repositories, string $providerBranch, string $providerBranchUrl, string $providerRepositoryName, string $providerRepositoryUrl, string $providerRepositoryOwner, string $providerCommitHash, string $providerCommitAuthor, string $providerCommitAuthorUrl, string $providerCommitMessage, string $providerCommitUrl, string $providerPullRequestId, bool $external, Database $dbForConsole, Build $queueForBuilds, callable $getProjectDB, Request $request, Authorization $auth) { +$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 $resource) { try { $resourceType = $resource->getAttribute('resourceType'); @@ -51,11 +52,11 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId } $projectId = $resource->getAttribute('projectId'); - $project = $auth->skip(fn () => $dbForConsole->getDocument('projects', $projectId)); + $project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId)); $dbForProject = $getProjectDB($project); $functionId = $resource->getAttribute('resourceId'); - $function = $auth->skip(fn () => $dbForProject->getDocument('functions', $functionId)); + $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); $functionInternalId = $function->getInternalId(); $deploymentId = ID::unique(); @@ -101,14 +102,14 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId $latestCommentId = ''; - if (!empty($providerPullRequestId) && $function->getAttribute('providerSilentMode', false)) { - $latestComment = $auth->skip(fn () => $dbForConsole->findOne('vcsComments', [ + if (!empty($providerPullRequestId) && $function->getAttribute('providerSilentMode', false) === false) { + $latestComment = Authorization::skip(fn () => $dbForConsole->findOne('vcsComments', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::equal('providerPullRequestId', [$providerPullRequestId]), Query::orderDesc('$createdAt'), ])); - if (!$latestComment->isEmpty()) { + if ($latestComment !== false && !$latestComment->isEmpty()) { $latestCommentId = $latestComment->getAttribute('providerCommentId', ''); $comment = new Comment(); $comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId)); @@ -123,7 +124,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId if (!empty($latestCommentId)) { $teamId = $project->getAttribute('teamId', ''); - $latestComment = $auth->skip(fn () => $dbForConsole->createDocument('vcsComments', new Document([ + $latestComment = Authorization::skip(fn () => $dbForConsole->createDocument('vcsComments', new Document([ '$id' => ID::unique(), '$permissions' => [ Permission::read(Role::team(ID::custom($teamId))), @@ -144,7 +145,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId } } } elseif (!empty($providerBranch)) { - $latestComments = $auth->skip(fn () => $dbForConsole->find('vcsComments', [ + $latestComments = Authorization::skip(fn () => $dbForConsole->find('vcsComments', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::equal('providerBranch', [$providerBranch]), Query::orderDesc('$createdAt'), @@ -261,8 +262,8 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId } }; -Http::get('/v1/vcs/github/authorize') - ->desc('Install GitHub App') +App::get('/v1/vcs/github/authorize') + ->desc('Install GitHub app') ->groups(['api', 'vcs']) ->label('scope', 'vcs.read') ->label('sdk.namespace', 'vcs') @@ -303,8 +304,8 @@ Http::get('/v1/vcs/github/authorize') ->redirect($url); }); -Http::get('/v1/vcs/github/callback') - ->desc('Capture installation and authorization from GitHub App') +App::get('/v1/vcs/github/callback') + ->desc('Capture installation and authorization from GitHub app') ->groups(['api', 'vcs']) ->label('scope', 'public') ->label('error', __DIR__ . '/../../views/general/error.phtml') @@ -369,7 +370,7 @@ Http::get('/v1/vcs/github/callback') $identity = $dbForConsole->findOne('identities', [ Query::equal('providerEmail', [$email]), ]); - if (!$identity->isEmpty()) { + if ($identity !== false && !$identity->isEmpty()) { if ($identity->getAttribute('userInternalId', '') !== $user->getInternalId()) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } @@ -416,7 +417,7 @@ Http::get('/v1/vcs/github/callback') Query::equal('projectInternalId', [$projectInternalId]) ]); - if ($installation->isEmpty()) { + if ($installation === false || $installation->isEmpty()) { $teamId = $project->getAttribute('teamId', ''); $installation = new Document([ @@ -463,7 +464,7 @@ Http::get('/v1/vcs/github/callback') ->redirect($redirectSuccess); }); -Http::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/contents') +App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/contents') ->desc('Get files and directories of a VCS repository') ->groups(['api', 'vcs']) ->label('scope', 'vcs.read') @@ -524,7 +525,7 @@ Http::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pr ]), Response::MODEL_VCS_CONTENT_LIST); }); -Http::post('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/detection') +App::post('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/detection') ->desc('Detect runtime settings from source code') ->groups(['api', 'vcs']) ->label('scope', 'vcs.write') @@ -596,8 +597,8 @@ Http::post('/v1/vcs/github/installations/:installationId/providerRepositories/:p $response->dynamic(new Document($detection), Response::MODEL_DETECTION); }); -Http::get('/v1/vcs/github/installations/:installationId/providerRepositories') - ->desc('List Repositories') +App::get('/v1/vcs/github/installations/:installationId/providerRepositories') + ->desc('List repositories') ->groups(['api', 'vcs']) ->label('scope', 'vcs.read') ->label('sdk.namespace', 'vcs') @@ -691,7 +692,7 @@ Http::get('/v1/vcs/github/installations/:installationId/providerRepositories') ]), Response::MODEL_PROVIDER_REPOSITORY_LIST); }); -Http::post('/v1/vcs/github/installations/:installationId/providerRepositories') +App::post('/v1/vcs/github/installations/:installationId/providerRepositories') ->desc('Create repository') ->groups(['api', 'vcs']) ->label('scope', 'vcs.write') @@ -724,7 +725,7 @@ Http::post('/v1/vcs/github/installations/:installationId/providerRepositories') Query::equal('provider', ['github']), Query::equal('userInternalId', [$user->getInternalId()]), ]); - if ($identity->isEmpty()) { + if ($identity === false || $identity->isEmpty()) { throw new Exception(Exception::USER_IDENTITY_NOT_FOUND); } @@ -792,7 +793,7 @@ Http::post('/v1/vcs/github/installations/:installationId/providerRepositories') $response->dynamic(new Document($repository), Response::MODEL_PROVIDER_REPOSITORY); }); -Http::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId') +App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId') ->desc('Get repository') ->groups(['api', 'vcs']) ->label('scope', 'vcs.read') @@ -841,8 +842,8 @@ Http::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pr $response->dynamic(new Document($repository), Response::MODEL_PROVIDER_REPOSITORY); }); -Http::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/branches') - ->desc('List Repository Branches') +App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/branches') + ->desc('List repository branches') ->groups(['api', 'vcs']) ->label('scope', 'vcs.read') ->label('sdk.namespace', 'vcs') @@ -890,8 +891,8 @@ Http::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pr ]), Response::MODEL_BRANCH_LIST); }); -Http::post('/v1/vcs/github/events') - ->desc('Create Event') +App::post('/v1/vcs/github/events') + ->desc('Create event') ->groups(['api', 'vcs']) ->label('scope', 'public') ->inject('gitHub') @@ -900,9 +901,8 @@ Http::post('/v1/vcs/github/events') ->inject('dbForConsole') ->inject('getProjectDB') ->inject('queueForBuilds') - ->inject('authorization') ->action( - function (GitHub $github, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds, Authorization $authorization) use ($createGitDeployments) { + function (GitHub $github, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) { $payload = $request->getRawPayload(); $signatureRemote = $request->getHeader('x-hub-signature-256', ''); $signatureLocal = System::getEnv('_APP_VCS_GITHUB_WEBHOOK_SECRET', ''); @@ -936,14 +936,14 @@ Http::post('/v1/vcs/github/events') $github->initializeVariables($providerInstallationId, $privateKey, $githubAppId); //find functionId from functions table - $repositories = $authorization->skip(fn () => $dbForConsole->find('repositories', [ + $repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::limit(100), ])); // create new deployment only on push and not when branch is created if (!$providerBranchCreated) { - $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForConsole, $queueForBuilds, $getProjectDB, $request, $authorization); + $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForConsole, $queueForBuilds, $getProjectDB, $request); } } elseif ($event == $github::EVENT_INSTALLATION) { if ($parsedPayload["action"] == "deleted") { @@ -956,13 +956,13 @@ Http::post('/v1/vcs/github/events') ]); foreach ($installations as $installation) { - $repositories = $authorization->skip(fn () => $dbForConsole->find('repositories', [ + $repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [ Query::equal('installationInternalId', [$installation->getInternalId()]), Query::limit(1000) ])); foreach ($repositories as $repository) { - $authorization->skip(fn () => $dbForConsole->deleteDocument('repositories', $repository->getId())); + Authorization::skip(fn () => $dbForConsole->deleteDocument('repositories', $repository->getId())); } $dbForConsole->deleteDocument('installations', $installation->getId()); @@ -994,12 +994,12 @@ Http::post('/v1/vcs/github/events') $providerCommitAuthor = $commitDetails["commitAuthor"] ?? ''; $providerCommitMessage = $commitDetails["commitMessage"] ?? ''; - $repositories = $authorization->skip(fn () => $dbForConsole->find('repositories', [ + $repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::orderDesc('$createdAt') ])); - $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForConsole, $queueForBuilds, $getProjectDB, $request, $authorization); + $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForConsole, $queueForBuilds, $getProjectDB, $request); } elseif ($parsedPayload["action"] == "closed") { // Allowed external contributions cleanup @@ -1008,7 +1008,7 @@ Http::post('/v1/vcs/github/events') $external = $parsedPayload["external"] ?? true; if ($external) { - $repositories = $authorization->skip(fn () => $dbForConsole->find('repositories', [ + $repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::orderDesc('$createdAt') ])); @@ -1019,7 +1019,7 @@ Http::post('/v1/vcs/github/events') if (\in_array($providerPullRequestId, $providerPullRequestIds)) { $providerPullRequestIds = \array_diff($providerPullRequestIds, [$providerPullRequestId]); $repository = $repository->setAttribute('providerPullRequestIds', $providerPullRequestIds); - $repository = $authorization->skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository)); + $repository = Authorization::skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository)); } } } @@ -1030,7 +1030,7 @@ Http::post('/v1/vcs/github/events') } ); -Http::get('/v1/vcs/installations') +App::get('/v1/vcs/installations') ->desc('List installations') ->groups(['api', 'vcs']) ->label('scope', 'vcs.read') @@ -1090,7 +1090,7 @@ Http::get('/v1/vcs/installations') ]), Response::MODEL_INSTALLATION_LIST); }); -Http::get('/v1/vcs/installations/:installationId') +App::get('/v1/vcs/installations/:installationId') ->desc('Get installation') ->groups(['api', 'vcs']) ->label('scope', 'vcs.read') @@ -1119,8 +1119,8 @@ Http::get('/v1/vcs/installations/:installationId') $response->dynamic($installation, Response::MODEL_INSTALLATION); }); -Http::delete('/v1/vcs/installations/:installationId') - ->desc('Delete Installation') +App::delete('/v1/vcs/installations/:installationId') + ->desc('Delete installation') ->groups(['api', 'vcs']) ->label('scope', 'vcs.write') ->label('sdk.namespace', 'vcs') @@ -1152,7 +1152,7 @@ Http::delete('/v1/vcs/installations/:installationId') $response->noContent(); }); -Http::patch('/v1/vcs/github/installations/:installationId/repositories/:repositoryId') +App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositoryId') ->desc('Authorize external deployment') ->groups(['api', 'vcs']) ->label('scope', 'vcs.write') @@ -1172,15 +1172,14 @@ Http::patch('/v1/vcs/github/installations/:installationId/repositories/:reposito ->inject('dbForConsole') ->inject('getProjectDB') ->inject('queueForBuilds') - ->inject('authorization') - ->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds, Authorization $authorization) use ($createGitDeployments) { + ->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) { $installation = $dbForConsole->getDocument('installations', $installationId); if ($installation->isEmpty()) { throw new Exception(Exception::INSTALLATION_NOT_FOUND); } - $repository = $authorization->skip(fn () => $dbForConsole->getDocument('repositories', $repositoryId, [ + $repository = Authorization::skip(fn () => $dbForConsole->getDocument('repositories', $repositoryId, [ Query::equal('projectInternalId', [$project->getInternalId()]) ])); @@ -1197,7 +1196,7 @@ Http::patch('/v1/vcs/github/installations/:installationId/repositories/:reposito // TODO: Delete from array when PR is closed - $repository = $authorization->skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository)); + $repository = Authorization::skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository)); $privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY'); $githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID'); @@ -1221,7 +1220,7 @@ Http::patch('/v1/vcs/github/installations/:installationId/repositories/:reposito $providerBranch = \explode(':', $pullRequestResponse['head']['label'])[1] ?? ''; $providerCommitHash = $pullRequestResponse['head']['sha'] ?? ''; - $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerCommitHash, $providerPullRequestId, true, $dbForConsole, $queueForBuilds, $getProjectDB, $request, $authorization); + $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerCommitHash, $providerPullRequestId, true, $dbForConsole, $queueForBuilds, $getProjectDB, $request); $response->noContent(); }); diff --git a/app/controllers/general.php b/app/controllers/general.php index 70cf55d280..04554a940e 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -1,5 +1,7 @@ label('error', __DIR__ . '/../views/general/error.phtml'); + $utopia->getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml'); $host = $request->getHostname() ?? ''; - $rule = $auth->skip( + $route = Authorization::skip( fn () => $dbForConsole->find('rules', [ Query::equal('domain', [$host]), Query::limit(1) ]) )[0] ?? null; - if ($rule === null) { + if ($route === null) { 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.'); } @@ -72,12 +73,12 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request } // Act as API - no Proxy logic - $route?->label('error', ''); + $utopia->getRoute()?->label('error', ''); return false; } - $projectId = $rule->getAttribute('projectId'); - $project = $auth->skip( + $projectId = $route->getAttribute('projectId'); + $project = Authorization::skip( fn () => $dbForConsole->getDocument('projects', $projectId) ); if (array_key_exists('proxy', $project->getAttribute('services', []))) { @@ -88,16 +89,16 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request } // Skip Appwrite Router for ACME challenge. Nessessary for certificate generation - $path = ($request->getURI() ?? '/'); + $path = ($swooleRequest->server['request_uri'] ?? '/'); if (\str_starts_with($path, '/.well-known/acme-challenge')) { return false; } - $type = $rule->getAttribute('resourceType'); + $type = $route->getAttribute('resourceType'); if ($type === 'function') { - $route->label('sdk.namespace', 'functions'); - $route->label('sdk.method', 'createExecution'); + $utopia->getRoute()?->label('sdk.namespace', 'functions'); + $utopia->getRoute()?->label('sdk.method', 'createExecution'); if (System::getEnv('_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS if ($request->getProtocol() !== 'https') { @@ -109,25 +110,26 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request } } - $functionId = $rule->getAttribute('resourceId'); - $projectId = $rule->getAttribute('projectId'); + $functionId = $route->getAttribute('resourceId'); + $projectId = $route->getAttribute('projectId'); - $path = ($request->getURI() ?? '/'); - $query = ($request->getQueryString() ?? ''); + $path = ($swooleRequest->server['request_uri'] ?? '/'); + $query = ($swooleRequest->server['query_string'] ?? ''); if (!empty($query)) { $path .= '?' . $query; } - $body = $request->getRawPayload() ?? ''; - $method = $request->getMethod(); + + $body = $swooleRequest->getContent() ?? ''; + $method = $swooleRequest->server['request_method']; $requestHeaders = $request->getHeaders(); - $project = $auth->skip(fn () => $dbForConsole->getDocument('projects', $projectId)); + $project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId)); $dbForProject = $getProjectDB($project); - $function = $auth->skip(fn () => $dbForProject->getDocument('functions', $functionId)); + $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); if ($function->isEmpty() || !$function->getAttribute('enabled')) { throw new AppwriteException(AppwriteException::FUNCTION_NOT_FOUND); @@ -143,7 +145,7 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request throw new AppwriteException(AppwriteException::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $function->getAttribute('runtime', '') . '" is not supported'); } - $deployment = $auth->skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', ''))); + $deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', ''))); 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'); @@ -154,7 +156,7 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request } /** Check if build has completed */ - $build = $auth->skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''))); + $build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''))); if ($build->isEmpty()) { throw new AppwriteException(AppwriteException::BUILD_NOT_FOUND); } @@ -215,7 +217,7 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request 'deploymentInternalId' => $deployment->getInternalId(), 'deploymentId' => $deployment->getId(), 'trigger' => 'http', // http / schedule / event - 'status' => 'processing', // waiting / processing / completed / failed + 'status' => 'processing', // waiting / processing / completed / failed 'responseStatusCode' => 0, 'responseHeaders' => [], 'requestPath' => $path, @@ -311,6 +313,7 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request cpus: $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT, memory: $spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT, logging: $function->getAttribute('logging', true), + requestTimeout: 30 ); $headersFiltered = []; @@ -328,6 +331,7 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request $execution->setAttribute('logs', $executionResponse['logs']); $execution->setAttribute('errors', $executionResponse['errors']); $execution->setAttribute('duration', $executionResponse['duration']); + } catch (\Throwable $th) { $durationEnd = \microtime(true); @@ -362,8 +366,7 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request ->trigger() ; - /** @var Document $execution */ - $execution = $auth->skip(fn () => $dbForProject->createDocument('executions', $execution)); + $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution)); } $execution->setAttribute('logs', ''); @@ -395,15 +398,18 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request return true; } elseif ($type === 'api') { - $route?->label('error', ''); + $utopia->getRoute()?->label('error', ''); return false; } else { throw new AppwriteException(AppwriteException::GENERAL_SERVER_ERROR, 'Unknown resource type ' . $type); } + + $utopia->getRoute()?->label('error', ''); + return false; } /* -Http::init() +App::init() ->groups(['api']) ->inject('project') ->inject('mode') @@ -414,7 +420,7 @@ Http::init() }); */ -Http::init() +App::init() ->groups(['database', 'functions', 'storage', 'messaging']) ->inject('project') ->inject('request') @@ -427,11 +433,12 @@ Http::init() } }); -Http::init() +App::init() ->groups(['api', 'web']) + ->inject('utopia') + ->inject('swooleRequest') ->inject('request') ->inject('response') - ->inject('route') ->inject('console') ->inject('project') ->inject('dbForConsole') @@ -443,27 +450,7 @@ Http::init() ->inject('queueForUsage') ->inject('queueForEvents') ->inject('queueForCertificates') - ->inject('authorization') - ->action(function ( - Request $request, - Response $response, - Route $route, - Document $console, - Document $project, - Database $dbForConsole, - $getProjectDB, - Locale $locale, - array $localeCodes, - array $clients, - /** - * @disregard P1009 Undefined type - */ - Reader $geodb, - Usage $queueForUsage, - Event $queueForEvents, - Certificate $queueForCertificates, - Authorization $authorization - ) { + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, Reader $geodb, Usage $queueForUsage, Event $queueForEvents, Certificate $queueForCertificates) { /* * Appwrite Router */ @@ -471,7 +458,7 @@ Http::init() $mainDomain = System::getEnv('_APP_DOMAIN', ''); // Only run Router when external domain if ($host !== $mainDomain) { - if (router($dbForConsole, $getProjectDB, $request, $response, $route, $queueForEvents, $queueForUsage, $geodb, $authorization)) { + if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb)) { return; } } @@ -479,8 +466,8 @@ Http::init() /* * Request format */ - //$route = $utopia->getRoute(); - //Request::setRoute($route); + $route = $utopia->getRoute(); + Request::setRoute($route); if ($route === null) { return $response @@ -512,7 +499,7 @@ Http::init() } elseif (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) { Console::warning('Skipping SSL certificates generation on ACME challenge.'); } else { - $authorization->disable(); + Authorization::disable(); $envDomain = System::getEnv('_APP_DOMAIN', ''); $mainDomain = null; @@ -520,7 +507,7 @@ Http::init() $mainDomain = $envDomain; } else { $domainDocument = $dbForConsole->findOne('rules', [Query::orderAsc('$id')]); - $mainDomain = !$domainDocument->isEmpty() ? $domainDocument->getAttribute('domain') : $domain->get(); + $mainDomain = $domainDocument ? $domainDocument->getAttribute('domain') : $domain->get(); } if ($mainDomain !== $domain->get()) { @@ -530,7 +517,7 @@ Http::init() Query::equal('domain', [$domain->get()]) ]); - if ($domainDocument->isEmpty()) { + if (!$domainDocument) { $domainDocument = new Document([ 'domain' => $domain->get(), 'resourceType' => 'api', @@ -551,12 +538,12 @@ Http::init() } $domains[$domain->get()] = true; - $authorization->reset(); // ensure authorization is re-enabled + Authorization::reset(); // ensure authorization is re-enabled } Config::setParam('domains', $domains); } - $localeParam = (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')); + $localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', '')); if (\in_array($localeParam, $localeCodes)) { $locale->setDefault($localeParam); } @@ -584,7 +571,7 @@ Http::init() Config::setParam( 'domainVerification', ($selfDomain->getRegisterable() === $endDomain->getRegisterable()) && - $endDomain->getRegisterable() !== '' + $endDomain->getRegisterable() !== '' ); $isLocalHost = $request->getHostname() === 'localhost' || $request->getHostname() === 'localhost:' . $request->getPort(); @@ -599,8 +586,8 @@ Http::init() ? null : ( $isConsoleProject && $isConsoleRootSession - ? '.' . $selfDomain->getRegisterable() - : '.' . $request->getHostname() + ? '.' . $selfDomain->getRegisterable() + : '.' . $request->getHostname() ) ); @@ -619,7 +606,7 @@ Http::init() $response->addFilter(new ResponseV18()); } if (version_compare($responseFormat, APP_VERSION_STABLE, '>')) { - $response->addHeader('X-Appwrite-Warning', "The current SDK is built for Appwrite " . $responseFormat . ". However, the current Appwrite server version is " . APP_VERSION_STABLE . ". Please downgrade your SDK to match the Appwrite version: https://appwrite.io/docs/sdks"); + $response->addHeader('X-Appwrite-Warning', "The current SDK is built for Appwrite " . $responseFormat . ". However, the current Appwrite server version is ". APP_VERSION_STABLE . ". Please downgrade your SDK to match the Appwrite version: https://appwrite.io/docs/sdks"); } } @@ -630,9 +617,7 @@ Http::init() * @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers */ if (System::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS - if ($request->getProtocol() !== 'https' // localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations - && ($request->getHeader('host') ?? '') !== 'localhost' - && ($request->getHeader('host') ?? '') !== APP_HOSTNAME_INTERNAL) { + if ($request->getProtocol() !== 'https' && ($swooleRequest->header['host'] ?? '') !== 'localhost' && ($swooleRequest->header['host'] ?? '') !== APP_HOSTNAME_INTERNAL) { // localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations if ($request->getMethod() !== Request::METHOD_GET) { throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP. Please use HTTPS instead.'); } @@ -672,8 +657,9 @@ Http::init() } }); -Http::options() - ->inject('route') +App::options() + ->inject('utopia') + ->inject('swooleRequest') ->inject('request') ->inject('response') ->inject('dbForConsole') @@ -681,8 +667,7 @@ Http::options() ->inject('queueForEvents') ->inject('queueForUsage') ->inject('geodb') - ->inject('authorization') - ->action(function (Route $route, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb, Authorization $authorization) { + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb) { /* * Appwrite Router */ @@ -690,7 +675,7 @@ Http::options() $mainDomain = System::getEnv('_APP_DOMAIN', ''); // Only run Router when external domain if ($host !== $mainDomain) { - if (router($dbForConsole, $getProjectDB, $request, $response, $route, $queueForEvents, $queueForUsage, $geodb, $authorization)) { + if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb)) { return; } } @@ -707,25 +692,18 @@ Http::options() ->noContent(); }); -Http::error() +App::error() ->inject('error') - ->inject('user') - ->inject('route') + ->inject('utopia') ->inject('request') ->inject('response') ->inject('project') ->inject('logger') ->inject('log') - ->inject('authorization') - ->inject('connections') ->inject('queueForUsage') - ->action(function (Throwable $error, Document $user, ?Route $route, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, Authorization $authorization, Connections $connections, Usage $queueForUsage) { + ->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, Usage $queueForUsage) { $version = System::getEnv('_APP_VERSION', 'UNKNOWN'); - - if (is_null($route)) { - $route = new Route($request->getMethod(), $request->getURI()); - } - + $route = $utopia->getRoute(); $class = \get_class($error); $code = $error->getCode(); $message = $error->getMessage(); @@ -746,9 +724,9 @@ Http::error() Console::error('[Error] File: ' . $file); Console::error('[Error] Line: ' . $line); } + switch ($class) { - case 'Utopia\Servers\Exception': - case 'Utopia\Http\Exception': + case 'Utopia\Exception': $error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error); switch ($code) { case 400: @@ -793,36 +771,35 @@ Http::error() } else { $publish = $error->getCode() === 0 || $error->getCode() >= 500; } + if ($error->getCode() >= 400 && $error->getCode() < 500) { // Register error logger $providerName = System::getEnv('_APP_EXPERIMENT_LOGGING_PROVIDER', ''); $providerConfig = System::getEnv('_APP_EXPERIMENT_LOGGING_CONFIG', ''); - if (!(empty($providerName) || empty($providerConfig))) { - try { - $loggingProvider = new DSN($providerConfig); - $providerName = $loggingProvider->getScheme(); + try { + $loggingProvider = new DSN($providerConfig ?? ''); + $providerName = $loggingProvider->getScheme(); - if (!empty($providerName) && $providerName === 'sentry') { - $key = $loggingProvider->getPassword(); - $projectId = $loggingProvider->getUser() ?? ''; - $host = 'https://' . $loggingProvider->getHost(); + if (!empty($providerName) && $providerName === 'sentry') { + $key = $loggingProvider->getPassword(); + $projectId = $loggingProvider->getUser() ?? ''; + $host = 'https://' . $loggingProvider->getHost(); - $adapter = new Sentry($projectId, $key, $host); - $logger = new Logger($adapter); - $logger->setSample(0.04); - $publish = true; - } else { - throw new \Exception('Invalid experimental logging provider'); - } - } catch (\Throwable $th) { - Console::warning('Failed to initialize logging provider: ' . $th->getMessage()); + $adapter = new Sentry($projectId, $key, $host); + $logger = new Logger($adapter); + $logger->setSample(0.04); + $publish = true; + } else { + throw new \Exception('Invalid experimental logging provider'); } + } catch (\Throwable $th) { + Console::warning('Failed to initialize logging provider: ' . $th->getMessage()); } } if ($publish && $project->getId() !== 'console') { - if (!Auth::isPrivilegedUser($authorization->getRoles())) { + if (!Auth::isPrivilegedUser(Authorization::getRoles())) { $fileSize = 0; $file = $request->getFiles('file'); if (!empty($file)) { @@ -841,7 +818,14 @@ Http::error() } - if ($logger && ($publish || $error->getCode() === 0)) { + if ($logger && $publish) { + try { + /** @var Utopia\Database\Document $user */ + $user = $utopia->getResource('user'); + } catch (\Throwable) { + // All good, user is optional information for logger + } + if (isset($user) && !$user->isEmpty()) { $log->setUser(new User($user->getId())); } @@ -871,7 +855,7 @@ Http::error() $log->addExtra('file', $error->getFile()); $log->addExtra('line', $error->getLine()); $log->addExtra('trace', $error->getTraceAsString()); - $log->addExtra('roles', $authorization->getRoles()); + $log->addExtra('roles', Authorization::getRoles()); $action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD"); $log->setAction($action); @@ -889,7 +873,7 @@ Http::error() /** Wrap all exceptions inside Appwrite\Extend\Exception */ if (!($error instanceof AppwriteException)) { - $error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, (int)$code, $error); + $error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error); } switch ($code) { // Don't show 500 errors! @@ -909,14 +893,14 @@ Http::error() break; default: $code = 500; // All other errors get the generic 500 server error status code - $message = (Http::getMode() === Http::MODE_TYPE_DEVELOPMENT) ? $message : 'Server Error'; + $message = 'Server Error'; } //$_SERVER = []; // Reset before reporting to error log to avoid keys being compromised $type = $error->getType(); - $output = ((Http::isDevelopment())) ? [ + $output = ((App::isDevelopment())) ? [ 'message' => $message, 'code' => $code, 'file' => $file, @@ -944,7 +928,7 @@ Http::error() $layout ->setParam('title', $project->getAttribute('name') . ' - Error') - ->setParam('development', Http::isDevelopment()) + ->setParam('development', App::isDevelopment()) ->setParam('projectName', $project->getAttribute('name')) ->setParam('projectURL', $project->getAttribute('url')) ->setParam('message', $output['message'] ?? '') @@ -955,18 +939,18 @@ Http::error() $response->html($layout->render()); } - $connections->reclaim(); - $response->dynamic( new Document($output), - Http::isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_ERROR + $utopia->isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_ERROR ); }); -Http::get('/robots.txt') +App::get('/robots.txt') ->desc('Robots.txt File') ->label('scope', 'public') ->label('docs', false) + ->inject('utopia') + ->inject('swooleRequest') ->inject('request') ->inject('response') ->inject('dbForConsole') @@ -974,9 +958,7 @@ Http::get('/robots.txt') ->inject('queueForEvents') ->inject('queueForUsage') ->inject('geodb') - ->inject('route') - ->inject('authorization') - ->action(function (Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb, ?Route $route, Authorization $authorization) { + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb) { $host = $request->getHostname() ?? ''; $mainDomain = System::getEnv('_APP_DOMAIN', ''); @@ -984,17 +966,16 @@ Http::get('/robots.txt') $template = new View(__DIR__ . '/../views/general/robots.phtml'); $response->text($template->render(false)); } else { - if (is_null($route)) { - $route = new Route($request->getMethod(), $request->getURI()); - } - router($dbForConsole, $getProjectDB, $request, $response, $route, $queueForEvents, $queueForUsage, $geodb, $authorization); + router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb); } }); -Http::get('/humans.txt') +App::get('/humans.txt') ->desc('Humans.txt File') ->label('scope', 'public') ->label('docs', false) + ->inject('utopia') + ->inject('swooleRequest') ->inject('request') ->inject('response') ->inject('dbForConsole') @@ -1002,9 +983,7 @@ Http::get('/humans.txt') ->inject('queueForEvents') ->inject('queueForUsage') ->inject('geodb') - ->inject('route') - ->inject('authorization') - ->action(function (Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb, Route $route, Authorization $authorization) { + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb) { $host = $request->getHostname() ?? ''; $mainDomain = System::getEnv('_APP_DOMAIN', ''); @@ -1012,11 +991,11 @@ Http::get('/humans.txt') $template = new View(__DIR__ . '/../views/general/humans.phtml'); $response->text($template->render(false)); } else { - router($dbForConsole, $getProjectDB, $request, $response, $route, $queueForEvents, $queueForUsage, $geodb, $authorization); + router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb); } }); -Http::get('/.well-known/acme-challenge/*') +App::get('/.well-known/acme-challenge/*') ->desc('SSL Verification') ->label('scope', 'public') ->label('docs', false) @@ -1066,32 +1045,16 @@ Http::get('/.well-known/acme-challenge/*') $response->text($content); }); -Http::wildcard() +include_once __DIR__ . '/shared/api.php'; +include_once __DIR__ . '/shared/api/auth.php'; + +App::wildcard() ->groups(['api']) ->label('scope', 'global') ->action(function () { throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND); }); -include_once 'mock.php'; -include_once 'shared/api.php'; -include_once 'shared/api/auth.php'; -include_once 'api/account.php'; -include_once 'api/avatars.php'; -include_once 'api/console.php'; -include_once 'api/databases.php'; -include_once 'api/functions.php'; -include_once 'api/graphql.php'; -include_once 'api/health.php'; -include_once 'api/locale.php'; -include_once 'api/messaging.php'; -include_once 'api/migrations.php'; -include_once 'api/project.php'; -include_once 'api/projects.php'; -include_once 'api/proxy.php'; -include_once 'api/storage.php'; -include_once 'api/teams.php'; -include_once 'api/users.php'; -include_once 'api/vcs.php'; -include_once 'web/console.php'; -include_once 'web/home.php'; +foreach (Config::getParam('services', []) as $service) { + include_once $service['controller']; +} diff --git a/app/controllers/mock.php b/app/controllers/mock.php index 89e21cfb26..bc071fc885 100644 --- a/app/controllers/mock.php +++ b/app/controllers/mock.php @@ -3,7 +3,9 @@ global $utopia, $request, $response; use Appwrite\Extend\Exception; +use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; +use Utopia\App; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; @@ -11,15 +13,13 @@ use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Validator\UID; -use Utopia\Http\Http; -use Utopia\Http\Route; -use Utopia\Http\Validator\Host; -use Utopia\Http\Validator\Text; -use Utopia\Http\Validator\WhiteList; use Utopia\System\System; +use Utopia\Validator\Host; +use Utopia\Validator\Text; +use Utopia\Validator\WhiteList; use Utopia\VCS\Adapter\Git\GitHub; -Http::get('/v1/mock/tests/general/oauth2') +App::get('/v1/mock/tests/general/oauth2') ->desc('OAuth Login') ->groups(['mock']) ->label('scope', 'public') @@ -35,7 +35,7 @@ Http::get('/v1/mock/tests/general/oauth2') $response->redirect($redirectURI . '?' . \http_build_query(['code' => 'abcdef', 'state' => $state])); }); -Http::get('/v1/mock/tests/general/oauth2/token') +App::get('/v1/mock/tests/general/oauth2/token') ->desc('OAuth2 Token') ->groups(['mock']) ->label('scope', 'public') @@ -81,7 +81,7 @@ Http::get('/v1/mock/tests/general/oauth2/token') } }); -Http::get('/v1/mock/tests/general/oauth2/user') +App::get('/v1/mock/tests/general/oauth2/user') ->desc('OAuth2 User') ->groups(['mock']) ->label('scope', 'public') @@ -101,7 +101,7 @@ Http::get('/v1/mock/tests/general/oauth2/user') ]); }); -Http::get('/v1/mock/tests/general/oauth2/success') +App::get('/v1/mock/tests/general/oauth2/success') ->desc('OAuth2 Success') ->groups(['mock']) ->label('scope', 'public') @@ -114,7 +114,7 @@ Http::get('/v1/mock/tests/general/oauth2/success') ]); }); -Http::get('/v1/mock/tests/general/oauth2/failure') +App::get('/v1/mock/tests/general/oauth2/failure') ->desc('OAuth2 Failure') ->groups(['mock']) ->label('scope', 'public') @@ -129,7 +129,7 @@ Http::get('/v1/mock/tests/general/oauth2/failure') ]); }); -Http::patch('/v1/mock/functions-v2') +App::patch('/v1/mock/functions-v2') ->desc('Update Function Version to V2 (outdated code syntax)') ->groups(['mock', 'api', 'functions']) ->label('scope', 'functions.write') @@ -155,7 +155,7 @@ Http::patch('/v1/mock/functions-v2') $response->noContent(); }); -Http::post('/v1/mock/api-key-unprefixed') +App::post('/v1/mock/api-key-unprefixed') ->desc('Create API Key (without standard prefix)') ->groups(['mock', 'api', 'projects']) ->label('scope', 'public') @@ -204,7 +204,7 @@ Http::post('/v1/mock/api-key-unprefixed') ->dynamic($key, Response::MODEL_KEY); }); -Http::get('/v1/mock/github/callback') +App::get('/v1/mock/github/callback') ->desc('Create installation document using GitHub installation id') ->groups(['mock', 'api', 'vcs']) ->label('scope', 'public') @@ -264,13 +264,15 @@ Http::get('/v1/mock/github/callback') ]); }); -Http::shutdown() +App::shutdown() ->groups(['mock']) - ->inject('route') + ->inject('utopia') ->inject('response') - ->action(function (Route $route, Response $response) { + ->inject('request') + ->action(function (App $utopia, Response $response, Request $request) { $result = []; + $route = $utopia->getRoute(); $path = APP_STORAGE_CACHE . '/tests.json'; $tests = (\file_exists($path)) ? \json_decode(\file_get_contents($path), true) : []; diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index ee9012d74c..f0d896c95a 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -15,11 +15,11 @@ use Appwrite\Event\Usage; use Appwrite\Extend\Exception; use Appwrite\Extend\Exception as AppwriteException; use Appwrite\Messaging\Adapter\Realtime; -use Appwrite\Utopia\Queue\Connections; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapters\Database\TimeLimit; +use Utopia\App; use Utopia\Cache\Adapter\Filesystem; use Utopia\Cache\Cache; use Utopia\Config\Config; @@ -28,11 +28,8 @@ use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\Role; use Utopia\Database\Validator\Authorization; -use Utopia\Database\Validator\Authorization\Input; -use Utopia\Http\Http; -use Utopia\Http\Route; -use Utopia\Http\Validator\WhiteList; use Utopia\System\System; +use Utopia\Validator\WhiteList; $parseLabel = function (string $label, array $responsePayload, array $requestParams, Document $user) { preg_match_all('/{(.*?)}/', $label, $matches); @@ -153,9 +150,9 @@ $databaseListener = function (string $event, Document $document, Document $proje } }; -Http::init() +App::init() ->groups(['api']) - ->inject('route') + ->inject('utopia') ->inject('request') ->inject('dbForConsole') ->inject('project') @@ -164,8 +161,9 @@ Http::init() ->inject('servers') ->inject('mode') ->inject('team') - ->inject('authorization') - ->action(function (Route $route, Request $request, Database $dbForConsole, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team, Authorization $authorization) { + ->action(function (App $utopia, Request $request, Database $dbForConsole, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team) { + $route = $utopia->getRoute(); + if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } @@ -223,8 +221,8 @@ Http::init() $role = Auth::USER_ROLE_APPS; $scopes = \array_merge($roles[$role]['scopes'], $tokenScopes); - $authorization->addRole(Auth::USER_ROLE_APPS); - $authorization->setDefaultStatus(false); // Cancel security segmentation for API keys. + Authorization::setRole(Auth::USER_ROLE_APPS); + Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys. } } elseif ($keyType === API_KEY_STANDARD) { // No underline means no prefix. Backwards compatibility. @@ -249,8 +247,8 @@ Http::init() throw new Exception(Exception::PROJECT_KEY_EXPIRED); } - $authorization->addRole(Auth::USER_ROLE_APPS); - $authorization->setDefaultStatus(false); // Cancel security segmentation for API keys. + Authorization::setRole(Auth::USER_ROLE_APPS); + Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys. $accessedAt = $key->getAttribute('accessedAt', ''); if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) { @@ -296,15 +294,15 @@ Http::init() foreach ($adminRoles as $role) { $scopes = \array_merge($scopes, $roles[$role]['scopes']); } - $authorization->setDefaultStatus(false); // Cancel security segmentation for admin users. + + Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users. } $scopes = \array_unique($scopes); - $authorization->addRole($role); - - foreach (Auth::getRoles($user, $authorization) as $authRole) { - $authorization->addRole($authRole); + Authorization::setRole($role); + foreach (Auth::getRoles($user) as $authRole) { + Authorization::setRole($authRole); } /** Do not allow access to disabled services */ @@ -313,7 +311,7 @@ Http::init() if ( array_key_exists($service, $project->getAttribute('services', [])) && !$project->getAttribute('services', [])[$service] - && !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles())) + && !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles())) ) { throw new Exception(Exception::GENERAL_SERVICE_DISABLED); } @@ -348,9 +346,9 @@ Http::init() } }); -Http::init() +App::init() ->groups(['api']) - ->inject('route') + ->inject('utopia') ->inject('request') ->inject('response') ->inject('project') @@ -364,12 +362,14 @@ Http::init() ->inject('queueForUsage') ->inject('dbForProject') ->inject('mode') - ->inject('authorization') - ->action(function (Route $route, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode, Authorization $authorization) use ($databaseListener) { + ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode) use ($databaseListener) { + + $route = $utopia->getRoute(); + if ( array_key_exists('rest', $project->getAttribute('apis', [])) && !$project->getAttribute('apis', [])['rest'] - && !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles())) + && !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles())) ) { throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED); } @@ -399,7 +399,7 @@ Http::init() $closestLimit = null; - $roles = $authorization->getRoles(); + $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -463,7 +463,7 @@ Http::init() $useCache = $route->getLabel('cache', false); if ($useCache) { $key = md5($request->getURI() . '*' . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER); - $cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key)); + $cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key)); $cache = new Cache( new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId()) ); @@ -476,18 +476,19 @@ Http::init() if ($type === 'bucket') { $bucketId = $parts[1] ?? null; + $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - - $isAPIKey = Auth::isAppUser($authorization->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAPIKey = Auth::isAppUser(Authorization::getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } $fileSecurity = $bucket->getAttribute('fileSecurity', false); - $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead())); + $validator = new Authorization(Database::PERMISSION_READ); + $valid = $validator->isValid($bucket->getRead()); + if (!$fileSecurity && !$valid) { throw new Exception(Exception::USER_UNAUTHORIZED); } @@ -498,7 +499,7 @@ Http::init() if ($fileSecurity && !$valid) { $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } else { - $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); } if ($file->isEmpty()) { @@ -522,7 +523,7 @@ Http::init() } }); -Http::init() +App::init() ->groups(['session']) ->inject('user') ->inject('request') @@ -542,12 +543,14 @@ Http::init() * Delete older sessions if the number of sessions have crossed * the session limit set for the project */ -Http::shutdown() +App::shutdown() ->groups(['session']) + ->inject('utopia') + ->inject('request') ->inject('response') ->inject('project') ->inject('dbForProject') - ->action(function (Response $response, Document $project, Database $dbForProject) { + ->action(function (App $utopia, Request $request, Response $response, Document $project, Database $dbForProject) { $sessionLimit = $project->getAttribute('auths', [])['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT; $session = $response->getPayload(); $userId = $session['userId'] ?? ''; @@ -574,9 +577,9 @@ Http::shutdown() $dbForProject->purgeCachedDocument('users', $userId); }); -Http::shutdown() +App::shutdown() ->groups(['api']) - ->inject('route') + ->inject('utopia') ->inject('request') ->inject('response') ->inject('project') @@ -592,29 +595,7 @@ Http::shutdown() ->inject('queueForFunctions') ->inject('mode') ->inject('dbForConsole') - ->inject('authorization') - ->action(function ( - Route $route, - Request $request, - Response $response, - Document $project, - Document $user, - Event $queueForEvents, - Audit $queueForAudits, - Usage $queueForUsage, - Delete $queueForDeletes, - EventDatabase $queueForDatabase, - Build $queueForBuilds, - Messaging $queueForMessaging, - Database $dbForProject, - Func $queueForFunctions, - string $mode, - Database $dbForConsole, - Authorization $authorization, - ) use ($parseLabel) { - if (!empty($user) && !$user->isEmpty() && empty($user->getInternalId())) { - $user = $authorization->skip(fn () => $dbForProject->getDocument('users', $user->getId())); - } + ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Usage $queueForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Database $dbForProject, Func $queueForFunctions, string $mode, Database $dbForConsole) use ($parseLabel) { $responsePayload = $response->getPayload(); @@ -674,6 +655,7 @@ Http::shutdown() } } + $route = $utopia->getRoute(); $requestParams = $route->getParamsValues(); /** @@ -743,11 +725,11 @@ Http::shutdown() $key = md5($request->getURI() . '*' . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER); $signature = md5($data['payload']); - $cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key)); + $cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key)); $accessedAt = $cacheLog->getAttribute('accessedAt', ''); $now = DateTime::now(); if ($cacheLog->isEmpty()) { - $authorization->skip(fn () => $dbForProject->createDocument('cache', new Document([ + Authorization::skip(fn () => $dbForProject->createDocument('cache', new Document([ '$id' => $key, 'resource' => $resource, 'resourceType' => $resourceType, @@ -757,7 +739,7 @@ Http::shutdown() ]))); } elseif (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_CACHE_UPDATE)) > $accessedAt) { $cacheLog->setAttribute('accessedAt', $now); - $authorization->skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog)); + Authorization::skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog)); } if ($signature !== $cacheLog->getAttribute('signature')) { @@ -769,8 +751,10 @@ Http::shutdown() } } + + if ($project->getId() !== 'console') { - if (!Auth::isPrivilegedUser($authorization->getRoles())) { + if (!Auth::isPrivilegedUser(Authorization::getRoles())) { $fileSize = 0; $file = $request->getFiles('file'); if (!empty($file)) { @@ -795,7 +779,7 @@ Http::shutdown() $accessedAt = $project->getAttribute('accessedAt', ''); if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) { $project->setAttribute('accessedAt', DateTime::now()); - $authorization->skip(fn () => $dbForConsole->updateDocument('projects', $project->getId(), $project)); + Authorization::skip(fn () => $dbForConsole->updateDocument('projects', $project->getId(), $project)); } } @@ -816,16 +800,10 @@ Http::shutdown() } }); -Http::init() +App::init() ->groups(['usage']) ->action(function () { if (System::getEnv('_APP_USAGE_STATS', 'enabled') !== 'enabled') { throw new Exception(Exception::GENERAL_USAGE_DISABLED); } }); - -Http::shutdown() - ->inject('connections') - ->action(function (Connections $connections) { - $connections->reclaim(); - }); diff --git a/app/controllers/shared/api/auth.php b/app/controllers/shared/api/auth.php index 224dcb3392..53aacabe21 100644 --- a/app/controllers/shared/api/auth.php +++ b/app/controllers/shared/api/auth.php @@ -4,14 +4,13 @@ use Appwrite\Auth\Auth; use Appwrite\Extend\Exception; use Appwrite\Utopia\Request; use MaxMind\Db\Reader; +use Utopia\App; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -use Utopia\Http\Http; -use Utopia\Http\Route; use Utopia\System\System; -Http::init() +App::init() ->groups(['mfaProtected']) ->inject('session') ->action(function (Document $session) { @@ -30,14 +29,13 @@ Http::init() } }); -Http::init() +App::init() ->groups(['auth']) - ->inject('route') + ->inject('utopia') ->inject('request') ->inject('project') ->inject('geodb') - ->inject('authorization') - ->action(function (Route $route, Request $request, Document $project, Reader $geodb, Authorization $authorization) { + ->action(function (App $utopia, Request $request, Document $project, Reader $geodb) { $denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', ''); if (!empty($denylist && $project->getId() === 'console')) { $countries = explode(',', $denylist); @@ -48,17 +46,15 @@ Http::init() } } - $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); - $isAppUser = Auth::isAppUser($authorization->getRoles()); + $route = $utopia->match($request); + + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); + $isAppUser = Auth::isAppUser(Authorization::getRoles()); if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs return; } - if ($route->getLabel('sdk.namespace', '') === 'graphql') { // Skip for graphQL recursive call - return; - } - $auths = $project->getAttribute('auths', []); switch ($route->getLabel('auth.type', '')) { case 'emailPassword': diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php index 60be4acf54..c02e140270 100644 --- a/app/controllers/web/console.php +++ b/app/controllers/web/console.php @@ -2,9 +2,9 @@ use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; -use Utopia\Http\Http; +use Utopia\App; -Http::init() +App::init() ->groups(['web']) ->inject('request') ->inject('response') @@ -16,7 +16,7 @@ Http::init() ; }); -Http::get('/') +App::get('/') ->alias('auth/*') ->alias('/invite') ->alias('/login') diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php index 3577061d69..27b2614c37 100644 --- a/app/controllers/web/home.php +++ b/app/controllers/web/home.php @@ -1,10 +1,10 @@ desc('Get Version') ->groups(['home', 'web']) ->label('scope', 'public') diff --git a/app/http.php b/app/http.php index 320621ad33..bec772c770 100644 --- a/app/http.php +++ b/app/http.php @@ -1,242 +1,335 @@ true, - 'http_compression' => true, - 'http_compression_level' => 6, - 'package_max_length' => $payloadSize, - 'buffer_output_size' => $payloadSize, - // Server - // 'log_level' => 0, - 'dispatch_mode' => 2, - 'worker_num' => $workerNumber, - 'reactor_num' => swoole_cpu_num() * 2, - 'open_cpu_affinity' => true, - // Coroutine - 'enable_coroutine' => true, - 'send_yield' => true, - 'tcp_fastopen' => true, -]); +$http + ->set([ + 'worker_num' => $workerNumber, + 'open_http2_protocol' => true, + 'http_compression' => true, + 'http_compression_level' => 6, + 'package_max_length' => $payloadSize, + 'buffer_output_size' => $payloadSize, + ]); -$http = new Http($server, $container, 'UTC'); -$http->setRequestClass(Request::class); -$http->setResponseClass(Response::class); +$http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) { + Console::success('Worker ' . ++$workerId . ' started successfully'); +}); -Http::onStart() - ->inject('authorization') - ->inject('cache') - ->inject('pools') - ->inject('connections') - ->action(function (Authorization $authorization, Cache $cache, array $pools, Connections $connections) { - try { - // wait for database to be ready - $attempts = 0; - $max = 15; - $sleep = 2; +$http->on(Constant::EVENT_BEFORE_RELOAD, function ($server, $workerId) { + Console::success('Starting reload...'); +}); - do { - try { - $attempts++; - $pool = $pools['pools-console-console']['pool']; - $dsn = $pools['pools-console-console']['dsn']; - $connection = $pool->get(); - $connections->add($connection, $pool); +$http->on(Constant::EVENT_AFTER_RELOAD, function ($server, $workerId) { + Console::success('Reload completed...'); +}); - $adapter = match ($dsn->getScheme()) { - 'mariadb' => new MariaDB($connection), - 'mysql' => new MySQL($connection), - default => null - }; +include __DIR__ . '/controllers/general.php'; - $adapter->setDatabase($dsn->getPath()); +$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $register) { + $app = new App('UTC'); - $dbForConsole = new Database($adapter, $cache); - $dbForConsole->setAuthorization($authorization); + go(function () use ($register, $app) { + $pools = $register->get('pools'); + /** @var Group $pools */ + App::setResource('pools', fn () => $pools); - $dbForConsole - ->setNamespace('_console') - ->setMetadata('host', \gethostname()) - ->setMetadata('project', 'console') - ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); + // wait for database to be ready + $attempts = 0; + $max = 10; + $sleep = 1; - $dbForConsole->ping(); - break; // leave the do-while if successful - } catch (\Throwable $e) { - Console::warning("Database not ready. Retrying connection ({$attempts})..."); - if ($attempts >= $max) { - throw new \Exception('Failed to connect to database: ' . $e->getMessage()); - } - sleep($sleep); + do { + try { + $attempts++; + $dbForConsole = $app->getResource('dbForConsole'); + /** @var Utopia\Database\Database $dbForConsole */ + break; // leave the do-while if successful + } catch (\Throwable $e) { + Console::warning("Database not ready. Retrying connection ({$attempts})..."); + if ($attempts >= $max) { + throw new \Exception('Failed to connect to database: ' . $e->getMessage()); } - } while ($attempts < $max); + sleep($sleep); + } + } while ($attempts < $max); - Console::success('[Setup] - Server database init started...'); + Console::success('[Setup] - Server database init started...'); + + try { + Console::success('[Setup] - Creating database: appwrite...'); + $dbForConsole->create(); + } catch (\Throwable $e) { + Console::success('[Setup] - Skip: metadata table already exists'); + } + + if ($dbForConsole->getCollection(Audit::COLLECTION)->isEmpty()) { + $audit = new Audit($dbForConsole); + $audit->setup(); + } + + if ($dbForConsole->getCollection(TimeLimit::COLLECTION)->isEmpty()) { + $adapter = new TimeLimit("", 0, 1, $dbForConsole); + $adapter->setup(); + } + + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $consoleCollections = $collections['console']; + foreach ($consoleCollections as $key => $collection) { + if (($collection['$collection'] ?? '') !== Database::METADATA) { + continue; + } + if (!$dbForConsole->getCollection($key)->isEmpty()) { + continue; + } + + Console::success('[Setup] - Creating 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'], + ]); + } + + $dbForConsole->createCollection($key, $attributes, $indexes); + } + + if ($dbForConsole->getDocument('buckets', 'default')->isEmpty() && !$dbForConsole->exists($dbForConsole->getDatabase(), 'bucket_1')) { + Console::success('[Setup] - Creating default bucket...'); + $dbForConsole->createDocument('buckets', new Document([ + '$id' => ID::custom('default'), + '$collection' => ID::custom('buckets'), + 'name' => 'Default', + 'maximumFileSize' => (int) System::getEnv('_APP_STORAGE_LIMIT', 0), // 10MB + 'allowedFileExtensions' => [], + 'enabled' => true, + 'compression' => 'gzip', + 'encryption' => true, + 'antivirus' => true, + 'fileSecurity' => true, + '$permissions' => [ + Permission::create(Role::any()), + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'search' => 'buckets Default', + ])); + + $bucket = $dbForConsole->getDocument('buckets', 'default'); + + Console::success('[Setup] - Creating files collection for default bucket...'); + $files = $collections['buckets']['files'] ?? []; + if (empty($files)) { + throw new Exception('Files collection is not configured.'); + } + + $attributes = []; + $indexes = []; + + foreach ($files['attributes'] as $attribute) { + $attributes[] = new Document([ + '$id' => ID::custom($attribute['$id']), + 'type' => $attribute['type'], + 'size' => $attribute['size'], + 'required' => $attribute['required'], + 'signed' => $attribute['signed'], + 'array' => $attribute['array'], + 'filters' => $attribute['filters'], + 'default' => $attribute['default'] ?? null, + 'format' => $attribute['format'] ?? '' + ]); + } + + foreach ($files['indexes'] as $index) { + $indexes[] = new Document([ + '$id' => ID::custom($index['$id']), + 'type' => $index['type'], + 'attributes' => $index['attributes'], + 'lengths' => $index['lengths'], + 'orders' => $index['orders'], + ]); + } + + $dbForConsole->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes); + } + + $pools->reclaim(); + + Console::success('[Setup] - Server database init completed...'); + }); + + Console::success('Server started successfully (max payload is ' . number_format($payloadSize) . ' bytes)'); + Console::info("Master pid {$http->master_pid}, manager pid {$http->manager_pid}"); + + // listen ctrl + c + Process::signal(2, function () use ($http) { + Console::log('Stop by Ctrl+C'); + $http->shutdown(); + }); +}); + +$http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register) { + App::setResource('swooleRequest', fn () => $swooleRequest); + App::setResource('swooleResponse', fn () => $swooleResponse); + + $request = new Request($swooleRequest); + $response = new Response($swooleResponse); + + if (Files::isFileLoaded($request->getURI())) { + $time = (60 * 60 * 24 * 365 * 2); // 45 days cache + + $response + ->setContentType(Files::getFileMimeType($request->getURI())) + ->addHeader('Cache-Control', 'public, max-age=' . $time) + ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache + ->send(Files::getFileContents($request->getURI())); + + return; + } + + $app = new App('UTC'); + + $pools = $register->get('pools'); + App::setResource('pools', fn () => $pools); + + try { + Authorization::cleanRoles(); + Authorization::setRole(Role::any()->toString()); + + $app->run($request, $response); + } catch (\Throwable $th) { + $version = System::getEnv('_APP_VERSION', 'UNKNOWN'); + + $logger = $app->getResource("logger"); + if ($logger) { + try { + /** @var Utopia\Database\Document $user */ + $user = $app->getResource('user'); + } catch (\Throwable $_th) { + // All good, user is optional information for logger + } + + $route = $app->getRoute(); + + $log = $app->getResource("log"); + + if (isset($user) && !$user->isEmpty()) { + $log->setUser(new User($user->getId())); + } + + $log->setNamespace("http"); + $log->setServer(\gethostname()); + $log->setVersion($version); + $log->setType(Log::TYPE_ERROR); + $log->setMessage($th->getMessage()); + + $log->addTag('method', $route->getMethod()); + $log->addTag('url', $route->getPath()); + $log->addTag('verboseType', get_class($th)); + $log->addTag('code', $th->getCode()); + // $log->addTag('projectId', $project->getId()); // TODO: Figure out how to get ProjectID, if it becomes relevant + $log->addTag('hostname', $request->getHostname()); + $log->addTag('locale', (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', ''))); + + $log->addExtra('file', $th->getFile()); + $log->addExtra('line', $th->getLine()); + $log->addExtra('trace', $th->getTraceAsString()); + $log->addExtra('roles', Authorization::getRoles()); + + $action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD"); + $log->setAction($action); + + $isProduction = System::getEnv('_APP_ENV', 'development') === 'production'; + $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING); try { - Console::success('[Setup] - Creating database: appwrite...'); - $dbForConsole->create(); - } catch (\Throwable $e) { - Console::success('[Setup] - Skip: metadata table already exists'); - return true; + $responseCode = $logger->addLog($log); + Console::info('Error log pushed with status code: ' . $responseCode); + } catch (Throwable $th) { + Console::error('Error pushing log: ' . $th->getMessage()); } - - if ($dbForConsole->getCollection(Audit::COLLECTION)->isEmpty()) { - $audit = new Audit($dbForConsole); - $audit->setup(); - } - - if ($dbForConsole->getCollection(TimeLimit::COLLECTION)->isEmpty()) { - $abuse = new TimeLimit("", 0, 1, $dbForConsole); - $abuse->setup(); - } - - /** @var array $collections */ - $collections = Config::getParam('collections', []); - $consoleCollections = $collections['console']; - foreach ($consoleCollections as $key => $collection) { - if (($collection['$collection'] ?? '') !== Database::METADATA) { - continue; - } - if (!$dbForConsole->getCollection($key)->isEmpty()) { - continue; - } - - Console::success('[Setup] - Creating 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'], - ]); - } - - $dbForConsole->createCollection($key, $attributes, $indexes); - } - - if ($dbForConsole->getDocument('buckets', 'default')->isEmpty() && !$dbForConsole->exists($dbForConsole->getDatabase(), 'bucket_1')) { - Console::success('[Setup] - Creating default bucket...'); - $dbForConsole->createDocument('buckets', new Document([ - '$id' => ID::custom('default'), - '$collection' => ID::custom('buckets'), - 'name' => 'Default', - 'maximumFileSize' => (int) System::getEnv('_APP_STORAGE_LIMIT', 0), // 10MB - 'allowedFileExtensions' => [], - 'enabled' => true, - 'compression' => 'gzip', - 'encryption' => true, - 'antivirus' => true, - 'fileSecurity' => true, - '$permissions' => [ - Permission::create(Role::any()), - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - 'search' => 'buckets Default', - ])); - - $bucket = $dbForConsole->getDocument('buckets', 'default'); - - Console::success('[Setup] - Creating files collection for default bucket...'); - - $files = $collections['buckets']['files'] ?? []; - if (empty($files)) { - throw new Exception('Files collection is not configured.'); - } - - $attributes = []; - $indexes = []; - - foreach ($files['attributes'] as $attribute) { - $attributes[] = new Document([ - '$id' => ID::custom($attribute['$id']), - 'type' => $attribute['type'], - 'size' => $attribute['size'], - 'required' => $attribute['required'], - 'signed' => $attribute['signed'], - 'array' => $attribute['array'], - 'filters' => $attribute['filters'], - 'default' => $attribute['default'] ?? null, - 'format' => $attribute['format'] ?? '' - ]); - } - - foreach ($files['indexes'] as $index) { - $indexes[] = new Document([ - '$id' => ID::custom($index['$id']), - 'type' => $index['type'], - 'attributes' => $index['attributes'], - 'lengths' => $index['lengths'], - 'orders' => $index['orders'], - ]); - } - - $dbForConsole->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes); - } - - $connections->reclaim(); - - Console::success('[Setup] - Server database init completed...'); - Console::success('Server started successfully'); - } catch (\Throwable $e) { - Console::warning('Database not ready: ' . $e->getMessage()); - exit(1); } - }); -Http::init() - ->inject('authorization') - ->action(function (Authorization $authorization) { - $authorization->cleanRoles(); - $authorization->addRole(Role::any()->toString()); - }); + Console::error('[Error] Type: ' . get_class($th)); + Console::error('[Error] Message: ' . $th->getMessage()); + Console::error('[Error] File: ' . $th->getFile()); + Console::error('[Error] Line: ' . $th->getLine()); + + $swooleResponse->setStatusCode(500); + + $output = ((App::isDevelopment())) ? [ + 'message' => 'Error: ' . $th->getMessage(), + 'code' => 500, + 'file' => $th->getFile(), + 'line' => $th->getLine(), + 'trace' => $th->getTrace(), + 'version' => $version, + ] : [ + 'message' => 'Error: Server Error', + 'code' => 500, + 'version' => $version, + ]; + + $swooleResponse->end(\json_encode($output)); + } finally { + $pools->reclaim(); + } +}); $http->start(); diff --git a/app/init.php b/app/init.php index 0f9e8f0ef6..c2777f36f1 100644 --- a/app/init.php +++ b/app/init.php @@ -1,12 +1,26 @@ $value], JSON_PRESERVE_ZERO_FRACTION); + }, + function (mixed $value) { + if (is_null($value)) { + return; + } -$registry->set('logger', function () { + return json_decode($value, true)['value']; + } +); + +Database::addFilter( + 'enum', + function (mixed $value, Document $attribute) { + if ($attribute->isSet('elements')) { + $attribute->removeAttribute('elements'); + } + + return $value; + }, + function (mixed $value, Document $attribute) { + $formatOptions = \json_decode($attribute->getAttribute('formatOptions', '[]'), true); + if (isset($formatOptions['elements'])) { + $attribute->setAttribute('elements', $formatOptions['elements']); + } + + return $value; + } +); + +Database::addFilter( + 'range', + function (mixed $value, Document $attribute) { + if ($attribute->isSet('min')) { + $attribute->removeAttribute('min'); + } + if ($attribute->isSet('max')) { + $attribute->removeAttribute('max'); + } + + return $value; + }, + function (mixed $value, Document $attribute) { + $formatOptions = json_decode($attribute->getAttribute('formatOptions', '[]'), true); + if (isset($formatOptions['min']) || isset($formatOptions['max'])) { + $attribute + ->setAttribute('min', $formatOptions['min']) + ->setAttribute('max', $formatOptions['max']); + } + + return $value; + } +); + +Database::addFilter( + 'subQueryAttributes', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + $attributes = $database->find('attributes', [ + Query::equal('collectionInternalId', [$document->getInternalId()]), + Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]), + Query::limit($database->getLimitForAttributes()), + ]); + + foreach ($attributes as $attribute) { + if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) { + $options = $attribute->getAttribute('options'); + foreach ($options as $key => $value) { + $attribute->setAttribute($key, $value); + } + $attribute->removeAttribute('options'); + } + } + + return $attributes; + } +); + +Database::addFilter( + 'subQueryIndexes', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database + ->find('indexes', [ + Query::equal('collectionInternalId', [$document->getInternalId()]), + Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]), + Query::limit($database->getLimitForIndexes()), + ]); + } +); + +Database::addFilter( + 'subQueryPlatforms', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database + ->find('platforms', [ + Query::equal('projectInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ]); + } +); + +Database::addFilter( + 'subQueryKeys', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database + ->find('keys', [ + Query::equal('projectInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ]); + } +); + +Database::addFilter( + 'subQueryWebhooks', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database + ->find('webhooks', [ + Query::equal('projectInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ]); + } +); + +Database::addFilter( + 'subQuerySessions', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return Authorization::skip(fn () => $database->find('sessions', [ + Query::equal('userInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ])); + } +); + +Database::addFilter( + 'subQueryTokens', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return Authorization::skip(fn () => $database + ->find('tokens', [ + Query::equal('userInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ])); + } +); + +Database::addFilter( + 'subQueryChallenges', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return Authorization::skip(fn () => $database + ->find('challenges', [ + Query::equal('userInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ])); + } +); + +Database::addFilter( + 'subQueryAuthenticators', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return Authorization::skip(fn () => $database + ->find('authenticators', [ + Query::equal('userInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ])); + } +); + +Database::addFilter( + 'subQueryMemberships', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return Authorization::skip(fn () => $database + ->find('memberships', [ + Query::equal('userInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ])); + } +); + +Database::addFilter( + 'subQueryVariables', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database + ->find('variables', [ + Query::equal('resourceInternalId', [$document->getInternalId()]), + Query::equal('resourceType', ['function']), + Query::limit(APP_LIMIT_SUBQUERY), + ]); + } +); + +Database::addFilter( + 'encrypt', + function (mixed $value) { + $key = System::getEnv('_APP_OPENSSL_KEY_V1'); + $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); + $tag = null; + + return json_encode([ + 'data' => OpenSSL::encrypt($value, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag), + 'method' => OpenSSL::CIPHER_AES_128_GCM, + 'iv' => \bin2hex($iv), + 'tag' => \bin2hex($tag ?? ''), + 'version' => '1', + ]); + }, + function (mixed $value) { + if (is_null($value)) { + return; + } + $value = json_decode($value, true); + $key = System::getEnv('_APP_OPENSSL_KEY_V' . $value['version']); + + return OpenSSL::decrypt($value['data'], $value['method'], $key, 0, hex2bin($value['iv']), hex2bin($value['tag'])); + } +); + +Database::addFilter( + 'subQueryProjectVariables', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database + ->find('variables', [ + Query::equal('resourceType', ['project']), + Query::limit(APP_LIMIT_SUBQUERY) + ]); + } +); + +Database::addFilter( + 'userSearch', + function (mixed $value, Document $user) { + $searchValues = [ + $user->getId(), + $user->getAttribute('email', ''), + $user->getAttribute('name', ''), + $user->getAttribute('phone', '') + ]; + + foreach ($user->getAttribute('labels', []) as $label) { + $searchValues[] = 'label:' . $label; + } + + $search = implode(' ', \array_filter($searchValues)); + + return $search; + }, + function (mixed $value) { + return $value; + } +); + +Database::addFilter( + 'subQueryTargets', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return Authorization::skip(fn () => $database + ->find('targets', [ + Query::equal('userInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY) + ])); + } +); + +Database::addFilter( + 'subQueryTopicTargets', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + $targetIds = Authorization::skip(fn () => \array_map( + fn ($document) => $document->getAttribute('targetInternalId'), + $database->find('subscribers', [ + Query::equal('topicInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBSCRIBERS_SUBQUERY) + ]) + )); + if (\count($targetIds) > 0) { + return $database->skipValidation(fn () => $database->find('targets', [ + Query::equal('$internalId', $targetIds) + ])); + } + return []; + } +); + +Database::addFilter( + 'providerSearch', + function (mixed $value, Document $provider) { + $searchValues = [ + $provider->getId(), + $provider->getAttribute('name', ''), + $provider->getAttribute('provider', ''), + $provider->getAttribute('type', '') + ]; + + $search = \implode(' ', \array_filter($searchValues)); + + return $search; + }, + function (mixed $value) { + return $value; + } +); + +Database::addFilter( + 'topicSearch', + function (mixed $value, Document $topic) { + $searchValues = [ + $topic->getId(), + $topic->getAttribute('name', ''), + $topic->getAttribute('description', ''), + ]; + + $search = \implode(' ', \array_filter($searchValues)); + + return $search; + }, + function (mixed $value) { + return $value; + } +); + +Database::addFilter( + 'messageSearch', + function (mixed $value, Document $message) { + $searchValues = [ + $message->getId(), + $message->getAttribute('description', ''), + $message->getAttribute('status', ''), + ]; + + $data = \json_decode($message->getAttribute('data', []), true); + $providerType = $message->getAttribute('providerType', ''); + + if ($providerType === MESSAGE_TYPE_EMAIL) { + $searchValues = \array_merge($searchValues, [$data['subject'], MESSAGE_TYPE_EMAIL]); + } elseif ($providerType === MESSAGE_TYPE_SMS) { + $searchValues = \array_merge($searchValues, [$data['content'], MESSAGE_TYPE_SMS]); + } else { + $searchValues = \array_merge($searchValues, [$data['title'], MESSAGE_TYPE_PUSH]); + } + + $search = \implode(' ', \array_filter($searchValues)); + + return $search; + }, + function (mixed $value) { + return $value; + } +); + +/** + * DB Formats + */ +Structure::addFormat(APP_DATABASE_ATTRIBUTE_EMAIL, function () { + return new Email(); +}, Database::VAR_STRING); + +Structure::addFormat(APP_DATABASE_ATTRIBUTE_DATETIME, function () { + return new DatetimeValidator(); +}, Database::VAR_DATETIME); + +Structure::addFormat(APP_DATABASE_ATTRIBUTE_ENUM, function ($attribute) { + $elements = $attribute['formatOptions']['elements']; + return new WhiteList($elements, true); +}, Database::VAR_STRING); + +Structure::addFormat(APP_DATABASE_ATTRIBUTE_IP, function () { + return new IP(); +}, Database::VAR_STRING); + +Structure::addFormat(APP_DATABASE_ATTRIBUTE_URL, function () { + return new URL(); +}, Database::VAR_STRING); + +Structure::addFormat(APP_DATABASE_ATTRIBUTE_INT_RANGE, function ($attribute) { + $min = $attribute['formatOptions']['min'] ?? -INF; + $max = $attribute['formatOptions']['max'] ?? INF; + return new Range($min, $max, Range::TYPE_INTEGER); +}, Database::VAR_INTEGER); + +Structure::addFormat(APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, function ($attribute) { + $min = $attribute['formatOptions']['min'] ?? -INF; + $max = $attribute['formatOptions']['max'] ?? INF; + return new Range($min, $max, Range::TYPE_FLOAT); +}, Database::VAR_FLOAT); + +/* + * Registry + */ +$register->set('logger', function () { // Register error logger $providerName = System::getEnv('_APP_LOGGING_PROVIDER', ''); $providerConfig = System::getEnv('_APP_LOGGING_CONFIG', ''); @@ -105,12 +777,12 @@ $registry->set('logger', function () { default => ['key' => $loggingProvider->getHost()], }; } catch (Throwable $th) { - Console::warning('Using deprecated logging configuration. Please update your configuration to use DSN format.' . $th->getMessage()); // Fallback for older Appwrite versions up to 1.5.x that use _APP_LOGGING_PROVIDER and _APP_LOGGING_CONFIG environment variables + Console::warning('Using deprecated logging configuration. Please update your configuration to use DSN format.' . $th->getMessage()); $configChunks = \explode(";", $providerConfig); $providerConfig = match ($providerName) { - 'sentry' => ['key' => $configChunks[0], 'projectId' => $configChunks[1] ?? '', 'host' => '',], + 'sentry' => [ 'key' => $configChunks[0], 'projectId' => $configChunks[1] ?? '', 'host' => '',], 'logowl' => ['ticket' => $configChunks[0] ?? '', 'host' => ''], default => ['key' => $providerConfig], }; @@ -141,167 +813,200 @@ $registry->set('logger', function () { return; } - $logger = new Logger($adapter); - $logger->setSample(0.4); - return $logger; + return new Logger($adapter); }); -$registry->set('geodb', function () { - /** - * @disregard P1009 Undefined type - */ - return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2024-09.mmdb'); -}); +$register->set('pools', function () { + $group = new Group(); -$registry->set('hooks', function () { - return new Hooks(); -}); + $fallbackForDB = 'db_main=' . AppwriteURL::unparse([ + 'scheme' => 'mariadb', + 'host' => System::getEnv('_APP_DB_HOST', 'mariadb'), + 'port' => System::getEnv('_APP_DB_PORT', '3306'), + 'user' => System::getEnv('_APP_DB_USER', ''), + 'pass' => System::getEnv('_APP_DB_PASS', ''), + 'path' => System::getEnv('_APP_DB_SCHEMA', ''), + ]); + $fallbackForRedis = 'redis_main=' . AppwriteURL::unparse([ + 'scheme' => 'redis', + 'host' => System::getEnv('_APP_REDIS_HOST', 'redis'), + 'port' => System::getEnv('_APP_REDIS_PORT', '6379'), + 'user' => System::getEnv('_APP_REDIS_USER', ''), + 'pass' => System::getEnv('_APP_REDIS_PASS', ''), + ]); -$registry->set( - 'pools', - (function () { - $fallbackForDB = 'db_main=' . URL::unparse([ - 'scheme' => 'mariadb', - 'host' => System::getEnv('_APP_DB_HOST', 'mariadb'), - 'port' => System::getEnv('_APP_DB_PORT', '3306'), - 'user' => System::getEnv('_APP_DB_USER', ''), - 'pass' => System::getEnv('_APP_DB_PASS', ''), - 'path' => System::getEnv('_APP_DB_SCHEMA', ''), - ]); - $fallbackForRedis = 'redis_main=' . URL::unparse([ - 'scheme' => 'redis', - 'host' => System::getEnv('_APP_REDIS_HOST', 'redis'), - 'port' => System::getEnv('_APP_REDIS_PORT', '6379'), - 'user' => System::getEnv('_APP_REDIS_USER', ''), - 'pass' => System::getEnv('_APP_REDIS_PASS', ''), - ]); + $connections = [ + 'console' => [ + 'type' => 'database', + 'dsns' => System::getEnv('_APP_CONNECTIONS_DB_CONSOLE', $fallbackForDB), + 'multiple' => false, + 'schemes' => ['mariadb', 'mysql'], + ], + 'database' => [ + 'type' => 'database', + 'dsns' => System::getEnv('_APP_CONNECTIONS_DB_PROJECT', $fallbackForDB), + 'multiple' => true, + 'schemes' => ['mariadb', 'mysql'], + ], + 'queue' => [ + 'type' => 'queue', + 'dsns' => System::getEnv('_APP_CONNECTIONS_QUEUE', $fallbackForRedis), + 'multiple' => false, + 'schemes' => ['redis'], + ], + 'pubsub' => [ + 'type' => 'pubsub', + 'dsns' => System::getEnv('_APP_CONNECTIONS_PUBSUB', $fallbackForRedis), + 'multiple' => false, + 'schemes' => ['redis'], + ], + 'cache' => [ + 'type' => 'cache', + 'dsns' => System::getEnv('_APP_CONNECTIONS_CACHE', $fallbackForRedis), + 'multiple' => true, + 'schemes' => ['redis'], + ], + ]; - $connections = [ - 'console' => [ - 'type' => 'database', - 'dsns' => System::getEnv('_APP_CONNECTIONS_DB_CONSOLE', $fallbackForDB), - 'multiple' => false, - 'schemes' => ['mariadb', 'mysql'], - ], - 'database' => [ - 'type' => 'database', - 'dsns' => System::getEnv('_APP_CONNECTIONS_DB_PROJECT', $fallbackForDB), - 'multiple' => true, - 'schemes' => ['mariadb', 'mysql'], - ], - 'queue' => [ - 'type' => 'queue', - 'dsns' => System::getEnv('_APP_CONNECTIONS_QUEUE', $fallbackForRedis), - 'multiple' => false, - 'schemes' => ['redis'], - ], - 'pubsub' => [ - 'type' => 'pubsub', - 'dsns' => System::getEnv('_APP_CONNECTIONS_PUBSUB', $fallbackForRedis), - 'multiple' => false, - 'schemes' => ['redis'], - ], - 'cache' => [ - 'type' => 'cache', - 'dsns' => System::getEnv('_APP_CONNECTIONS_CACHE', $fallbackForRedis), - 'multiple' => true, - 'schemes' => ['redis'], - ], - ]; + $maxConnections = System::getEnv('_APP_CONNECTIONS_MAX', 151); + $instanceConnections = $maxConnections / System::getEnv('_APP_POOL_CLIENTS', 14); - $pools = []; - $poolSize = (int)System::getEnv('_APP_POOL_SIZE', 64); + $multiprocessing = System::getEnv('_APP_SERVER_MULTIPROCESS', 'disabled') === 'enabled'; - foreach ($connections as $key => $connection) { - $dsns = $connection['dsns'] ?? ''; - $multiple = $connection['multiple'] ?? false; - $schemes = $connection['schemes'] ?? []; - $dsns = explode(',', $connection['dsns'] ?? ''); - $config = []; + if ($multiprocessing) { + $workerCount = swoole_cpu_num() * intval(System::getEnv('_APP_WORKER_PER_CORE', 6)); + } else { + $workerCount = 1; + } - foreach ($dsns as &$dsn) { - $dsn = explode('=', $dsn); - $name = ($multiple) ? $key . '_' . $dsn[0] : $key; - $config[] = $name; - $dsn = $dsn[1] ?? ''; + if ($workerCount > $instanceConnections) { + throw new \Exception('Pool size is too small. Increase the number of allowed database connections or decrease the number of workers.', 500); + } - if (empty($dsn)) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, "Missing value for DSN connection in {$key}"); - } + $poolSize = (int)($instanceConnections / $workerCount); - $dsn = new DSN($dsn); - $dsnHost = $dsn->getHost(); - $dsnPort = $dsn->getPort(); - $dsnUser = $dsn->getUser(); - $dsnPass = $dsn->getPassword(); - $dsnScheme = $dsn->getScheme(); - $dsnDatabase = $dsn->getPath(); + foreach ($connections as $key => $connection) { + $type = $connection['type'] ?? ''; + $multiple = $connection['multiple'] ?? false; + $schemes = $connection['schemes'] ?? []; + $config = []; + $dsns = explode(',', $connection['dsns'] ?? ''); + foreach ($dsns as &$dsn) { + $dsn = explode('=', $dsn); + $name = ($multiple) ? $key . '_' . $dsn[0] : $key; + $dsn = $dsn[1] ?? ''; + $config[] = $name; + if (empty($dsn)) { + //throw new Exception(Exception::GENERAL_SERVER_ERROR, "Missing value for DSN connection in {$key}"); + continue; + } - if (!in_array($dsnScheme, $schemes)) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, "Invalid console database scheme"); - } + $dsn = new DSN($dsn); + $dsnHost = $dsn->getHost(); + $dsnPort = $dsn->getPort(); + $dsnUser = $dsn->getUser(); + $dsnPass = $dsn->getPassword(); + $dsnScheme = $dsn->getScheme(); + $dsnDatabase = $dsn->getPath(); - /** - * Get Resource - * - * Creation could be reused accross connection types like database, cache, queue, etc. - * - * Resource assignment to an adapter will happen below. - */ - switch ($dsnScheme) { - case 'mysql': - case 'mariadb': - $pool = new PDOPool( - (new PDOConfig()) - ->withHost($dsnHost) - ->withPort($dsnPort) - ->withDbName($dsnDatabase) - ->withCharset('utf8mb4') - ->withUsername($dsnUser) - ->withPassword($dsnPass) - ->withOptions([ - // No need to set PDO::ATTR_ERRMODE it is overwitten in PDOProxy - // PDO::ATTR_TIMEOUT => 3, // Seconds - // PDO::ATTR_PERSISTENT => true, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::ATTR_EMULATE_PREPARES => true, - PDO::ATTR_STRINGIFY_FETCHES => true, - PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true, + if (!in_array($dsnScheme, $schemes)) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Invalid console database scheme"); + } - ]), - $poolSize - ); + /** + * Get Resource + * + * Creation could be reused across connection types like database, cache, queue, etc. + * + * Resource assignment to an adapter will happen below. + */ + $resource = match ($dsnScheme) { + 'mysql', + 'mariadb' => function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) { + return new PDOProxy(function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) { + return new PDO("mysql:host={$dsnHost};port={$dsnPort};dbname={$dsnDatabase};charset=utf8mb4", $dsnUser, $dsnPass, array( + PDO::ATTR_TIMEOUT => 3, // Seconds + PDO::ATTR_PERSISTENT => true, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => true, + PDO::ATTR_STRINGIFY_FETCHES => true + )); + }); + }, + 'redis' => function () use ($dsnHost, $dsnPort, $dsnPass) { + $redis = new Redis(); + @$redis->pconnect($dsnHost, (int)$dsnPort); + if ($dsnPass) { + $redis->auth($dsnPass); + } + $redis->setOption(Redis::OPT_READ_TIMEOUT, -1); + + return $redis; + }, + default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Invalid scheme'), + }; + + $pool = new Pool($name, $poolSize, function () use ($type, $resource, $dsn) { + // Get Adapter + switch ($type) { + case 'database': + $adapter = match ($dsn->getScheme()) { + 'mariadb' => new MariaDB($resource()), + 'mysql' => new MySQL($resource()), + default => null + }; + + $adapter->setDatabase($dsn->getPath()); break; - case 'redis': - $pool = new RedisPool( - (new RedisConfig()) - ->withHost($dsnHost) - ->withPort((int)$dsnPort) - ->withAuth($dsnPass ?? ''), - $poolSize - ); + case 'pubsub': + $adapter = $resource(); + break; + case 'queue': + $adapter = match ($dsn->getScheme()) { + 'redis' => new Queue\Connection\Redis($dsn->getHost(), $dsn->getPort()), + default => null + }; + break; + case 'cache': + $adapter = match ($dsn->getScheme()) { + 'redis' => new RedisCache($resource()), + default => null + }; break; default: - throw new Exception(Exception::GENERAL_SERVER_ERROR, "Invalid scheme"); + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Server error: Missing adapter implementation."); } - $pools['pools-' . $key . '-' . $name] = [ - 'pool' => $pool, - 'dsn' => $dsn, - ]; - } + return $adapter; + }); - Config::setParam('pools-' . $key, $config); + $group->add($pool); } - return function () use ($pools): array { - return $pools; - }; - })() -); + Config::setParam('pools-' . $key, $config); + } -$registry->set('smtp', function () { + return $group; +}); + +$register->set('db', function () { + // This is usually for our workers or CLI commands scope + $dbHost = System::getEnv('_APP_DB_HOST', ''); + $dbPort = System::getEnv('_APP_DB_PORT', ''); + $dbUser = System::getEnv('_APP_DB_USER', ''); + $dbPass = System::getEnv('_APP_DB_PASS', ''); + $dbScheme = System::getEnv('_APP_DB_SCHEMA', ''); + + return new PDO( + "mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4", + $dbUser, + $dbPass, + SQL::getPDOAttributes() + ); +}); + +$register->set('smtp', function () { $mail = new PHPMailer(true); $mail->isSMTP(); @@ -329,65 +1034,771 @@ $registry->set('smtp', function () { return $mail; }); - -$registry->set('promiseAdapter', function () { +$register->set('geodb', function () { + return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2024-09.mmdb'); +}); +$register->set('passwordsDictionary', function () { + $content = \file_get_contents(__DIR__ . '/assets/security/10k-common-passwords'); + $content = explode("\n", $content); + $content = array_flip($content); + return $content; +}); +$register->set('promiseAdapter', function () { return new Swoole(); }); +$register->set('hooks', function () { + return new Hooks(); +}); +/* + * Localization + */ +Locale::$exceptions = false; -$registry->set('db', function () { - // This is usually for our workers or CLI commands scope - $dbHost = System::getEnv('_APP_DB_HOST', ''); - $dbPort = System::getEnv('_APP_DB_PORT', ''); - $dbUser = System::getEnv('_APP_DB_USER', ''); - $dbPass = System::getEnv('_APP_DB_PASS', ''); - $dbScheme = System::getEnv('_APP_DB_SCHEMA', ''); +$locales = Config::getParam('locale-codes', []); - return new PDO( - "mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4", - $dbUser, - $dbPass, - SQL::getPDOAttributes() - ); +foreach ($locales as $locale) { + $code = $locale['code']; + + $path = __DIR__ . '/config/locale/translations/' . $code . '.json'; + + if (!\file_exists($path)) { + $path = __DIR__ . '/config/locale/translations/' . \substr($code, 0, 2) . '.json'; // if `ar-ae` doesn't exist, look for `ar` + if (!\file_exists($path)) { + $path = __DIR__ . '/config/locale/translations/en.json'; // if none translation exists, use default from `en.json` + } + } + + Locale::setLanguageFromJSON($code, $path); +} + +\stream_context_set_default([ // Set global user agent and http settings + 'http' => [ + 'method' => 'GET', + 'user_agent' => \sprintf( + APP_USERAGENT, + System::getEnv('_APP_VERSION', 'UNKNOWN'), + System::getEnv('_APP_EMAIL_SECURITY', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)) + ), + 'timeout' => 2, + ], +]); + +// Runtime Execution +App::setResource('log', fn () => new Log()); +App::setResource('logger', function ($register) { + return $register->get('logger'); +}, ['register']); + +App::setResource('hooks', function ($register) { + return $register->get('hooks'); +}, ['register']); + +App::setResource('register', fn () => $register); +App::setResource('locale', fn () => new Locale(System::getEnv('_APP_LOCALE', 'en'))); + +App::setResource('localeCodes', function () { + return array_map(fn ($locale) => $locale['code'], Config::getParam('locale-codes', [])); }); -// Autoload -class_exists(JWT::class, true); -class_exists(DSN::class, true); -class_exists(Log::class, true); -class_exists(TOTP::class, true); -class_exists(Mail::class, true); -class_exists(Func::class, true); -class_exists(Cache::class, true); -class_exists(Abuse::class, true); -class_exists(MySQL::class, true); -class_exists(Event::class, true); -class_exists(Audit::class, true); -class_exists(Usage::class, true); -class_exists(Local::class, true); -class_exists(Build::class, true); -class_exists(Locale::class, true); -class_exists(Delete::class, true); -class_exists(GitHub::class, true); -class_exists(Schema::class, true); -class_exists(Domain::class, true); -class_exists(Console::class, true); -class_exists(Request::class, true); -class_exists(MariaDB::class, true); -class_exists(Document::class, true); -class_exists(Sharding::class, true); -class_exists(Database::class, true); -class_exists(Hostname::class, true); -class_exists(TimeLimit::class, true); -class_exists(Migration::class, true); -class_exists(Messaging::class, true); -class_exists(CacheRedis::class, true); -class_exists(Connections::class, true); -class_exists(Certificate::class, true); -class_exists(EventDatabase::class, true); -class_exists(Authorization::class, true); -class_exists(Authentication::class, true); -class_exists(Queue\Connection\Redis::class, true); +// Queues +App::setResource('queue', function (Group $pools) { + return $pools->get('queue')->pop()->getResource(); +}, ['pools']); +App::setResource('queueForMessaging', function (Connection $queue) { + return new Messaging($queue); +}, ['queue']); +App::setResource('queueForMails', function (Connection $queue) { + return new Mail($queue); +}, ['queue']); +App::setResource('queueForBuilds', function (Connection $queue) { + return new Build($queue); +}, ['queue']); +App::setResource('queueForDatabase', function (Connection $queue) { + return new EventDatabase($queue); +}, ['queue']); +App::setResource('queueForDeletes', function (Connection $queue) { + return new Delete($queue); +}, ['queue']); +App::setResource('queueForEvents', function (Connection $queue) { + return new Event($queue); +}, ['queue']); +App::setResource('queueForAudits', function (Connection $queue) { + return new Audit($queue); +}, ['queue']); +App::setResource('queueForFunctions', function (Connection $queue) { + return new Func($queue); +}, ['queue']); +App::setResource('queueForUsage', function (Connection $queue) { + return new Usage($queue); +}, ['queue']); +App::setResource('queueForCertificates', function (Connection $queue) { + return new Certificate($queue); +}, ['queue']); +App::setResource('queueForMigrations', function (Connection $queue) { + return new Migration($queue); +}, ['queue']); +App::setResource('clients', function ($request, $console, $project) { + $console->setAttribute('platforms', [ // Always allow current host + '$collection' => ID::custom('platforms'), + 'name' => 'Current Host', + 'type' => Origin::CLIENT_TYPE_WEB, + 'hostname' => $request->getHostname(), + ], Document::SET_TYPE_APPEND); -require_once __DIR__ . '/init/resources.php'; + $hostnames = explode(',', System::getEnv('_APP_CONSOLE_HOSTNAMES', '')); + $validator = new Hostname(); + foreach ($hostnames as $hostname) { + $hostname = trim($hostname); + if (!$validator->isValid($hostname)) { + continue; + } + $console->setAttribute('platforms', [ + '$collection' => ID::custom('platforms'), + 'type' => Origin::CLIENT_TYPE_WEB, + 'name' => $hostname, + 'hostname' => $hostname, + ], Document::SET_TYPE_APPEND); + } -Models::init(); + /** + * Get All verified client URLs for both console and current projects + * + Filter for duplicated entries + */ + $clientsConsole = \array_map( + fn ($node) => $node['hostname'], + \array_filter( + $console->getAttribute('platforms', []), + fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB) && !empty($node['hostname'])) + ) + ); + + $clients = $clientsConsole; + $platforms = $project->getAttribute('platforms', []); + + foreach ($platforms as $node) { + if ( + isset($node['type']) && + ($node['type'] === Origin::CLIENT_TYPE_WEB || + $node['type'] === Origin::CLIENT_TYPE_FLUTTER_WEB) && + !empty($node['hostname']) + ) { + $clients[] = $node['hostname']; + } + } + + return \array_unique($clients); +}, ['request', 'console', 'project']); + +App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForConsole) { + /** @var Appwrite\Utopia\Request $request */ + /** @var Appwrite\Utopia\Response $response */ + /** @var Utopia\Database\Document $project */ + /** @var Utopia\Database\Database $dbForProject */ + /** @var Utopia\Database\Database $dbForConsole */ + /** @var string $mode */ + + Authorization::setDefaultStatus(true); + + Auth::setCookieName('a_session_' . $project->getId()); + + if (APP_MODE_ADMIN === $mode) { + Auth::setCookieName('a_session_' . $console->getId()); + } + + $session = Auth::decodeSession( + $request->getCookie( + Auth::$cookieName, // Get sessions + $request->getCookie(Auth::$cookieName . '_legacy', '') + ) + ); + + // Get session from header for SSR clients + if (empty($session['id']) && empty($session['secret'])) { + $sessionHeader = $request->getHeader('x-appwrite-session', ''); + + if (!empty($sessionHeader)) { + $session = Auth::decodeSession($sessionHeader); + } + } + + // Get fallback session from old clients (no SameSite support) or clients who block 3rd-party cookies + if ($response) { + $response->addHeader('X-Debug-Fallback', 'false'); + } + + if (empty($session['id']) && empty($session['secret'])) { + if ($response) { + $response->addHeader('X-Debug-Fallback', 'true'); + } + $fallback = $request->getHeader('x-fallback-cookies', ''); + $fallback = \json_decode($fallback, true); + $session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : '')); + } + + Auth::$unique = $session['id'] ?? ''; + Auth::$secret = $session['secret'] ?? ''; + + if (APP_MODE_ADMIN !== $mode) { + if ($project->isEmpty()) { + $user = new Document([]); + } else { + if ($project->getId() === 'console') { + $user = $dbForConsole->getDocument('users', Auth::$unique); + } else { + $user = $dbForProject->getDocument('users', Auth::$unique); + } + } + } else { + $user = $dbForConsole->getDocument('users', Auth::$unique); + } + + if ( + $user->isEmpty() // Check a document has been found in the DB + || !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) + ) { // Validate user has valid login token + $user = new Document([]); + } + + // if (APP_MODE_ADMIN === $mode) { + // if ($user->find('teamInternalId', $project->getAttribute('teamInternalId'), 'memberships')) { + // Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users. + // } else { + // $user = new Document([]); + // } + // } + + $authJWT = $request->getHeader('x-appwrite-jwt', ''); + + if (!empty($authJWT) && !$project->isEmpty()) { // JWT authentication + $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0); + + try { + $payload = $jwt->decode($authJWT); + } catch (JWTException $error) { + throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage()); + } + + $jwtUserId = $payload['userId'] ?? ''; + if (!empty($jwtUserId)) { + $user = $dbForProject->getDocument('users', $jwtUserId); + } + + $jwtSessionId = $payload['sessionId'] ?? ''; + if (!empty($jwtSessionId)) { + if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token + $user = new Document([]); + } + } + } + + $dbForProject->setMetadata('user', $user->getId()); + $dbForConsole->setMetadata('user', $user->getId()); + + return $user; +}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForConsole']); + +App::setResource('project', function ($dbForConsole, $request, $console) { + /** @var Appwrite\Utopia\Request $request */ + /** @var Utopia\Database\Database $dbForConsole */ + /** @var Utopia\Database\Document $console */ + + $projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', '')); + + if (empty($projectId) || $projectId === 'console') { + return $console; + } + + $project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId)); + + return $project; +}, ['dbForConsole', 'request', 'console']); + +App::setResource('session', function (Document $user) { + if ($user->isEmpty()) { + return; + } + + $sessions = $user->getAttribute('sessions', []); + $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret); + + if (!$sessionId) { + return; + } + + foreach ($sessions as $session) {/** @var Document $session */ + if ($sessionId === $session->getId()) { + return $session; + } + } + + return; +}, ['user']); + +App::setResource('console', function () { + return new Document([ + '$id' => ID::custom('console'), + '$internalId' => ID::custom('console'), + 'name' => 'Appwrite', + '$collection' => ID::custom('projects'), + 'description' => 'Appwrite core engine', + 'logo' => '', + 'teamId' => null, + 'webhooks' => [], + 'keys' => [], + 'platforms' => [ + [ + '$collection' => ID::custom('platforms'), + 'name' => 'Localhost', + 'type' => Origin::CLIENT_TYPE_WEB, + 'hostname' => 'localhost', + ], // Current host is added on app init + ], + 'legalName' => '', + 'legalCountry' => '', + 'legalState' => '', + 'legalCity' => '', + 'legalAddress' => '', + 'legalTaxId' => '', + 'auths' => [ + 'mockNumbers' => [], + 'invites' => System::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled', + 'limit' => (System::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user + 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds + 'sessionAlerts' => System::getEnv('_APP_CONSOLE_SESSION_ALERTS', 'disabled') === 'enabled' + ], + 'authWhitelistEmails' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [], + 'authWhitelistIPs' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [], + 'oAuthProviders' => [ + 'githubEnabled' => true, + 'githubSecret' => System::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''), + 'githubAppid' => System::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '') + ], + ]); +}, []); + +App::setResource('dbForProject', function (Group $pools, Database $dbForConsole, Cache $cache, Document $project) { + if ($project->isEmpty() || $project->getId() === 'console') { + return $dbForConsole; + } + + try { + $dsn = new DSN($project->getAttribute('database')); + } catch (\InvalidArgumentException) { + // TODO: Temporary until all projects are using shared tables + $dsn = new DSN('mysql://' . $project->getAttribute('database')); + } + + $dbAdapter = $pools + ->get($dsn->getHost()) + ->pop() + ->getResource(); + + $database = new Database($dbAdapter, $cache); + + $database + ->setMetadata('host', \gethostname()) + ->setMetadata('project', $project->getId()) + ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); + + 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')); + } + + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + $database + ->setSharedTables(true) + ->setTenant($project->getInternalId()) + ->setNamespace($dsn->getParam('namespace')); + } else { + $database + ->setSharedTables(false) + ->setTenant(null) + ->setNamespace('_' . $project->getInternalId()); + } + + return $database; +}, ['pools', 'dbForConsole', 'cache', 'project']); + +App::setResource('dbForConsole', function (Group $pools, Cache $cache) { + $dbAdapter = $pools + ->get('console') + ->pop() + ->getResource(); + + $database = new Database($dbAdapter, $cache); + + $database + ->setNamespace('_console') + ->setMetadata('host', \gethostname()) + ->setMetadata('project', 'console') + ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); + + return $database; +}, ['pools', 'cache']); + +App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) { + $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools + + return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases) { + if ($project->isEmpty() || $project->getId() === 'console') { + return $dbForConsole; + } + + try { + $dsn = new DSN($project->getAttribute('database')); + } catch (\InvalidArgumentException) { + // TODO: Temporary until all projects are using shared tables + $dsn = new DSN('mysql://' . $project->getAttribute('database')); + } + + $configure = (function (Database $database) use ($project, $dsn) { + $database + ->setMetadata('host', \gethostname()) + ->setMetadata('project', $project->getId()) + ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); + + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + $database + ->setSharedTables(true) + ->setTenant($project->getInternalId()) + ->setNamespace($dsn->getParam('namespace')); + } else { + $database + ->setSharedTables(false) + ->setTenant(null) + ->setNamespace('_' . $project->getInternalId()); + } + }); + + if (isset($databases[$dsn->getHost()])) { + $database = $databases[$dsn->getHost()]; + $configure($database); + return $database; + } + + $dbAdapter = $pools + ->get($dsn->getHost()) + ->pop() + ->getResource(); + + $database = new Database($dbAdapter, $cache); + $databases[$dsn->getHost()] = $database; + $configure($database); + + return $database; + }; +}, ['pools', 'dbForConsole', 'cache']); + +App::setResource('cache', function (Group $pools) { + $list = Config::getParam('pools-cache', []); + $adapters = []; + + foreach ($list as $value) { + $adapters[] = $pools + ->get($value) + ->pop() + ->getResource() + ; + } + + return new Cache(new Sharding($adapters)); +}, ['pools']); + +App::setResource('deviceForLocal', function () { + return new Local(); +}); + +App::setResource('deviceForFiles', function ($project) { + return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId()); +}, ['project']); + +App::setResource('deviceForFunctions', function ($project) { + return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId()); +}, ['project']); + +App::setResource('deviceForBuilds', function ($project) { + return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId()); +}, ['project']); + +function getDevice(string $root, string $connection = ''): Device +{ + $connection = !empty($connection) ? $connection : System::getEnv('_APP_CONNECTIONS_STORAGE', ''); + + if (!empty($connection)) { + $acl = 'private'; + $device = Storage::DEVICE_LOCAL; + $accessKey = ''; + $accessSecret = ''; + $bucket = ''; + $region = ''; + + try { + $dsn = new DSN($connection); + $device = $dsn->getScheme(); + $accessKey = $dsn->getUser() ?? ''; + $accessSecret = $dsn->getPassword() ?? ''; + $bucket = $dsn->getPath() ?? ''; + $region = $dsn->getParam('region'); + } catch (\Throwable $e) { + Console::warning($e->getMessage() . 'Invalid DSN. Defaulting to Local device.'); + } + + switch ($device) { + case Storage::DEVICE_S3: + return new S3($root, $accessKey, $accessSecret, $bucket, $region, $acl); + case STORAGE::DEVICE_DO_SPACES: + $device = new DOSpaces($root, $accessKey, $accessSecret, $bucket, $region, $acl); + $device->setHttpVersion(S3::HTTP_VERSION_1_1); + return $device; + case Storage::DEVICE_BACKBLAZE: + return new Backblaze($root, $accessKey, $accessSecret, $bucket, $region, $acl); + case Storage::DEVICE_LINODE: + return new Linode($root, $accessKey, $accessSecret, $bucket, $region, $acl); + case Storage::DEVICE_WASABI: + return new Wasabi($root, $accessKey, $accessSecret, $bucket, $region, $acl); + case Storage::DEVICE_LOCAL: + default: + return new Local($root); + } + } else { + switch (strtolower(System::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) ?? '')) { + case Storage::DEVICE_LOCAL: + default: + return new Local($root); + case Storage::DEVICE_S3: + $s3AccessKey = System::getEnv('_APP_STORAGE_S3_ACCESS_KEY', ''); + $s3SecretKey = System::getEnv('_APP_STORAGE_S3_SECRET', ''); + $s3Region = System::getEnv('_APP_STORAGE_S3_REGION', ''); + $s3Bucket = System::getEnv('_APP_STORAGE_S3_BUCKET', ''); + $s3Acl = 'private'; + return new S3($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl); + case Storage::DEVICE_DO_SPACES: + $doSpacesAccessKey = System::getEnv('_APP_STORAGE_DO_SPACES_ACCESS_KEY', ''); + $doSpacesSecretKey = System::getEnv('_APP_STORAGE_DO_SPACES_SECRET', ''); + $doSpacesRegion = System::getEnv('_APP_STORAGE_DO_SPACES_REGION', ''); + $doSpacesBucket = System::getEnv('_APP_STORAGE_DO_SPACES_BUCKET', ''); + $doSpacesAcl = 'private'; + $device = new DOSpaces($root, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl); + $device->setHttpVersion(S3::HTTP_VERSION_1_1); + return $device; + case Storage::DEVICE_BACKBLAZE: + $backblazeAccessKey = System::getEnv('_APP_STORAGE_BACKBLAZE_ACCESS_KEY', ''); + $backblazeSecretKey = System::getEnv('_APP_STORAGE_BACKBLAZE_SECRET', ''); + $backblazeRegion = System::getEnv('_APP_STORAGE_BACKBLAZE_REGION', ''); + $backblazeBucket = System::getEnv('_APP_STORAGE_BACKBLAZE_BUCKET', ''); + $backblazeAcl = 'private'; + return new Backblaze($root, $backblazeAccessKey, $backblazeSecretKey, $backblazeBucket, $backblazeRegion, $backblazeAcl); + case Storage::DEVICE_LINODE: + $linodeAccessKey = System::getEnv('_APP_STORAGE_LINODE_ACCESS_KEY', ''); + $linodeSecretKey = System::getEnv('_APP_STORAGE_LINODE_SECRET', ''); + $linodeRegion = System::getEnv('_APP_STORAGE_LINODE_REGION', ''); + $linodeBucket = System::getEnv('_APP_STORAGE_LINODE_BUCKET', ''); + $linodeAcl = 'private'; + return new Linode($root, $linodeAccessKey, $linodeSecretKey, $linodeBucket, $linodeRegion, $linodeAcl); + case Storage::DEVICE_WASABI: + $wasabiAccessKey = System::getEnv('_APP_STORAGE_WASABI_ACCESS_KEY', ''); + $wasabiSecretKey = System::getEnv('_APP_STORAGE_WASABI_SECRET', ''); + $wasabiRegion = System::getEnv('_APP_STORAGE_WASABI_REGION', ''); + $wasabiBucket = System::getEnv('_APP_STORAGE_WASABI_BUCKET', ''); + $wasabiAcl = 'private'; + return new Wasabi($root, $wasabiAccessKey, $wasabiSecretKey, $wasabiBucket, $wasabiRegion, $wasabiAcl); + } + } +} + +App::setResource('mode', function ($request) { + /** @var Appwrite\Utopia\Request $request */ + + /** + * Defines the mode for the request: + * - 'default' => Requests for Client and Server Side + * - 'admin' => Request from the Console on non-console projects + */ + return $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT)); +}, ['request']); + +App::setResource('geodb', function ($register) { + /** @var Utopia\Registry\Registry $register */ + return $register->get('geodb'); +}, ['register']); + +App::setResource('passwordsDictionary', function ($register) { + /** @var Utopia\Registry\Registry $register */ + return $register->get('passwordsDictionary'); +}, ['register']); + + +App::setResource('servers', function () { + $platforms = Config::getParam('platforms'); + $server = $platforms[APP_PLATFORM_SERVER]; + + $languages = array_map(function ($language) { + return strtolower($language['name']); + }, $server['sdks']); + + return $languages; +}); + +App::setResource('promiseAdapter', function ($register) { + return $register->get('promiseAdapter'); +}, ['register']); + +App::setResource('schema', function ($utopia, $dbForProject) { + + $complexity = function (int $complexity, array $args) { + $queries = Query::parseQueries($args['queries'] ?? []); + $query = Query::getByType($queries, [Query::TYPE_LIMIT])[0] ?? null; + $limit = $query ? $query->getValue() : APP_LIMIT_LIST_DEFAULT; + + return $complexity * $limit; + }; + + $attributes = function (int $limit, int $offset) use ($dbForProject) { + $attrs = Authorization::skip(fn () => $dbForProject->find('attributes', [ + Query::limit($limit), + Query::offset($offset), + ])); + + return \array_map(function ($attr) { + return $attr->getArrayCopy(); + }, $attrs); + }; + + $urls = [ + 'list' => function (string $databaseId, string $collectionId, array $args) { + return "/v1/databases/$databaseId/collections/$collectionId/documents"; + }, + 'create' => function (string $databaseId, string $collectionId, array $args) { + return "/v1/databases/$databaseId/collections/$collectionId/documents"; + }, + 'read' => function (string $databaseId, string $collectionId, array $args) { + return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}"; + }, + 'update' => function (string $databaseId, string $collectionId, array $args) { + return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}"; + }, + 'delete' => function (string $databaseId, string $collectionId, array $args) { + return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}"; + }, + ]; + + $params = [ + 'list' => function (string $databaseId, string $collectionId, array $args) { + return [ 'queries' => $args['queries']]; + }, + 'create' => function (string $databaseId, string $collectionId, array $args) { + $id = $args['id'] ?? 'unique()'; + $permissions = $args['permissions'] ?? null; + + unset($args['id']); + unset($args['permissions']); + + // Order must be the same as the route params + return [ + 'databaseId' => $databaseId, + 'documentId' => $id, + 'collectionId' => $collectionId, + 'data' => $args, + 'permissions' => $permissions, + ]; + }, + 'update' => function (string $databaseId, string $collectionId, array $args) { + $documentId = $args['id']; + $permissions = $args['permissions'] ?? null; + + unset($args['id']); + unset($args['permissions']); + + // Order must be the same as the route params + return [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'documentId' => $documentId, + 'data' => $args, + 'permissions' => $permissions, + ]; + }, + ]; + + return Schema::build( + $utopia, + $complexity, + $attributes, + $urls, + $params, + ); +}, ['utopia', 'dbForProject']); + +App::setResource('contributors', function () { + $path = 'app/config/contributors.json'; + $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : []; + return $list; +}); + +App::setResource('employees', function () { + $path = 'app/config/employees.json'; + $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : []; + return $list; +}); + +App::setResource('heroes', function () { + $path = 'app/config/heroes.json'; + $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : []; + return $list; +}); + +App::setResource('gitHub', function (Cache $cache) { + return new VcsGitHub($cache); +}, ['cache']); + +App::setResource('requestTimestamp', function ($request) { + //TODO: Move this to the Request class itself + $timestampHeader = $request->getHeader('x-appwrite-timestamp'); + $requestTimestamp = null; + if (!empty($timestampHeader)) { + try { + $requestTimestamp = new \DateTime($timestampHeader); + } catch (\Throwable $e) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid X-Appwrite-Timestamp header value'); + } + } + return $requestTimestamp; +}, ['request']); +App::setResource('plan', function (array $plan = []) { + return []; +}); + + +App::setResource('team', function (Document $project, Database $dbForConsole, App $utopia, Request $request) { + $teamInternalId = ''; + if ($project->getId() !== 'console') { + $teamInternalId = $project->getAttribute('teamInternalId', ''); + } else { + $route = $utopia->match($request); + $path = $route->getPath(); + if (str_starts_with($path, '/v1/projects/:projectId')) { + $uri = $request->getURI(); + $pid = explode('/', $uri)[3]; + $p = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $pid)); + $teamInternalId = $p->getAttribute('teamInternalId', ''); + } elseif ($path === '/v1/projects') { + $teamId = $request->getParam('teamId', ''); + $team = Authorization::skip(fn () => $dbForConsole->getDocument('teams', $teamId)); + return $team; + } + } + + $team = Authorization::skip(function () use ($dbForConsole, $teamInternalId) { + return $dbForConsole->findOne('teams', [ + Query::equal('$internalId', [$teamInternalId]), + ]); + }); + + if (!$team) { + $team = new Document([]); + } + return $team; +}, ['project', 'dbForConsole', 'utopia', 'request']); diff --git a/app/init/config.php b/app/init/config.php deleted file mode 100644 index bc8333b5d9..0000000000 --- a/app/init/config.php +++ /dev/null @@ -1,37 +0,0 @@ - $value], JSON_PRESERVE_ZERO_FRACTION); - }, - function (mixed $value) { - if (is_null($value)) { - return; - } - - return json_decode($value, true)['value']; - } -); - -Database::addFilter( - 'enum', - function (mixed $value, Document $attribute) { - if ($attribute->isSet('elements')) { - $attribute->removeAttribute('elements'); - } - - return $value; - }, - function (mixed $value, Document $attribute) { - $formatOptions = \json_decode($attribute->getAttribute('formatOptions', '[]'), true); - if (isset($formatOptions['elements'])) { - $attribute->setAttribute('elements', $formatOptions['elements']); - } - - return $value; - } -); - -Database::addFilter( - 'range', - function (mixed $value, Document $attribute) { - if ($attribute->isSet('min')) { - $attribute->removeAttribute('min'); - } - if ($attribute->isSet('max')) { - $attribute->removeAttribute('max'); - } - - return $value; - }, - function (mixed $value, Document $attribute) { - $formatOptions = json_decode($attribute->getAttribute('formatOptions', '[]'), true); - if (isset($formatOptions['min']) || isset($formatOptions['max'])) { - $attribute - ->setAttribute('min', $formatOptions['min']) - ->setAttribute('max', $formatOptions['max']) - ; - } - - return $value; - } -); - -Database::addFilter( - 'subQueryAttributes', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - $attributes = $database->find('attributes', [ - Query::equal('collectionInternalId', [$document->getInternalId()]), - Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]), - Query::limit($database->getLimitForAttributes()), - ]); - - foreach ($attributes as $attribute) { - if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) { - $options = $attribute->getAttribute('options'); - foreach ($options as $key => $value) { - $attribute->setAttribute($key, $value); - } - $attribute->removeAttribute('options'); - } - } - - return $attributes; - } -); - -Database::addFilter( - 'subQueryIndexes', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return $database - ->find('indexes', [ - Query::equal('collectionInternalId', [$document->getInternalId()]), - Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]), - Query::limit($database->getLimitForIndexes()), - ]); - } -); - -Database::addFilter( - 'subQueryPlatforms', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return $database - ->find('platforms', [ - Query::equal('projectInternalId', [$document->getInternalId()]), - Query::limit(APP_LIMIT_SUBQUERY), - ]); - } -); - -Database::addFilter( - 'subQueryKeys', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return $database - ->find('keys', [ - Query::equal('projectInternalId', [$document->getInternalId()]), - Query::limit(APP_LIMIT_SUBQUERY), - ]); - } -); - -Database::addFilter( - 'subQueryWebhooks', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return $database - ->find('webhooks', [ - Query::equal('projectInternalId', [$document->getInternalId()]), - Query::limit(APP_LIMIT_SUBQUERY), - ]); - } -); - -Database::addFilter( - 'subQuerySessions', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return $database->getAuthorization()->skip(fn () => $database->find('sessions', [ - Query::equal('userInternalId', [$document->getInternalId()]), - Query::limit(APP_LIMIT_SUBQUERY), - ])); - } -); - -Database::addFilter( - 'subQueryTokens', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return $database->getAuthorization()->skip(fn () => $database - ->find('tokens', [ - Query::equal('userInternalId', [$document->getInternalId()]), - Query::limit(APP_LIMIT_SUBQUERY), - ])); - } -); - -Database::addFilter( - 'subQueryChallenges', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return $database->getAuthorization()->skip(fn () => $database - ->find('challenges', [ - Query::equal('userInternalId', [$document->getInternalId()]), - Query::limit(APP_LIMIT_SUBQUERY), - ])); - } -); - -Database::addFilter( - 'subQueryAuthenticators', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return $database->getAuthorization()->skip(fn () => $database - ->find('authenticators', [ - Query::equal('userInternalId', [$document->getInternalId()]), - Query::limit(APP_LIMIT_SUBQUERY), - ])); - } -); - -Database::addFilter( - 'subQueryMemberships', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return $database->getAuthorization()->skip(fn () => $database - ->find('memberships', [ - Query::equal('userInternalId', [$document->getInternalId()]), - Query::limit(APP_LIMIT_SUBQUERY), - ])); - } -); - -Database::addFilter( - 'subQueryVariables', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return $database - ->find('variables', [ - Query::equal('resourceInternalId', [$document->getInternalId()]), - Query::equal('resourceType', ['function']), - Query::limit(APP_LIMIT_SUBQUERY), - ]); - } -); - -Database::addFilter( - 'encrypt', - function (mixed $value) { - $key = System::getEnv('_APP_OPENSSL_KEY_V1'); - $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); - $tag = null; - - return json_encode([ - 'data' => OpenSSL::encrypt($value, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag), - 'method' => OpenSSL::CIPHER_AES_128_GCM, - 'iv' => \bin2hex($iv), - 'tag' => \bin2hex($tag ?? ''), - 'version' => '1', - ]); - }, - function (mixed $value) { - if (is_null($value)) { - return; - } - $value = json_decode($value, true); - $key = System::getEnv('_APP_OPENSSL_KEY_V' . $value['version']); - - return OpenSSL::decrypt($value['data'], $value['method'], $key, 0, hex2bin($value['iv']), hex2bin($value['tag'])); - } -); - -Database::addFilter( - 'subQueryProjectVariables', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return $database - ->find('variables', [ - Query::equal('resourceType', ['project']), - Query::limit(APP_LIMIT_SUBQUERY) - ]); - } -); - -Database::addFilter( - 'userSearch', - function (mixed $value, Document $user) { - $searchValues = [ - $user->getId(), - $user->getAttribute('email', ''), - $user->getAttribute('name', ''), - $user->getAttribute('phone', '') - ]; - - foreach ($user->getAttribute('labels', []) as $label) { - $searchValues[] = 'label:' . $label; - } - - $search = implode(' ', \array_filter($searchValues)); - - return $search; - }, - function (mixed $value) { - return $value; - } -); - -Database::addFilter( - 'subQueryTargets', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - return $database->getAuthorization()->skip(fn () => $database - ->find('targets', [ - Query::equal('userInternalId', [$document->getInternalId()]), - Query::limit(APP_LIMIT_SUBQUERY) - ])); - } -); - -Database::addFilter( - 'subQueryTopicTargets', - function (mixed $value) { - return; - }, - function (mixed $value, Document $document, Database $database) { - $targetIds = $database->getAuthorization()->skip(fn () => \array_map( - fn ($document) => $document->getAttribute('targetInternalId'), - $database->find('subscribers', [ - Query::equal('topicInternalId', [$document->getInternalId()]), - Query::limit(APP_LIMIT_SUBSCRIBERS_SUBQUERY) - ]) - )); - if (\count($targetIds) > 0) { - return $database->find('targets', [ - Query::equal('$internalId', $targetIds) - ]); - } - return []; - } -); - -Database::addFilter( - 'providerSearch', - function (mixed $value, Document $provider) { - $searchValues = [ - $provider->getId(), - $provider->getAttribute('name', ''), - $provider->getAttribute('provider', ''), - $provider->getAttribute('type', '') - ]; - - $search = \implode(' ', \array_filter($searchValues)); - - return $search; - }, - function (mixed $value) { - return $value; - } -); - -Database::addFilter( - 'topicSearch', - function (mixed $value, Document $topic) { - $searchValues = [ - $topic->getId(), - $topic->getAttribute('name', ''), - $topic->getAttribute('description', ''), - ]; - - $search = \implode(' ', \array_filter($searchValues)); - - return $search; - }, - function (mixed $value) { - return $value; - } -); - -Database::addFilter( - 'messageSearch', - function (mixed $value, Document $message) { - $searchValues = [ - $message->getId(), - $message->getAttribute('description', ''), - $message->getAttribute('status', ''), - ]; - - $data = \json_decode($message->getAttribute('data', []), true); - $providerType = $message->getAttribute('providerType', ''); - - if ($providerType === MESSAGE_TYPE_EMAIL) { - $searchValues = \array_merge($searchValues, [$data['subject'], MESSAGE_TYPE_EMAIL]); - } elseif ($providerType === MESSAGE_TYPE_SMS) { - $searchValues = \array_merge($searchValues, [$data['content'], MESSAGE_TYPE_SMS]); - } else { - $searchValues = \array_merge($searchValues, [$data['title'], MESSAGE_TYPE_PUSH]); - } - - $search = \implode(' ', \array_filter($searchValues)); - - return $search; - }, - function (mixed $value) { - return $value; - } -); diff --git a/app/init/database/formats.php b/app/init/database/formats.php deleted file mode 100644 index 88e46655ac..0000000000 --- a/app/init/database/formats.php +++ /dev/null @@ -1,43 +0,0 @@ -getScheme(); - $accessKey = $dsn->getUser() ?? ''; - $accessSecret = $dsn->getPassword() ?? ''; - $bucket = $dsn->getPath() ?? ''; - $region = $dsn->getParam('region'); - } catch (\Throwable $e) { - Console::warning($e->getMessage() . 'Invalid DSN. Defaulting to Local device.'); - } - - switch ($device) { - case Storage::DEVICE_S3: - return new S3($root, $accessKey, $accessSecret, $bucket, $region, $acl); - case STORAGE::DEVICE_DO_SPACES: - return new DOSpaces($root, $accessKey, $accessSecret, $bucket, $region, $acl); - case Storage::DEVICE_BACKBLAZE: - return new Backblaze($root, $accessKey, $accessSecret, $bucket, $region, $acl); - case Storage::DEVICE_LINODE: - return new Linode($root, $accessKey, $accessSecret, $bucket, $region, $acl); - case Storage::DEVICE_WASABI: - return new Wasabi($root, $accessKey, $accessSecret, $bucket, $region, $acl); - case Storage::DEVICE_LOCAL: - default: - return new Local($root); - } - } else { - switch (strtolower(System::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) ?? '')) { - case Storage::DEVICE_LOCAL: - default: - return new Local($root); - case Storage::DEVICE_S3: - $s3AccessKey = System::getEnv('_APP_STORAGE_S3_ACCESS_KEY', ''); - $s3SecretKey = System::getEnv('_APP_STORAGE_S3_SECRET', ''); - $s3Region = System::getEnv('_APP_STORAGE_S3_REGION', ''); - $s3Bucket = System::getEnv('_APP_STORAGE_S3_BUCKET', ''); - $s3Acl = 'private'; - return new S3($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl); - case Storage::DEVICE_DO_SPACES: - $doSpacesAccessKey = System::getEnv('_APP_STORAGE_DO_SPACES_ACCESS_KEY', ''); - $doSpacesSecretKey = System::getEnv('_APP_STORAGE_DO_SPACES_SECRET', ''); - $doSpacesRegion = System::getEnv('_APP_STORAGE_DO_SPACES_REGION', ''); - $doSpacesBucket = System::getEnv('_APP_STORAGE_DO_SPACES_BUCKET', ''); - $doSpacesAcl = 'private'; - return new DOSpaces($root, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl); - case Storage::DEVICE_BACKBLAZE: - $backblazeAccessKey = System::getEnv('_APP_STORAGE_BACKBLAZE_ACCESS_KEY', ''); - $backblazeSecretKey = System::getEnv('_APP_STORAGE_BACKBLAZE_SECRET', ''); - $backblazeRegion = System::getEnv('_APP_STORAGE_BACKBLAZE_REGION', ''); - $backblazeBucket = System::getEnv('_APP_STORAGE_BACKBLAZE_BUCKET', ''); - $backblazeAcl = 'private'; - return new Backblaze($root, $backblazeAccessKey, $backblazeSecretKey, $backblazeBucket, $backblazeRegion, $backblazeAcl); - case Storage::DEVICE_LINODE: - $linodeAccessKey = System::getEnv('_APP_STORAGE_LINODE_ACCESS_KEY', ''); - $linodeSecretKey = System::getEnv('_APP_STORAGE_LINODE_SECRET', ''); - $linodeRegion = System::getEnv('_APP_STORAGE_LINODE_REGION', ''); - $linodeBucket = System::getEnv('_APP_STORAGE_LINODE_BUCKET', ''); - $linodeAcl = 'private'; - return new Linode($root, $linodeAccessKey, $linodeSecretKey, $linodeBucket, $linodeRegion, $linodeAcl); - case Storage::DEVICE_WASABI: - $wasabiAccessKey = System::getEnv('_APP_STORAGE_WASABI_ACCESS_KEY', ''); - $wasabiSecretKey = System::getEnv('_APP_STORAGE_WASABI_SECRET', ''); - $wasabiRegion = System::getEnv('_APP_STORAGE_WASABI_REGION', ''); - $wasabiBucket = System::getEnv('_APP_STORAGE_WASABI_BUCKET', ''); - $wasabiAcl = 'private'; - return new Wasabi($root, $wasabiAccessKey, $wasabiSecretKey, $wasabiBucket, $wasabiRegion, $wasabiAcl); - } - } -} - -$log = new Dependency(); -$mode = new Dependency(); -$user = new Dependency(); -$team = new Dependency(); -$plan = new Dependency(); -$pools = new Dependency(); -$geodb = new Dependency(); -$cache = new Dependency(); -$pools = new Dependency(); -$queue = new Dependency(); -$hooks = new Dependency(); -$logger = new Dependency(); -$locale = new Dependency(); -$schema = new Dependency(); -$github = new Dependency(); -$session = new Dependency(); -$console = new Dependency(); -$project = new Dependency(); -$clients = new Dependency(); -$servers = new Dependency(); -$register = new Dependency(); -$connections = new Dependency(); -$localeCodes = new Dependency(); -$getConsoleDB = new Dependency(); -$getProjectDB = new Dependency(); -$dbForProject = new Dependency(); -$dbForConsole = new Dependency(); -$queueForUsage = new Dependency(); -$queueForMails = new Dependency(); -$authorization = new Dependency(); -$authentication = new Dependency(); -$queueForBuilds = new Dependency(); -$deviceForLocal = new Dependency(); -$deviceForFiles = new Dependency(); -$queueForEvents = new Dependency(); -$queueForAudits = new Dependency(); -$promiseAdapter = new Dependency(); -$schemaVariable = new Dependency(); -$deviceForBuilds = new Dependency(); -$queueForDeletes = new Dependency(); -$requestTimestamp = new Dependency(); -$queueForDatabase = new Dependency(); -$queueForMessaging = new Dependency(); -$queueForFunctions = new Dependency(); -$queueForMigrations = new Dependency(); -$deviceForFunctions = new Dependency(); -$passwordsDictionary = new Dependency(); -$queueForCertificates = new Dependency(); - - -$team - ->setName('team') - ->inject('project') - ->inject('request') - ->inject('dbForConsole') - ->inject('authorization') - ->setCallback(function (Document $project, Request $request, Database $dbForConsole, Authorization $authorization) { - $teamInternalId = ''; - if ($project->getId() !== 'console') { - $teamInternalId = $project->getAttribute('teamInternalId', ''); - } else { - $method = (Http::REQUEST_METHOD_HEAD == $request->getMethod()) ? Http::REQUEST_METHOD_GET : $request->getMethod(); - $route = Router::match($method, $request->getURI()); - - if ($route !== null) { - $path = $route->getPath(); - if (str_starts_with($path, '/v1/projects/:projectId')) { - $uri = $request->getURI(); - $pid = explode('/', $uri)[3]; - $p = $authorization->skip(fn () => $dbForConsole->getDocument('projects', $pid)); - $teamInternalId = $p->getAttribute('teamInternalId', ''); - } elseif ($path === '/v1/projects') { - $teamId = $request->getParam('teamId', ''); - $team = $authorization->skip(fn () => $dbForConsole->getDocument('teams', $teamId)); - return $team; - } - } - } - - $team = $authorization->skip(function () use ($dbForConsole, $teamInternalId) { - return $dbForConsole->findOne('teams', [ - Query::equal('$internalId', [$teamInternalId]), - ]); - }); - - if (!$team) { - $team = new Document([]); - } - return $team; - }); - - -$plan - ->setName('plan') - ->setCallback(fn () => []); - -$mode - ->setName('mode') - ->inject('request') - ->setCallback(function (Request $request) { - /** - * Defines the mode for the request: - * - 'default' => Requests for Client and Server Side - * - 'admin' => Request from the Console on non-console projects - */ - return $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT)); - }); - -$user - ->setName('user') - ->inject('mode') - ->inject('project') - ->inject('console') - ->inject('request') - ->inject('response') - ->inject('dbForProject') - ->inject('dbForConsole') - ->inject('authorization') - ->inject('authentication') - ->setCallback(function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForConsole, Authorization $authorization, Authentication $authentication) { - $authorization->setDefaultStatus(true); - $authentication->setCookieName('a_session_' . $project->getId()); - - if (APP_MODE_ADMIN === $mode) { - $authentication->setCookieName('a_session_' . $console->getId()); - } - - $session = Auth::decodeSession( - $request->getCookie( - $authentication->getCookieName(), // Get sessions - $request->getCookie($authentication->getCookieName() . '_legacy', '') - ) - ); - - // Get session from header for SSR clients - if (empty($session['id']) && empty($session['secret'])) { - $sessionHeader = $request->getHeader('x-appwrite-session', ''); - - if (!empty($sessionHeader)) { - $session = Auth::decodeSession($sessionHeader); - } - } - - // Get fallback session from old clients (no SameSite support) or clients who block 3rd-party cookies - if ($response) { - $response->addHeader('X-Debug-Fallback', 'false'); - } - - if (empty($session['id']) && empty($session['secret'])) { - if ($response) { - $response->addHeader('X-Debug-Fallback', 'true'); - } - $fallback = $request->getHeader('x-fallback-cookies', ''); - $fallback = \json_decode($fallback, true); - $session = Auth::decodeSession(((isset($fallback[$authentication->getCookieName()])) ? $fallback[$authentication->getCookieName()] : '')); - } - - $authentication->setUnique($session['id'] ?? ''); - $authentication->setSecret($session['secret'] ?? ''); - - if (APP_MODE_ADMIN !== $mode) { - if ($project->isEmpty()) { - $user = new Document([]); - } else { - if ($project->getId() === 'console') { - $user = $dbForConsole->getDocument('users', $authentication->getUnique()); - } else { - $user = $dbForProject->getDocument('users', $authentication->getUnique()); - } - } - } else { - $user = $dbForConsole->getDocument('users', $authentication->getUnique()); - } - - if ( - $user->isEmpty() // Check a document has been found in the DB - || !Auth::sessionVerify($user->getAttribute('sessions', []), $authentication->getSecret()) - ) { // Validate user has valid login token - $user = new Document([]); - } - - // if (APP_MODE_ADMIN === $mode) { - // if ($user->find('teamId', $project->getAttribute('teamId'), 'memberships')) { - // $authorization->setDefaultStatus(false); // Cancel security segmentation for admin users. - // } else { - // $user = new Document([]); - // } - // } - - $authJWT = $request->getHeader('x-appwrite-jwt', ''); - - if (!empty($authJWT) && !$project->isEmpty()) { // JWT authentication - $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0); - try { - $payload = $jwt->decode($authJWT); - } catch (JWTException $error) { - $request->removeHeader('x-appwrite-jwt'); - throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage()); - } - - $jwtUserId = $payload['userId'] ?? ''; - if (!empty($jwtUserId)) { - $user = $dbForProject->getDocument('users', $jwtUserId); - } - - $jwtSessionId = $payload['sessionId'] ?? ''; - if (!empty($jwtSessionId)) { - if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token - $user = new Document([]); - } - } - } - - // Adds logs to database queries - $dbForProject->setMetadata('user', $user->getId()); - $dbForConsole->setMetadata('user', $user->getId()); - - return $user; - }); - - -$session - ->setName('session') - ->inject('user') - ->inject('project') - ->inject('authorization') - ->inject('authentication') - ->setCallback(function (Document $user, Document $project, Authorization $authorization, Authentication $authentication) { - if ($user->isEmpty()) { - return; - } - - $sessions = $user->getAttribute('sessions', []); - $authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; - $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), $authentication->getSecret(), $authDuration); - - if (!$sessionId) { - return; - } - - foreach ($sessions as $session) { - if ($sessionId === $session->getId()) { - return $session; - } - } - - return; - }); - -$console - ->setName('console') - ->setCallback(function () { - return new Document([ - '$id' => ID::custom('console'), - '$internalId' => ID::custom('console'), - 'name' => 'Appwrite', - '$collection' => ID::custom('projects'), - 'description' => 'Appwrite core engine', - 'logo' => '', - 'teamId' => null, - 'webhooks' => [], - 'keys' => [], - 'platforms' => [ - [ - '$collection' => ID::custom('platforms'), - 'name' => 'Localhost', - 'type' => Origin::CLIENT_TYPE_WEB, - 'hostname' => 'localhost', - ], // Current host is added on app init - ], - 'legalName' => '', - 'legalCountry' => '', - 'legalState' => '', - 'legalCity' => '', - 'legalAddress' => '', - 'legalTaxId' => '', - 'auths' => [ - 'mockNumbers' => [], - 'invites' => System::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled', - 'limit' => (System::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user - 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds - 'sessionAlerts' => System::getEnv('_APP_CONSOLE_SESSION_ALERTS', 'disabled') === 'enabled' - ], - 'authWhitelistEmails' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [], - 'authWhitelistIPs' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [], - 'oAuthProviders' => [ - 'githubEnabled' => true, - 'githubSecret' => System::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''), - 'githubAppid' => System::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '') - ], - ]); - }); - - -$project - ->setName('project') - ->inject('dbForConsole') - ->inject('request') - ->inject('console') - ->inject('authorization') - ->setCallback(function (Database $dbForConsole, Request $request, Document $console, Authorization $authorization) { - $projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', '')); - - if (empty($projectId) || $projectId === 'console') { - return $console; - } - - $project = $authorization->skip(fn () => $dbForConsole->getDocument('projects', $projectId)); - - return $project; - }); - -$pools - ->setName('pools') - ->inject('registry') - ->setCallback(function (Registry $registry) { - return $registry->get('pools'); - }); - -$dbForProject - ->setName('dbForProject') - ->inject('cache') - ->inject('pools') - ->inject('project') - ->inject('dbForConsole') - ->inject('authorization') - ->inject('connections') - ->setCallback(function (Cache $cache, array $pools, Document $project, Database $dbForConsole, Authorization $authorization, Connections $connections) { - if ($project->isEmpty() || $project->getId() === 'console') { - return $dbForConsole; - } - - 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')); - } - - $pool = $pools['pools-database-' . $dsn->getHost()]['pool']; - $connectionDsn = $pools['pools-database-' . $dsn->getHost()]['dsn']; - - $connection = $pool->get(); - $connections->add($connection, $pool); - $adapter = match ($connectionDsn->getScheme()) { - 'mariadb' => new MariaDB($connection), - 'mysql' => new MySQL($connection), - default => null - }; - - $adapter->setDatabase($connectionDsn->getPath()); - - $database = new Database($adapter, $cache); - - 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')); - } - - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { - $database - ->setSharedTables(true) - ->setTenant($project->getInternalId()) - ->setNamespace($dsn->getParam('namespace')); - } else { - $database - ->setSharedTables(false) - ->setTenant(null) - ->setNamespace('_' . $project->getInternalId()); - } - - $database->setAuthorization($authorization); - return $database; - }); - -$dbForConsole - ->setName('dbForConsole') - ->inject('getConsoleDB') - ->inject('connections') - ->setCallback(function (callable $getConsoleDB, Connections $connections): Database { - [$connection,$pool, $database] = $getConsoleDB(); - $connections->add($connection, $pool); - - return $database; - }); - -$cache - ->setName('cache') - ->inject('pools') - ->inject('connections') - ->setCallback(function (array $pools, Connections $connections) { - $adapters = []; - $databases = Config::getParam('pools-cache'); - - foreach ($databases as $database) { - $pool = $pools['pools-cache-' . $database]['pool']; - $dsn = $pools['pools-cache-' . $database]['dsn']; - - $connection = $pool->get(); - $connections->add($connection, $pool); - - $adapters[] = new CacheRedis($connection); - } - - return new Cache(new Sharding($adapters)); - }); - -$authorization - ->setName('authorization') - ->setCallback(function (): Authorization { - return new Authorization(); - }); - -$authentication - ->setName('authentication') - ->setCallback(function (): Authentication { - return new Authentication(); - }); - -$register - ->setName('registry') - ->setCallback(function () use (&$registry): Registry { - return $registry; - }); - -$pools - ->setName('pools') - ->inject('registry') - ->setCallback(function (Registry $registry) { - return $registry->get('pools'); - }); - -$logger - ->setName('logger') - ->inject('registry') - ->setCallback(function (Registry $registry) { - return $registry->get('logger'); - }); - -$log - ->setName('log') - ->setCallback(function () { - return new Log(); - }); - -$connections - ->setName('connections') - ->setCallback(function () { - return new Connections(); - }); - -$locale - ->setName('locale') - ->setCallback(fn () => new Locale(System::getEnv('_APP_LOCALE', 'en'))); - -$localeCodes - ->setName('localeCodes') - ->setCallback(fn () => array_map(fn ($locale) => $locale['code'], Config::getParam('locale-codes', []))); - -$queue - ->setName('queue') - ->inject('pools') - ->inject('connections') - ->setCallback(function (array $pools, Connections $connections) { - $pool = $pools['pools-queue-queue']['pool']; - $connection = $pool->get(); - $connections->add($connection, $pool); - - return new Queue\Connection\Redis($connection); - }); - -$queueForMessaging - ->setName('queueForMessaging') - ->inject('queue') - ->setCallback(function (Connection $queue) { - return new Messaging($queue); - }); - -$queueForMails - ->setName('queueForMails') - ->inject('queue') - ->setCallback(function (Connection $queue) { - return new Mail($queue); - }); - -$queueForBuilds - ->setName('queueForBuilds') - ->inject('queue') - ->setCallback(function (Connection $queue) { - return new Build($queue); - }); - -$queueForDatabase - ->setName('queueForDatabase') - ->inject('queue') - ->setCallback(function (Connection $queue) { - return new EventDatabase($queue); - }); - -$queueForDeletes - ->setName('queueForDeletes') - ->inject('queue') - ->setCallback(function (Connection $queue) { - return new Delete($queue); - }); - -$queueForEvents - ->setName('queueForEvents') - ->inject('queue') - ->setCallback(function (Connection $queue) { - return new Event($queue); - }); - -$queueForAudits - ->setName('queueForAudits') - ->inject('queue') - ->setCallback(function (Connection $queue) { - return new Audit($queue); - }); - -$queueForFunctions - ->setName('queueForFunctions') - ->inject('queue') - ->setCallback(function (Connection $queue) { - return new Func($queue); - }); - -$queueForUsage - ->setName('queueForUsage') - ->inject('queue') - ->setCallback(function (Connection $queue) { - return new Usage($queue); - }); - -$queueForCertificates - ->setName('queueForCertificates') - ->inject('queue') - ->setCallback(function (Connection $queue) { - return new Certificate($queue); - }); - -$queueForMigrations - ->setName('queueForMigrations') - ->inject('queue') - ->setCallback(function (Connection $queue) { - return new Migration($queue); - }); - -$deviceForLocal - ->setName('deviceForLocal') - ->setCallback(function () { - return new Local(); - }); - -$deviceForFiles - ->setName('deviceForFiles') - ->inject('project') - ->setCallback(function ($project) { - return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId()); - }); - -$deviceForFunctions - ->setName('deviceForFunctions') - ->inject('project') - ->setCallback(function ($project) { - return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId()); - }); - -$deviceForBuilds - ->setName('deviceForBuilds') - ->inject('project') - ->setCallback(function ($project) { - return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId()); - }); - -$clients - ->setName('clients') - ->inject('request') - ->inject('console') - ->inject('project') - ->setCallback(function (Request $request, Document $console, Document $project) { - $console->setAttribute('platforms', [ // Always allow current host - '$collection' => ID::custom('platforms'), - 'name' => 'Current Host', - 'type' => Origin::CLIENT_TYPE_WEB, - 'hostname' => $request->getHostname(), - ], Document::SET_TYPE_APPEND); - - $hostnames = explode(',', System::getEnv('_APP_CONSOLE_HOSTNAMES', '')); - $validator = new Hostname(); - foreach ($hostnames as $hostname) { - $hostname = trim($hostname); - if (!$validator->isValid($hostname)) { - continue; - } - $console->setAttribute('platforms', [ - '$collection' => ID::custom('platforms'), - 'type' => Origin::CLIENT_TYPE_WEB, - 'name' => $hostname, - 'hostname' => $hostname, - ], Document::SET_TYPE_APPEND); - } - - /** - * Get All verified client URLs for both console and current projects - * + Filter for duplicated entries - */ - $clientsConsole = \array_map( - fn ($node) => $node['hostname'], - \array_filter( - $console->getAttribute('platforms', []), - fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB) && isset($node['hostname']) && !empty($node['hostname'])) - ) - ); - - $clients = \array_unique( - \array_merge( - $clientsConsole, - \array_map( - fn ($node) => $node['hostname'], - \array_filter( - $project->getAttribute('platforms', []), - fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB || $node['type'] === Origin::CLIENT_TYPE_FLUTTER_WEB) && isset($node['hostname']) && !empty($node['hostname'])) - ) - ) - ) - ); - - return $clients; - }); - -$servers - ->setName('servers') - ->setCallback(function () { - $platforms = Config::getParam('platforms'); - $server = $platforms[APP_PLATFORM_SERVER]; - - $languages = array_map(function ($language) { - return strtolower($language['name']); - }, $server['sdks']); - - return $languages; - }); - -$geodb - ->setName('geodb') - ->inject('registry') - ->setCallback(function (Registry $register) { - return $register->get('geodb'); - }); - -$passwordsDictionary - ->setName('passwordsDictionary') - ->setCallback(function () { - $content = file_get_contents(__DIR__ . '/../assets/security/10k-common-passwords'); - $content = explode("\n", $content); - $content = array_flip($content); - return $content; - }); - -$hooks - ->setName('hooks') - ->inject('registry') - ->setCallback(function (Registry $registry) { - return $registry->get('hooks'); - }); - -$github - ->setName('gitHub') - ->inject('cache') - ->setCallback(function (Cache $cache) { - return new GitHub($cache); - }); - -$requestTimestamp - ->setName('requestTimestamp') - ->inject('request') - ->setCallback(function ($request) { - $timestampHeader = $request->getHeader('x-appwrite-timestamp'); - $requestTimestamp = null; - if (!empty($timestampHeader)) { - try { - $requestTimestamp = new \DateTime($timestampHeader); - } catch (\Throwable $e) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid X-Appwrite-Timestamp header value'); - } - } - return $requestTimestamp; - }); -$getConsoleDB - ->setName('getConsoleDB') - ->inject('pools') - ->inject('cache') - ->inject('authorization') - ->inject('connections') - ->setCallback(function (array $pools, Cache $cache, Authorization $authorization) { - return function () use ($pools, $cache, $authorization): array { - $pool = $pools['pools-console-console']['pool']; - $dsn = $pools['pools-console-console']['dsn']; - $connection = $pool->get(); - - $adapter = match ($dsn->getScheme()) { - 'mariadb' => new MariaDB($connection), - 'mysql' => new MySQL($connection), - default => null - }; - - $adapter->setDatabase($dsn->getPath()); - - $database = new Database($adapter, $cache); - $database->setAuthorization($authorization); - - $database - ->setNamespace('_console') - ->setMetadata('host', \gethostname()) - ->setMetadata('project', 'console') - ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); - - return [$connection, $pool, $database]; - }; - }); - -$getProjectDB - ->setName('getProjectDB') - ->inject('pools') - ->inject('dbForConsole') - ->inject('cache') - ->inject('authorization') - ->inject('connections') - ->setCallback(function (array $pools, Database $dbForConsole, Cache $cache, Authorization $authorization, Connections $connections) { - $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools - - return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases, $authorization, $connections): Database { - if ($project->isEmpty() || $project->getId() === 'console') { - return $dbForConsole; - } - - 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')); - } - - if (isset($databases[$dsn->getHost()])) { - $database = $databases[$dsn->getHost()]; - - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { - $database - ->setSharedTables(true) - ->setTenant($project->getInternalId()) - ->setNamespace($dsn->getParam('namespace')); - } else { - $database - ->setSharedTables(false) - ->setTenant(null) - ->setNamespace('_' . $project->getInternalId()); - } - - return $database; - } - - $pool = $pools['pools-database-' . $dsn->getHost()]['pool']; - $connectionDsn = $pools['pools-database-' . $dsn->getHost()]['dsn']; - - $connection = $pool->get(); - $connections->add($connection, $pool); - $adapter = match ($connectionDsn->getScheme()) { - 'mariadb' => new MariaDB($connection), - 'mysql' => new MySQL($connection), - default => null - }; - $adapter->setDatabase($connectionDsn->getPath()); - - $database = new Database($adapter, $cache); - $database->setAuthorization($authorization); - - $databases[$dsn->getHost()] = $database; - - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { - $database - ->setSharedTables(true) - ->setTenant($project->getInternalId()) - ->setNamespace($dsn->getParam('namespace')); - } else { - $database - ->setSharedTables(false) - ->setTenant(null) - ->setNamespace('_' . $project->getInternalId()); - } - - return $database; - }; - }); - -$promiseAdapter - ->setName('promiseAdapter') - ->setCallback(function () use ($registry) { - return $registry->get('promiseAdapter'); - }); - -$schemaVariable - ->setName('schemaVariable') - ->setCallback(fn () => new Schema()); - -$schema - ->setName('schema') - ->inject('http') - ->inject('context') - ->inject('request') - ->inject('response') - ->inject('dbForProject') - ->inject('authorization') - ->inject('schemaVariable') - ->setCallback(function (Http $http, Container $context, Request $request, Response $response, Database $dbForProject, Authorization $authorization, $schemaVariable) { - $complexity = function (int $complexity, array $args) { - $queries = Query::parseQueries($args['queries'] ?? []); - $query = Query::getByType($queries, [Query::TYPE_LIMIT])[0] ?? null; - $limit = $query ? $query->getValue() : APP_LIMIT_LIST_DEFAULT; - - return $complexity * $limit; - }; - - $attributes = function (int $limit, int $offset) use ($dbForProject, $authorization) { - $attrs = $authorization->skip(fn () => $dbForProject->find('attributes', [ - Query::limit($limit), - Query::offset($offset), - ])); - - return \array_map(function ($attr) { - return $attr->getArrayCopy(); - }, $attrs); - }; - - $urls = [ - 'list' => function (string $databaseId, string $collectionId, array $args) { - return "/v1/databases/$databaseId/collections/$collectionId/documents"; - }, - 'create' => function (string $databaseId, string $collectionId, array $args) { - return "/v1/databases/$databaseId/collections/$collectionId/documents"; - }, - 'read' => function (string $databaseId, string $collectionId, array $args) { - return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}"; - }, - 'update' => function (string $databaseId, string $collectionId, array $args) { - return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}"; - }, - 'delete' => function (string $databaseId, string $collectionId, array $args) { - return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}"; - }, - ]; - - $params = [ - 'list' => function (string $databaseId, string $collectionId, array $args) { - return ['queries' => $args['queries']]; - }, - 'create' => function (string $databaseId, string $collectionId, array $args) { - $id = $args['id'] ?? 'unique()'; - $permissions = $args['permissions'] ?? null; - - unset($args['id']); - unset($args['permissions']); - - return [ - 'databaseId' => $databaseId, - 'documentId' => $id, - 'collectionId' => $collectionId, - 'data' => $args, - 'permissions' => $permissions, - ]; - }, - 'update' => function (string $databaseId, string $collectionId, array $args) { - $documentId = $args['id']; - $permissions = $args['permissions'] ?? null; - - unset($args['id']); - unset($args['permissions']); - - return [ - 'databaseId' => $databaseId, - 'collectionId' => $collectionId, - 'documentId' => $documentId, - 'data' => $args, - 'permissions' => $permissions, - ]; - }, - ]; - - return $schemaVariable->build( - $http, - $request, - $response, - $context, - $complexity, - $attributes, - $urls, - $params, - ); - }); - -$container->set($log); -$container->set($mode); -$container->set($user); -$container->set($team); -$container->set($plan); -$container->set($cache); -$container->set($pools); -$container->set($queue); -$container->set($geodb); -$container->set($hooks); -$container->set($locale); -$container->set($schema); -$container->set($github); -$container->set($logger); -$container->set($session); -$container->set($console); -$container->set($project); -$container->set($clients); -$container->set($servers); -$container->set($register); -$container->set($connections); -$container->set($localeCodes); -$container->set($dbForProject); -$container->set($dbForConsole); -$container->set($getConsoleDB); -$container->set($getProjectDB); -$container->set($queueForUsage); -$container->set($queueForMails); -$container->set($authorization); -$container->set($authentication); -$container->set($schemaVariable); -$container->set($queueForBuilds); -$container->set($queueForEvents); -$container->set($queueForAudits); -$container->set($deviceForLocal); -$container->set($deviceForFiles); -$container->set($promiseAdapter); -$container->set($queueForDeletes); -$container->set($deviceForBuilds); -$container->set($queueForDatabase); -$container->set($requestTimestamp); -$container->set($queueForMessaging); -$container->set($queueForFunctions); -$container->set($queueForMigrations); -$container->set($deviceForFunctions); -$container->set($passwordsDictionary); -$container->set($queueForCertificates); diff --git a/app/realtime.php b/app/realtime.php index 8f2b994b50..1b59eb3bc7 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -5,47 +5,136 @@ use Appwrite\Extend\Exception; use Appwrite\Extend\Exception as AppwriteException; use Appwrite\Messaging\Adapter\Realtime; use Appwrite\Network\Validator\Origin; -use Appwrite\Utopia\Queue\Connections; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use Swoole\Http\Request as SwooleRequest; -use Swoole\Http\Response as SwooleHttpResponse; use Swoole\Http\Response as SwooleResponse; use Swoole\Runtime; use Swoole\Table; use Swoole\Timer; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapters\Database\TimeLimit; +use Utopia\App; +use Utopia\Cache\Adapter\Sharding; +use Utopia\Cache\Cache; use Utopia\CLI\Console; +use Utopia\Config\Config; +use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; -use Utopia\DI\Container; -use Utopia\DI\Dependency; -use Utopia\Http\Adapter\Swoole\Request as UtopiaRequest; -use Utopia\Http\Adapter\Swoole\Response as HttpResponse; -use Utopia\Http\Adapter\Swoole\Response as UtopiaResponse; -use Utopia\Http\Http; +use Utopia\Database\Validator\Authorization; +use Utopia\DSN\DSN; use Utopia\Logger\Log; -use Utopia\Pools\Connection; -use Utopia\Registry\Registry; use Utopia\System\System; use Utopia\WebSocket\Adapter; use Utopia\WebSocket\Server; /** - * @var Registry $registry - * @var Container $container + * @var \Utopia\Registry\Registry $register */ -global $registry, $container; - - require_once __DIR__ . '/init.php'; Runtime::enableCoroutine(SWOOLE_HOOK_ALL); +// Allows overriding +if (!function_exists('getConsoleDB')) { + function getConsoleDB(): Database + { + global $register; + + /** @var \Utopia\Pools\Group $pools */ + $pools = $register->get('pools'); + + $dbAdapter = $pools + ->get('console') + ->pop() + ->getResource() + ; + + $database = new Database($dbAdapter, getCache()); + + $database + ->setNamespace('_console') + ->setMetadata('host', \gethostname()) + ->setMetadata('project', '_console'); + + return $database; + } +} + +// Allows overriding +if (!function_exists('getProjectDB')) { + function getProjectDB(Document $project): Database + { + global $register; + + /** @var \Utopia\Pools\Group $pools */ + $pools = $register->get('pools'); + + if ($project->isEmpty() || $project->getId() === 'console') { + return getConsoleDB(); + } + + 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')); + } + + $adapter = $pools + ->get($dsn->getHost()) + ->pop() + ->getResource(); + + $database = new Database($adapter, getCache()); + + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + $database + ->setSharedTables(true) + ->setTenant($project->getInternalId()) + ->setNamespace($dsn->getParam('namespace')); + } else { + $database + ->setSharedTables(false) + ->setTenant(null) + ->setNamespace('_' . $project->getInternalId()); + } + + $database + ->setMetadata('host', \gethostname()) + ->setMetadata('project', $project->getId()); + + return $database; + } +} + +// Allows overriding +if (!function_exists('getCache')) { + function getCache(): Cache + { + global $register; + + $pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */ + + $list = Config::getParam('pools-cache', []); + $adapters = []; + + foreach ($list as $value) { + $adapters[] = $pools + ->get($value) + ->pop() + ->getResource() + ; + } + + return new Cache(new Sharding($adapters)); + } +} + if (!function_exists('getRealtime')) { function getRealtime(): Realtime { @@ -77,8 +166,8 @@ $adapter $server = new Server($adapter); -$logError = function (Throwable $error, string $action) use ($registry) { - $logger = $registry->get('logger'); +$logError = function (Throwable $error, string $action) use ($register) { + $logger = $register->get('logger'); if ($logger && !$error instanceof Exception) { $version = System::getEnv('_APP_VERSION', 'UNKNOWN'); @@ -118,16 +207,16 @@ $logError = function (Throwable $error, string $action) use ($registry) { $server->error($logError); -$server->onStart(function () use ($stats, $container, $containerId, &$statsDocument, $logError) { +$server->onStart(function () use ($stats, $register, $containerId, &$statsDocument, $logError) { sleep(5); // wait for the initial database schema to be ready Console::success('Server started successfully'); - $authorization = $container->get('authorization'); + /** * Create document for this worker to share stats across Containers. */ - go(function () use ($container, $containerId, &$statsDocument) { + go(function () use ($register, $containerId, &$statsDocument) { $attempts = 0; - $database = $container->get('dbForConsole'); + $database = getConsoleDB(); do { try { @@ -141,15 +230,14 @@ $server->onStart(function () use ($stats, $container, $containerId, &$statsDocum 'value' => '{}' ]); - $authorization = $container->get('authorization'); - $statsDocument = $authorization->skip(fn () => $database->createDocument('realtime', $document)); + $statsDocument = Authorization::skip(fn () => $database->createDocument('realtime', $document)); break; } catch (Throwable) { Console::warning("Collection not ready. Retrying connection ({$attempts})..."); sleep(DATABASE_RECONNECT_SLEEP); } } while (true); - ($container->get('connections'))->reclaim(); + $register->get('pools')->reclaim(); }); /** @@ -157,7 +245,7 @@ $server->onStart(function () use ($stats, $container, $containerId, &$statsDocum */ // TODO: Remove this if check once it doesn't cause issues for cloud if (System::getEnv('_APP_EDITION', 'self-hosted') === 'self-hosted') { - Timer::tick(5000, function () use ($container, $stats, &$statsDocument, $logError, $authorization) { + Timer::tick(5000, function () use ($register, $stats, &$statsDocument, $logError) { $payload = []; foreach ($stats as $projectId => $value) { $payload[$projectId] = $stats->get($projectId, 'connectionsTotal'); @@ -167,43 +255,40 @@ $server->onStart(function () use ($stats, $container, $containerId, &$statsDocum } try { - $database = $container->get('dbForConsole'); + $database = getConsoleDB(); $statsDocument ->setAttribute('timestamp', DateTime::now()) ->setAttribute('value', json_encode($payload)); - $authorization->skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument)); + Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument)); } catch (Throwable $th) { call_user_func($logError, $th, "updateWorkerDocument"); } finally { - ($container->get('connections'))->reclaim(); - $container->refresh('dbForConsole'); + $register->get('pools')->reclaim(); } }); } }); -$server->onWorkerStart(function (int $workerId) use ($server, $container, $stats, $realtime, $logError) { +$server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $realtime, $logError) { Console::success('Worker ' . $workerId . ' started successfully'); $attempts = 0; $start = time(); - $authorization = $container->get('authorization'); - - Timer::tick(5000, function () use ($server, $container, $realtime, $stats, $logError, $authorization) { + Timer::tick(5000, function () use ($server, $register, $realtime, $stats, $logError) { /** * Sending current connections to project channels on the console project every 5 seconds. */ // TODO: Remove this if check once it doesn't cause issues for cloud if (System::getEnv('_APP_EDITION', 'self-hosted') === 'self-hosted') { if ($realtime->hasSubscriber('console', Role::users()->toString(), 'project')) { - $database = $container->get('dbForConsole'); + $database = getConsoleDB(); $payload = []; - $list = $authorization->skip(fn () => $database->find('realtime', [ + $list = Authorization::skip(fn () => $database->find('realtime', [ Query::greaterThan('timestamp', DateTime::addSeconds(new \DateTime(), -15)), ])); @@ -243,8 +328,8 @@ $server->onWorkerStart(function (int $workerId) use ($server, $container, $stats 'data' => $event['data'] ])); } - ($container->get('connections'))->reclaim(); - $container->refresh('dbForConsole'); + + $register->get('pools')->reclaim(); } } /** @@ -274,26 +359,13 @@ $server->onWorkerStart(function (int $workerId) use ($server, $container, $stats while ($attempts < 300) { try { if ($attempts > 0) { - Console::error( - 'Pub/sub connection lost (lasted ' . (time() - $start) . ' seconds, worker: ' . $workerId . '). - Attempting restart in 5 seconds (attempt #' . $attempts . ')' - ); + Console::error('Pub/sub connection lost (lasted ' . (time() - $start) . ' seconds, worker: ' . $workerId . '). + Attempting restart in 5 seconds (attempt #' . $attempts . ')'); sleep(5); // 5 sec delay between connection attempts } - $start = time(); - $pools = $container->get('pools'); - $pool = $pools['pools-pubsub-pubsub']['pool']; - - /** @var Connections $connections */ - $connections = $container->get('connections'); - $connection = $pool->get(); - $connections->add($connection, $pool); - - $redis = $connection; - - /** @var Redis $redis */ + $redis = $register->get('pools')->get('pubsub')->pop()->getResource(); /** @var Redis $redis */ $redis->setOption(Redis::OPT_READ_TIMEOUT, -1); if ($redis->ping(true)) { @@ -303,7 +375,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $container, $stats Console::error('Pub/sub failed (worker: ' . $workerId . ')'); } - $redis->subscribe(['realtime'], function (Redis $redis, string $channel, string $payload) use ($server, $workerId, $stats, $realtime, $authorization, $container) { + $redis->subscribe(['realtime'], function (Redis $redis, string $channel, string $payload) use ($server, $workerId, $stats, $register, $realtime) { $event = json_decode($payload, true); if ($event['permissionsChanged'] && isset($event['userId'])) { @@ -312,24 +384,25 @@ $server->onWorkerStart(function (int $workerId) use ($server, $container, $stats if ($realtime->hasSubscriber($projectId, 'user:' . $userId)) { $connection = array_key_first(reset($realtime->subscriptions[$projectId]['user:' . $userId])); - $dbForConsole = $container->get('dbForConsole'); + $consoleDatabase = getConsoleDB(); + $project = Authorization::skip(fn () => $consoleDatabase->getDocument('projects', $projectId)); + $database = getProjectDB($project); - $project = $authorization->skip(fn () => $dbForConsole->getDocument('projects', $projectId)); - $dbForProject = $container->get('getProjectDB')($project); + $user = $database->getDocument('users', $userId); - $user = $dbForProject->getDocument('users', $userId); - - $roles = Auth::getRoles($user, $authorization); + $roles = Auth::getRoles($user); $channels = $realtime->connections[$connection]['channels']; $realtime->unsubscribe($connection); $realtime->subscribe($projectId, $connection, $roles, $channels); + + $register->get('pools')->reclaim(); } } $receivers = $realtime->getSubscribers($event); - if (Http::isDevelopment() && !empty($receivers)) { + if (App::isDevelopment() && !empty($receivers)) { Console::log("[Debug][Worker {$workerId}] Receivers: " . count($receivers)); Console::log("[Debug][Worker {$workerId}] Receivers Connection IDs: " . json_encode($receivers)); Console::log("[Debug][Worker {$workerId}] Event: " . $payload); @@ -355,40 +428,30 @@ $server->onWorkerStart(function (int $workerId) use ($server, $container, $stats sleep(DATABASE_RECONNECT_SLEEP); continue; } finally { - ($container->get('connections'))->reclaim(); - $container->refresh('dbForConsole'); + $register->get('pools')->reclaim(); } } Console::error('Failed to restart pub/sub...'); }); -$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $container, $stats, &$realtime, $logError) { - $authorization = $container->get('authorization'); - - $request = new Request(new UtopiaRequest($request)); - $response = new Response(new UtopiaResponse(new SwooleResponse())); - - $requestInjection = new Dependency(); - $responseInjection = new Dependency(); - - $requestInjection->setName('request')->setCallback(fn () => $request); - $responseInjection->setName('response')->setCallback(fn () => $response); - - $container->set($requestInjection); - $container->set($responseInjection); +$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $register, $stats, &$realtime, $logError) { + $app = new App('UTC'); + $request = new Request($request); + $response = new Response(new SwooleResponse()); Console::info("Connection open (user: {$connection})"); + App::setResource('pools', fn () => $register->get('pools')); + App::setResource('request', fn () => $request); + App::setResource('response', fn () => $response); + try { - /** @var Document $project */ - $project = $container->refresh('project')->get('project'); - - $container->refresh('dbForProject'); + $project = $app->getResource('project'); /* - * Project Check + * Project Check */ if (empty($project->getId())) { throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing or unknown project ID'); @@ -397,16 +460,15 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, if ( array_key_exists('realtime', $project->getAttribute('apis', [])) && !$project->getAttribute('apis', [])['realtime'] - && !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles())) + && !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles())) ) { throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED); } - $dbForProject = $container->get('getProjectDB')($project); - /** @var Document $console */ - $console = $container->get('console'); - /** @var Document $user */ - $user = $container->refresh('user')->get('user'); + $dbForProject = getProjectDB($project); + $console = $app->getResource('console'); /** @var Document $console */ + $user = $app->getResource('user'); /** @var Document $user */ + /* * Abuse Check * @@ -435,8 +497,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription()); } - $authorization = $container->get('authorization'); - $roles = Auth::getRoles($user, $authorization); + $roles = Auth::getRoles($user); $channels = Realtime::convertChannels($request->getQuery('channels', []), $user->getId()); @@ -485,33 +546,25 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $server->send([$connection], json_encode($response)); $server->close($connection, $code); - if (Http::isDevelopment()) { + if (App::isDevelopment()) { Console::error('[Error] Connection Error'); Console::error('[Error] Code: ' . $response['data']['code']); Console::error('[Error] Message: ' . $response['data']['message']); } } finally { - $connections = $container->get('connections'); - $connections->reclaim(); + $register->get('pools')->reclaim(); } }); -$server->onWorkerStop(function (int $workerId) use ($container) { - $connections = $container->get('connections'); - $connections->reclaim(); -}); - -$server->onMessage(function (int $connection, string $message) use ($server, $container, $realtime, $containerId) { +$server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) { try { - $response = new Response(new HttpResponse(new SwooleHttpResponse())); + $response = new Response(new SwooleResponse()); $projectId = $realtime->connections[$connection]['projectId']; - $database = $container->get('dbForConsole'); - $authorization = $container->get('authorization'); - $authentication = $container->get('authentication'); + $database = getConsoleDB(); if ($projectId !== 'console') { - $project = $authorization->skip(fn () => $database->getDocument('projects', $projectId)); - $database = $container->get('getProjectDB')($project); + $project = Authorization::skip(fn () => $database->getDocument('projects', $projectId)); + $database = getProjectDB($project); } else { $project = null; } @@ -549,21 +602,20 @@ $server->onMessage(function (int $connection, string $message) use ($server, $co } $session = Auth::decodeSession($message['data']['session']); + Auth::$unique = $session['id'] ?? ''; + Auth::$secret = $session['secret'] ?? ''; - $authentication->setUnique($session['id'] ?? ''); - $authentication->setSecret($session['secret'] ?? ''); - - $user = $database->getDocument('users', $authentication->getUnique()); + $user = $database->getDocument('users', Auth::$unique); if ( empty($user->getId()) // Check a document has been found in the DB - || !Auth::sessionVerify($user->getAttribute('sessions', []), $authentication->getSecret()) // Validate user has valid login token + || !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) // Validate user has valid login token ) { // cookie not valid throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.'); } - $roles = Auth::getRoles($user, $authorization); + $roles = Auth::getRoles($user); $channels = Realtime::convertChannels(array_flip($realtime->connections[$connection]['channels']), $user->getId()); $realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels); @@ -597,8 +649,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $co $server->close($connection, $th->getCode()); } } finally { - ($container->get('connections'))->reclaim(); - $container->refresh('dbForConsole'); + $register->get('pools')->reclaim(); } }); diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml index 4365bf39fc..ad35135a6f 100644 --- a/app/views/install/compose.phtml +++ b/app/views/install/compose.phtml @@ -11,8 +11,7 @@ $httpsPort = $this->getParam('httpsPort', ''); $version = $this->getParam('version', ''); $organization = $this->getParam('organization', ''); $image = $this->getParam('image', ''); -?> -services: +?>services: traefik: image: traefik:2.11 container_name: appwrite-traefik @@ -854,7 +853,7 @@ services: - MYSQL_USER=${_APP_DB_USER} - MYSQL_PASSWORD=${_APP_DB_PASS} - MARIADB_AUTO_UPGRADE=1 - command: 'mysqld --innodb-flush-method=fsync --max_connections=5000' + command: 'mysqld --innodb-flush-method=fsync' redis: image: redis:7.2.4-alpine diff --git a/app/worker.php b/app/worker.php index 47093a31e1..2d59259284 100644 --- a/app/worker.php +++ b/app/worker.php @@ -2,107 +2,278 @@ require_once __DIR__ . '/init.php'; +use Appwrite\Event\Audit; +use Appwrite\Event\Build; +use Appwrite\Event\Certificate; +use Appwrite\Event\Database as EventDatabase; +use Appwrite\Event\Delete; +use Appwrite\Event\Event; +use Appwrite\Event\Func; +use Appwrite\Event\Mail; +use Appwrite\Event\Messaging; +use Appwrite\Event\Migration; +use Appwrite\Event\Usage; use Appwrite\Event\UsageDump; use Appwrite\Platform\Appwrite; -use Appwrite\Utopia\Queue\Connections; use Swoole\Runtime; +use Utopia\App; +use Utopia\Cache\Adapter\Sharding; +use Utopia\Cache\Cache; use Utopia\CLI\Console; +use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -use Utopia\DI\Dependency; +use Utopia\DSN\DSN; use Utopia\Logger\Log; use Utopia\Logger\Logger; use Utopia\Platform\Service; +use Utopia\Pools\Group; use Utopia\Queue\Connection; use Utopia\Queue\Message; -use Utopia\Queue\Worker; -use Utopia\Storage\Device\Local; +use Utopia\Queue\Server; +use Utopia\Registry\Registry; use Utopia\System\System; -global $registry, $container; - +Authorization::disable(); Runtime::enableCoroutine(SWOOLE_HOOK_ALL); -$project = new Dependency(); -$register = new Dependency(); -$dbForProject = new Dependency(); -$abuseRetention = new Dependency(); -$deviceForCache = new Dependency(); -$auditRetention = new Dependency(); -$queueForUsageDump = new Dependency(); -$executionRetention = new Dependency(); -$deviceForLocalFiles = new Dependency(); +Server::setResource('register', fn () => $register); -$register - ->setName('register') - ->setCallback(fn () => $registry); +Server::setResource('dbForConsole', function (Cache $cache, Registry $register) { + $pools = $register->get('pools'); + $database = $pools + ->get('console') + ->pop() + ->getResource(); -$project - ->setName('project') - ->inject('message') - ->inject('dbForConsole') - ->setCallback(function (Message $message, Database $dbForConsole) { - $payload = $message->getPayload() ?? []; - $project = new Document($payload['project'] ?? []); + $adapter = new Database($database, $cache); + $adapter->setNamespace('_console'); - if ($project->getId() === 'console' || $project->isEmpty() || ! empty($project->getInternalId())) { - return $project; + return $adapter; +}, ['cache', 'register']); + +Server::setResource('project', function (Message $message, Database $dbForConsole) { + $payload = $message->getPayload() ?? []; + $project = new Document($payload['project'] ?? []); + + if ($project->getId() === 'console' || $project->isEmpty() || ! empty($project->getInternalId())) { + return $project; + } + + return $dbForConsole->getDocument('projects', $project->getId()); +}, ['message', 'dbForConsole']); + +Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForConsole) { + if ($project->isEmpty() || $project->getId() === 'console') { + return $dbForConsole; + } + + $pools = $register->get('pools'); + + 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')); + } + + $adapter = $pools + ->get($dsn->getHost()) + ->pop() + ->getResource(); + + $database = new Database($adapter, $cache); + + 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')); + } + + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + $database + ->setSharedTables(true) + ->setTenant($project->getInternalId()) + ->setNamespace($dsn->getParam('namespace')); + } else { + $database + ->setSharedTables(false) + ->setTenant(null) + ->setNamespace('_' . $project->getInternalId()); + } + + return $database; +}, ['cache', 'register', 'message', 'project', 'dbForConsole']); + +Server::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) { + $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools + + return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases): Database { + if ($project->isEmpty() || $project->getId() === 'console') { + return $dbForConsole; } - return $dbForConsole->getDocument('projects', $project->getId()); - }); + 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')); + } -$abuseRetention - ->setName('abuseRetention') - ->setCallback(function () { - return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400)); - }); + if (isset($databases[$dsn->getHost()])) { + $database = $databases[$dsn->getHost()]; -$auditRetention - ->setName('auditRetention') - ->setCallback(function () { - return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600)); - }); + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + $database + ->setSharedTables(true) + ->setTenant($project->getInternalId()) + ->setNamespace($dsn->getParam('namespace')); + } else { + $database + ->setSharedTables(false) + ->setTenant(null) + ->setNamespace('_' . $project->getInternalId()); + } -$executionRetention - ->setName('executionRetention') - ->setCallback(function () { - return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600)); - }); + return $database; + } -$queueForUsageDump - ->setName('queueForUsageDump') - ->inject('queue') - ->setCallback(function (Connection $queue) { - return new UsageDump($queue); - }); + $dbAdapter = $pools + ->get($dsn->getHost()) + ->pop() + ->getResource(); -$deviceForCache - ->setName('deviceForCache') - ->inject('project') - ->setCallback(function (Document $project) { - return getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId()); - }); + $database = new Database($dbAdapter, $cache); -$deviceForLocalFiles - ->setName('deviceForLocalFiles') - ->inject('project') - ->setCallback(function (Document $project) { - return new Local(APP_STORAGE_UPLOADS . '/app-' . $project->getId()); - }); + $databases[$dsn->getHost()] = $database; -$container->set($project); -$container->set($register); -$container->set($dbForProject); -$container->set($abuseRetention); -$container->set($auditRetention); -$container->set($deviceForCache); -$container->set($queueForUsageDump); -$container->set($executionRetention); -$container->set($deviceForLocalFiles); + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + $database + ->setSharedTables(true) + ->setTenant($project->getInternalId()) + ->setNamespace($dsn->getParam('namespace')); + } else { + $database + ->setSharedTables(false) + ->setTenant(null) + ->setNamespace('_' . $project->getInternalId()); + } + return $database; + }; +}, ['pools', 'dbForConsole', 'cache']); + +Server::setResource('abuseRetention', function () { + return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400)); +}); + +Server::setResource('auditRetention', function () { + return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600)); +}); + +Server::setResource('executionRetention', function () { + return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600)); +}); + +Server::setResource('cache', function (Registry $register) { + $pools = $register->get('pools'); + $list = Config::getParam('pools-cache', []); + $adapters = []; + + foreach ($list as $value) { + $adapters[] = $pools + ->get($value) + ->pop() + ->getResource() + ; + } + + return new Cache(new Sharding($adapters)); +}, ['register']); + +Server::setResource('log', fn () => new Log()); + +Server::setResource('queueForUsage', function (Connection $queue) { + return new Usage($queue); +}, ['queue']); + +Server::setResource('queueForUsageDump', function (Connection $queue) { + return new UsageDump($queue); +}, ['queue']); + +Server::setResource('queue', function (Group $pools) { + return $pools->get('queue')->pop()->getResource(); +}, ['pools']); + +Server::setResource('queueForDatabase', function (Connection $queue) { + return new EventDatabase($queue); +}, ['queue']); + +Server::setResource('queueForMessaging', function (Connection $queue) { + return new Messaging($queue); +}, ['queue']); + +Server::setResource('queueForMails', function (Connection $queue) { + return new Mail($queue); +}, ['queue']); + +Server::setResource('queueForBuilds', function (Connection $queue) { + return new Build($queue); +}, ['queue']); + +Server::setResource('queueForDeletes', function (Connection $queue) { + return new Delete($queue); +}, ['queue']); + +Server::setResource('queueForEvents', function (Connection $queue) { + return new Event($queue); +}, ['queue']); + +Server::setResource('queueForAudits', function (Connection $queue) { + return new Audit($queue); +}, ['queue']); + +Server::setResource('queueForFunctions', function (Connection $queue) { + return new Func($queue); +}, ['queue']); + +Server::setResource('queueForCertificates', function (Connection $queue) { + return new Certificate($queue); +}, ['queue']); + +Server::setResource('queueForMigrations', function (Connection $queue) { + return new Migration($queue); +}, ['queue']); + +Server::setResource('logger', function (Registry $register) { + return $register->get('logger'); +}, ['register']); + +Server::setResource('pools', function (Registry $register) { + return $register->get('pools'); +}, ['register']); + +Server::setResource('deviceForFunctions', function (Document $project) { + return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId()); +}, ['project']); + +Server::setResource('deviceForFiles', function (Document $project) { + return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId()); +}, ['project']); + +Server::setResource('deviceForBuilds', function (Document $project) { + return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId()); +}, ['project']); + +Server::setResource('deviceForCache', function (Document $project) { + return getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId()); +}, ['project']); + + +$pools = $register->get('pools'); $platform = new Appwrite(); $args = $platform->getEnv('argv'); @@ -121,13 +292,6 @@ if (\str_starts_with($workerName, 'databases')) { } try { - $connection = new Connection\Redis( - System::getEnv('_APP_REDIS_HOST', 'redis'), - System::getEnv('_APP_REDIS_PORT', '6379'), - System::getEnv('_APP_REDIS_USER', ''), - System::getEnv('_APP_REDIS_PASS', '') - ); - /** * Any worker can be configured with the following env vars: * - _APP_WORKERS_NUM The total number of worker processes @@ -136,35 +300,32 @@ try { */ $platform->init(Service::TYPE_WORKER, [ 'workersNum' => System::getEnv('_APP_WORKERS_NUM', 1), - 'connection' => $connection, + 'connection' => $pools->get('queue')->pop()->getResource(), 'workerName' => strtolower($workerName) ?? null, 'queueName' => $queueName ]); } catch (\Throwable $e) { - Console::error($e->getMessage() . ', File: ' . $e->getFile() . ', Line: ' . $e->getLine()); + Console::error($e->getMessage() . ', File: ' . $e->getFile() . ', Line: ' . $e->getLine()); } -Worker::init() - ->inject('authorization') - ->action(function (Authorization $authorization) { - $authorization->disable(); +$worker = $platform->getWorker(); + +$worker + ->shutdown() + ->inject('pools') + ->action(function (Group $pools) { + $pools->reclaim(); }); -Worker::shutdown() - ->inject('connections') - ->action(function (Connections $connections) { - $connections->reclaim(); - }); - -Worker::error() +$worker + ->error() ->inject('error') ->inject('logger') ->inject('log') - ->inject('connections') + ->inject('pools') ->inject('project') - ->inject('authorization') - ->action(function (Throwable $error, ?Logger $logger, Log $log, Connections $connections, Document $project, Authorization $authorization) use ($queueName) { - $connections->reclaim(); + ->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project) use ($queueName) { + $pools->reclaim(); $version = System::getEnv('_APP_VERSION', 'UNKNOWN'); if ($logger) { @@ -180,7 +341,7 @@ Worker::error() $log->addExtra('file', $error->getFile()); $log->addExtra('line', $error->getLine()); $log->addExtra('trace', $error->getTraceAsString()); - $log->addExtra('roles', $authorization->getRoles()); + $log->addExtra('roles', Authorization::getRoles()); $isProduction = System::getEnv('_APP_ENV', 'development') === 'production'; $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING); @@ -199,7 +360,9 @@ Worker::error() Console::error('[Error] Line: ' . $error->getLine()); }); -$platform - ->getWorker() - ->setContainer($container) - ->start(); +$worker->workerStart() + ->action(function () use ($workerName) { + Console::info("Worker $workerName started"); + }); + +$worker->start(); diff --git a/composer.json b/composer.json index 135822bc9e..c176d383f3 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,6 @@ "description": "End to end backend server for frontend and mobile apps.", "type": "project", "license": "BSD-3-Clause", - "minimum-stability": "stable", "authors": [ { "name": "Eldad Fux", @@ -15,8 +14,7 @@ "test": "vendor/bin/phpunit", "lint": "vendor/bin/pint --test", "format": "vendor/bin/pint", - "bench": "vendor/bin/phpbench run --report=benchmark", - "check": "./vendor/bin/phpstan analyse -c phpstan.neon --memory-limit 1G app src tests" + "bench": "vendor/bin/phpbench run --report=benchmark" }, "autoload": { "psr-4": { @@ -47,33 +45,33 @@ "ext-sockets": "*", "appwrite/php-runtimes": "0.15.*", "appwrite/php-clamav": "2.0.*", - "utopia-php/abuse": "0.46.*", - "utopia-php/analytics": "0.13.*", - "utopia-php/audit": "0.46.*", + "utopia-php/abuse": "0.43.0", + "utopia-php/analytics": "0.10.*", + "utopia-php/audit": "0.43.0", "utopia-php/cache": "0.10.*", - "utopia-php/cli": "0.19.*", + "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.55.1", - "utopia-php/domains": "0.6.*", - "utopia-php/dsn": "0.2.*", - "utopia-php/framework": "1.0.*", + "utopia-php/database": "0.53.5", + "utopia-php/domains": "0.5.*", + "utopia-php/dsn": "0.2.1", + "utopia-php/framework": "0.33.*", "utopia-php/fetch": "0.2.*", "utopia-php/image": "0.7.*", "utopia-php/locale": "0.4.*", "utopia-php/logger": "0.6.*", "utopia-php/messaging": "0.12.*", - "utopia-php/migration": "0.7.*", - "utopia-php/orchestration": "0.15.*", - "utopia-php/platform": "0.8.*", - "utopia-php/view": "0.2.*", + "utopia-php/migration": "0.6.*", + "utopia-php/orchestration": "0.9.*", + "utopia-php/platform": "0.7.*", "utopia-php/pools": "0.5.*", "utopia-php/preloader": "0.2.*", - "utopia-php/queue": "0.8.*", + "utopia-php/queue": "0.7.*", "utopia-php/registry": "0.5.*", - "utopia-php/storage": "0.19.*", + "utopia-php/storage": "0.18.*", + "utopia-php/swoole": "0.8.*", "utopia-php/system": "0.8.*", - "utopia-php/vcs": "0.9.*", - "utopia-php/websocket": "0.2.*", + "utopia-php/vcs": "0.8.*", + "utopia-php/websocket": "0.1.*", "matomo/device-detector": "6.1.*", "dragonmantank/cron-expression": "3.3.2", "phpmailer/phpmailer": "6.9.1", @@ -90,8 +88,7 @@ "swoole/ide-helper": "5.1.2", "textalk/websocket": "1.5.7", "laravel/pint": "^1.14", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "1.8.*" + "phpbench/phpbench": "^1.2" }, "provide": { "ext-phpiredis": "*" diff --git a/composer.lock b/composer.lock index cb468c1663..d2c82a65d7 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "b808f001c3ff4c0396cf51f46e8b314a", + "content-hash": "1884e3a2966762c4a955842426b64f6c", "packages": [ { "name": "adhocore/jwt", @@ -1430,16 +1430,16 @@ }, { "name": "utopia-php/abuse", - "version": "0.46.0", + "version": "0.43.0", "source": { "type": "git", "url": "https://github.com/utopia-php/abuse.git", - "reference": "ab09ecf6ceac5420020e6046ec651c155005d3c3" + "reference": "6346a3b4c5177a43160035a7289e30fdfb0790d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/ab09ecf6ceac5420020e6046ec651c155005d3c3", - "reference": "ab09ecf6ceac5420020e6046ec651c155005d3c3", + "url": "https://api.github.com/repos/utopia-php/abuse/zipball/6346a3b4c5177a43160035a7289e30fdfb0790d6", + "reference": "6346a3b4c5177a43160035a7289e30fdfb0790d6", "shasum": "" }, "require": { @@ -1447,7 +1447,7 @@ "ext-pdo": "*", "ext-redis": "*", "php": ">=8.0", - "utopia-php/database": "0.55.*" + "utopia-php/database": "0.53.*" }, "require-dev": { "laravel/pint": "1.5.*", @@ -1475,28 +1475,27 @@ ], "support": { "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/0.46.0" + "source": "https://github.com/utopia-php/abuse/tree/0.43.0" }, - "time": "2024-10-07T19:09:29+00:00" + "time": "2024-08-30T05:17:23+00:00" }, { "name": "utopia-php/analytics", - "version": "0.13.0", + "version": "0.10.2", "source": { "type": "git", "url": "https://github.com/utopia-php/analytics.git", - "reference": "3dace02af5d4190623f88fb6e02f5559a99f14c4" + "reference": "14c805114736f44c26d6d24b176a2f8b93d86a1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/analytics/zipball/3dace02af5d4190623f88fb6e02f5559a99f14c4", - "reference": "3dace02af5d4190623f88fb6e02f5559a99f14c4", + "url": "https://api.github.com/repos/utopia-php/analytics/zipball/14c805114736f44c26d6d24b176a2f8b93d86a1f", + "reference": "14c805114736f44c26d6d24b176a2f8b93d86a1f", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/cli": "0.19.*", - "utopia-php/system": "0.8.*" + "utopia-php/cli": "^0.15.0" }, "require-dev": { "laravel/pint": "dev-main", @@ -1522,27 +1521,27 @@ ], "support": { "issues": "https://github.com/utopia-php/analytics/issues", - "source": "https://github.com/utopia-php/analytics/tree/0.13.0" + "source": "https://github.com/utopia-php/analytics/tree/0.10.2" }, - "time": "2024-09-05T16:19:26+00:00" + "time": "2023-03-22T12:01:09+00:00" }, { "name": "utopia-php/audit", - "version": "0.46.0", + "version": "0.43.0", "source": { "type": "git", "url": "https://github.com/utopia-php/audit.git", - "reference": "660bb322ca1e6a9fa121610a85930e65863e1e5d" + "reference": "cef22b5dc6a6d28fcd522f41c7bf7ded4a4dfd3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/audit/zipball/660bb322ca1e6a9fa121610a85930e65863e1e5d", - "reference": "660bb322ca1e6a9fa121610a85930e65863e1e5d", + "url": "https://api.github.com/repos/utopia-php/audit/zipball/cef22b5dc6a6d28fcd522f41c7bf7ded4a4dfd3e", + "reference": "cef22b5dc6a6d28fcd522f41c7bf7ded4a4dfd3e", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/database": "0.55.*" + "utopia-php/database": "0.53.*" }, "require-dev": { "laravel/pint": "1.5.*", @@ -1569,9 +1568,9 @@ ], "support": { "issues": "https://github.com/utopia-php/audit/issues", - "source": "https://github.com/utopia-php/audit/tree/0.46.0" + "source": "https://github.com/utopia-php/audit/tree/0.43.0" }, - "time": "2024-10-07T19:10:12+00:00" + "time": "2024-08-30T05:17:36+00:00" }, { "name": "utopia-php/cache", @@ -1625,29 +1624,27 @@ }, { "name": "utopia-php/cli", - "version": "0.19.0", + "version": "0.15.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cli.git", - "reference": "f8af1d6087f498bc1f0191750a118d357ded9948" + "reference": "ccb7c8125ffe0254fef8f25744bfa376eb7bd0ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cli/zipball/f8af1d6087f498bc1f0191750a118d357ded9948", - "reference": "f8af1d6087f498bc1f0191750a118d357ded9948", + "url": "https://api.github.com/repos/utopia-php/cli/zipball/ccb7c8125ffe0254fef8f25744bfa376eb7bd0ea", + "reference": "ccb7c8125ffe0254fef8f25744bfa376eb7bd0ea", "shasum": "" }, "require": { "php": ">=7.4", - "utopia-php/di": "0.1.*", - "utopia-php/framework": "1.0.*" + "utopia-php/framework": "0.*.*" }, "require-dev": { "laravel/pint": "1.2.*", - "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.3", "squizlabs/php_codesniffer": "^3.6", - "swoole/ide-helper": "4.8.8" + "vimeo/psalm": "4.0.1" }, "type": "library", "autoload": { @@ -1670,9 +1667,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cli/issues", - "source": "https://github.com/utopia-php/cli/tree/0.19.0" + "source": "https://github.com/utopia-php/cli/tree/0.15.0" }, - "time": "2024-09-05T15:46:56+00:00" + "time": "2023-03-01T05:55:14+00:00" }, { "name": "utopia-php/config", @@ -1727,16 +1724,16 @@ }, { "name": "utopia-php/database", - "version": "0.55.1", + "version": "0.53.5", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "6f44079999491feb0ef85686f6b2ac7ef54db828" + "reference": "689ba22063bf46def385da8695ba7a921e81e38d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/6f44079999491feb0ef85686f6b2ac7ef54db828", - "reference": "6f44079999491feb0ef85686f6b2ac7ef54db828", + "url": "https://api.github.com/repos/utopia-php/database/zipball/689ba22063bf46def385da8695ba7a921e81e38d", + "reference": "689ba22063bf46def385da8695ba7a921e81e38d", "shasum": "" }, "require": { @@ -1744,7 +1741,7 @@ "ext-pdo": "*", "php": ">=8.0", "utopia-php/cache": "0.10.*", - "utopia-php/framework": "1.0.*", + "utopia-php/framework": "0.33.*", "utopia-php/mongo": "0.3.*" }, "require-dev": { @@ -1755,7 +1752,7 @@ "phpunit/phpunit": "9.6.*", "rregeer/phpunit-coverage-check": "0.3.*", "swoole/ide-helper": "5.1.3", - "utopia-php/cli": "0.19.*" + "utopia-php/cli": "0.14.*" }, "type": "library", "autoload": { @@ -1777,79 +1774,30 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.55.1" + "source": "https://github.com/utopia-php/database/tree/0.53.5" }, - "time": "2024-10-07T18:52:26+00:00" - }, - { - "name": "utopia-php/di", - "version": "0.1.0", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/di.git", - "reference": "22490c95f7ac3898ed1c33f1b1b5dd577305ee31" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/di/zipball/22490c95f7ac3898ed1c33f1b1b5dd577305ee31", - "reference": "22490c95f7ac3898ed1c33f1b1b5dd577305ee31", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "laravel/pint": "^1.2", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.5.25", - "swoole/ide-helper": "4.8.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\": "src/", - "Tests\\E2E\\": "tests/e2e" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A simple and lite library for managing dependency injections", - "keywords": [ - "framework", - "http", - "php", - "upf" - ], - "support": { - "issues": "https://github.com/utopia-php/di/issues", - "source": "https://github.com/utopia-php/di/tree/0.1.0" - }, - "time": "2024-08-08T14:35:19+00:00" + "time": "2024-09-24T08:43:10+00:00" }, { "name": "utopia-php/domains", - "version": "0.6.0", + "version": "0.5.0", "source": { "type": "git", "url": "https://github.com/utopia-php/domains.git", - "reference": "5c70b0f524deeb1fccc3962ad1da650ae2c94cda" + "reference": "bf07f60326f8389f378ddf6fcde86217e5cfe18c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/domains/zipball/5c70b0f524deeb1fccc3962ad1da650ae2c94cda", - "reference": "5c70b0f524deeb1fccc3962ad1da650ae2c94cda", + "url": "https://api.github.com/repos/utopia-php/domains/zipball/bf07f60326f8389f378ddf6fcde86217e5cfe18c", + "reference": "bf07f60326f8389f378ddf6fcde86217e5cfe18c", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/framework": "1.0.*" + "utopia-php/framework": "0.*.*" }, "require-dev": { "laravel/pint": "1.2.*", - "phpstan/phpstan": "1.9.x-dev", "phpunit/phpunit": "^9.3" }, "type": "library", @@ -1886,9 +1834,9 @@ ], "support": { "issues": "https://github.com/utopia-php/domains/issues", - "source": "https://github.com/utopia-php/domains/tree/0.6.0" + "source": "https://github.com/utopia-php/domains/tree/0.5.0" }, - "time": "2024-09-05T16:21:11+00:00" + "time": "2024-01-03T22:04:27+00:00" }, { "name": "utopia-php/dsn", @@ -1978,30 +1926,26 @@ }, { "name": "utopia-php/framework", - "version": "1.0.2", + "version": "0.33.8", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "fc63ec61c720190a5ea5bb484c615145850951e6" + "reference": "a7f577540a25cb90896fef2b64767bf8d700f3c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/fc63ec61c720190a5ea5bb484c615145850951e6", - "reference": "fc63ec61c720190a5ea5bb484c615145850951e6", + "url": "https://api.github.com/repos/utopia-php/http/zipball/a7f577540a25cb90896fef2b64767bf8d700f3c5", + "reference": "a7f577540a25cb90896fef2b64767bf8d700f3c5", "shasum": "" }, "require": { - "ext-swoole": "*", - "php": ">=8.0", - "utopia-php/servers": "0.1.*" + "php": ">=8.0" }, "require-dev": { - "ext-xdebug": "*", "laravel/pint": "^1.2", "phpbench/phpbench": "^1.2", "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.5.25", - "swoole/ide-helper": "4.8.3" + "phpunit/phpunit": "^9.5.25" }, "type": "library", "autoload": { @@ -2013,18 +1957,17 @@ "license": [ "MIT" ], - "description": "A simple, light and advanced PHP HTTP framework", + "description": "A simple, light and advanced PHP framework", "keywords": [ "framework", - "http", "php", "upf" ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/1.0.2" + "source": "https://github.com/utopia-php/http/tree/0.33.8" }, - "time": "2024-09-10T09:04:19+00:00" + "time": "2024-08-15T14:10:09+00:00" }, { "name": "utopia-php/image", @@ -2232,16 +2175,16 @@ }, { "name": "utopia-php/migration", - "version": "0.7.0", + "version": "0.6.4", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "aa996ebfd3223fca2aa7d8b9aa31457c95344084" + "reference": "e43ef283f1386084e11d1ffe093fb6c6d7a6ce6c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/aa996ebfd3223fca2aa7d8b9aa31457c95344084", - "reference": "aa996ebfd3223fca2aa7d8b9aa31457c95344084", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/e43ef283f1386084e11d1ffe093fb6c6d7a6ce6c", + "reference": "e43ef283f1386084e11d1ffe093fb6c6d7a6ce6c", "shasum": "" }, "require": { @@ -2249,16 +2192,17 @@ "ext-curl": "*", "ext-openssl": "*", "php": "8.3.*", - "utopia-php/database": "0.55.*", + "utopia-php/database": "0.53.*", "utopia-php/dsn": "0.2.*", - "utopia-php/storage": "0.19.*" + "utopia-php/framework": "0.33.*", + "utopia-php/storage": "0.18.*" }, "require-dev": { "ext-pdo": "*", "laravel/pint": "1.17.*", "phpstan/phpstan": "1.11.*", "phpunit/phpunit": "11.2.*", - "utopia-php/cli": "0.19.*", + "utopia-php/cli": "0.16.*", "vlucas/phpdotenv": "5.6.*" }, "type": "library", @@ -2281,9 +2225,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/0.7.0" + "source": "https://github.com/utopia-php/migration/tree/0.6.4" }, - "time": "2024-10-07T19:00:18+00:00" + "time": "2024-10-02T15:16:36+00:00" }, { "name": "utopia-php/mongo", @@ -2347,26 +2291,26 @@ }, { "name": "utopia-php/orchestration", - "version": "0.15.0", + "version": "0.9.1", "source": { "type": "git", "url": "https://github.com/utopia-php/orchestration.git", - "reference": "cd55650ba5f13118c3580048e6dd86b604f9a5b3" + "reference": "55f43513b3f940a3f4f9c2cde7682d0c2581beb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/orchestration/zipball/cd55650ba5f13118c3580048e6dd86b604f9a5b3", - "reference": "cd55650ba5f13118c3580048e6dd86b604f9a5b3", + "url": "https://api.github.com/repos/utopia-php/orchestration/zipball/55f43513b3f940a3f4f9c2cde7682d0c2581beb0", + "reference": "55f43513b3f940a3f4f9c2cde7682d0c2581beb0", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/cli": "0.19.*" + "utopia-php/cli": "0.15.*" }, "require-dev": { "laravel/pint": "^1.2", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.3", + "vimeo/psalm": "4.0.1" }, "type": "library", "autoload": { @@ -2391,32 +2335,31 @@ ], "support": { "issues": "https://github.com/utopia-php/orchestration/issues", - "source": "https://github.com/utopia-php/orchestration/tree/0.15.0" + "source": "https://github.com/utopia-php/orchestration/tree/0.9.1" }, - "time": "2024-09-05T16:28:02+00:00" + "time": "2023-03-17T15:05:06+00:00" }, { "name": "utopia-php/platform", - "version": "0.8.1", + "version": "0.7.0", "source": { "type": "git", "url": "https://github.com/utopia-php/platform.git", - "reference": "95d57f38a4001c7189a66885c485ac635d305234" + "reference": "beeea0f2c9bce14a6869fc5c87a1047cdecb5c52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/platform/zipball/95d57f38a4001c7189a66885c485ac635d305234", - "reference": "95d57f38a4001c7189a66885c485ac635d305234", + "url": "https://api.github.com/repos/utopia-php/platform/zipball/beeea0f2c9bce14a6869fc5c87a1047cdecb5c52", + "reference": "beeea0f2c9bce14a6869fc5c87a1047cdecb5c52", "shasum": "" }, "require": { "ext-json": "*", "ext-redis": "*", "php": ">=8.0", - "utopia-php/cli": "0.19.*", - "utopia-php/framework": "1.0.*", - "utopia-php/queue": "0.8.*", - "utopia-php/servers": "0.1.*" + "utopia-php/cli": "0.15.*", + "utopia-php/framework": "0.33.*", + "utopia-php/queue": "0.7.*" }, "require-dev": { "laravel/pint": "1.2.*", @@ -2442,9 +2385,9 @@ ], "support": { "issues": "https://github.com/utopia-php/platform/issues", - "source": "https://github.com/utopia-php/platform/tree/0.8.1" + "source": "https://github.com/utopia-php/platform/tree/0.7.0" }, - "time": "2024-09-06T02:33:27+00:00" + "time": "2024-05-08T17:00:55+00:00" }, { "name": "utopia-php/pools", @@ -2552,23 +2495,22 @@ }, { "name": "utopia-php/queue", - "version": "0.8.0", + "version": "0.7.0", "source": { "type": "git", "url": "https://github.com/utopia-php/queue.git", - "reference": "a518b271f8c158d6e66e36972f767189111033c2" + "reference": "917565256eb94bcab7246f7a746b1a486813761b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/queue/zipball/a518b271f8c158d6e66e36972f767189111033c2", - "reference": "a518b271f8c158d6e66e36972f767189111033c2", + "url": "https://api.github.com/repos/utopia-php/queue/zipball/917565256eb94bcab7246f7a746b1a486813761b", + "reference": "917565256eb94bcab7246f7a746b1a486813761b", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/cli": "0.19.*", - "utopia-php/di": "0.1.*", - "utopia-php/servers": "0.1.*" + "utopia-php/cli": "0.15.*", + "utopia-php/framework": "0.*.*" }, "require-dev": { "laravel/pint": "^0.2.3", @@ -2578,7 +2520,6 @@ "workerman/workerman": "^4.0" }, "suggest": { - "ext-redis": "Needed to support Redis connections", "ext-swoole": "Needed to support Swoole.", "workerman/workerman": "Needed to support Workerman." }, @@ -2609,9 +2550,9 @@ ], "support": { "issues": "https://github.com/utopia-php/queue/issues", - "source": "https://github.com/utopia-php/queue/tree/0.8.0" + "source": "https://github.com/utopia-php/queue/tree/0.7.0" }, - "time": "2024-09-05T16:33:01+00:00" + "time": "2024-01-17T19:00:43+00:00" }, { "name": "utopia-php/registry", @@ -2665,71 +2606,18 @@ }, "time": "2021-03-10T10:45:22+00:00" }, - { - "name": "utopia-php/servers", - "version": "0.1.1", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/servers.git", - "reference": "fd5c8d32778f265256c1936372a071b944f5ba8a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/servers/zipball/fd5c8d32778f265256c1936372a071b944f5ba8a", - "reference": "fd5c8d32778f265256c1936372a071b944f5ba8a", - "shasum": "" - }, - "require": { - "php": ">=8.0", - "utopia-php/di": "0.1.*" - }, - "require-dev": { - "laravel/pint": "^0.2.3", - "phpstan/phpstan": "^1.8", - "phpunit/phpunit": "^9.5.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\Servers\\": "src/Servers" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Team Appwrite", - "email": "team@appwrite.io" - } - ], - "description": "A base library for building Utopia style servers.", - "keywords": [ - "framework", - "php", - "servers", - "upf", - "utopia" - ], - "support": { - "issues": "https://github.com/utopia-php/servers/issues", - "source": "https://github.com/utopia-php/servers/tree/0.1.1" - }, - "time": "2024-09-06T02:25:56+00:00" - }, { "name": "utopia-php/storage", - "version": "0.19.0", + "version": "0.18.5", "source": { "type": "git", "url": "https://github.com/utopia-php/storage.git", - "reference": "5013b894a776874d6010753fc9349df2225c69af" + "reference": "7d355c5e3ccc8ecebc0266f8ddd30088a43be919" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/storage/zipball/5013b894a776874d6010753fc9349df2225c69af", - "reference": "5013b894a776874d6010753fc9349df2225c69af", + "url": "https://api.github.com/repos/utopia-php/storage/zipball/7d355c5e3ccc8ecebc0266f8ddd30088a43be919", + "reference": "7d355c5e3ccc8ecebc0266f8ddd30088a43be919", "shasum": "" }, "require": { @@ -2741,8 +2629,8 @@ "ext-zlib": "*", "ext-zstd": "*", "php": ">=8.0", - "utopia-php/framework": "1.0.*", - "utopia-php/system": "0.8.*" + "utopia-php/framework": "0.*.*", + "utopia-php/system": "0.*.*" }, "require-dev": { "laravel/pint": "1.2.*", @@ -2769,9 +2657,60 @@ ], "support": { "issues": "https://github.com/utopia-php/storage/issues", - "source": "https://github.com/utopia-php/storage/tree/0.19.0" + "source": "https://github.com/utopia-php/storage/tree/0.18.5" }, - "time": "2024-09-05T17:00:24+00:00" + "time": "2024-09-04T08:57:27+00:00" + }, + { + "name": "utopia-php/swoole", + "version": "0.8.2", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/swoole.git", + "reference": "5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/swoole/zipball/5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4", + "reference": "5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4", + "shasum": "" + }, + "require": { + "ext-swoole": "*", + "php": ">=8.0", + "utopia-php/framework": "0.33.*" + }, + "require-dev": { + "laravel/pint": "1.2.*", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.3", + "swoole/ide-helper": "5.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Swoole\\": "src/Swoole" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "An extension for Utopia Framework to work with PHP Swoole as a PHP FPM alternative", + "keywords": [ + "framework", + "http", + "php", + "server", + "swoole", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/swoole/issues", + "source": "https://github.com/utopia-php/swoole/tree/0.8.2" + }, + "time": "2024-02-01T14:54:12+00:00" }, { "name": "utopia-php/system", @@ -2831,24 +2770,23 @@ }, { "name": "utopia-php/vcs", - "version": "0.9.0", + "version": "0.8.2", "source": { "type": "git", "url": "https://github.com/utopia-php/vcs.git", - "reference": "673abe2fef0750a841a4fa8fa6f99d4a602c68e7" + "reference": "eb9b7eade1a46a4f660e0d5a6304f7fa26ec9d18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/vcs/zipball/673abe2fef0750a841a4fa8fa6f99d4a602c68e7", - "reference": "673abe2fef0750a841a4fa8fa6f99d4a602c68e7", + "url": "https://api.github.com/repos/utopia-php/vcs/zipball/eb9b7eade1a46a4f660e0d5a6304f7fa26ec9d18", + "reference": "eb9b7eade1a46a4f660e0d5a6304f7fa26ec9d18", "shasum": "" }, "require": { "adhocore/jwt": "^1.1", "php": ">=8.0", - "utopia-php/cache": "0.10.*", - "utopia-php/framework": "1.0.*", - "utopia-php/system": "0.8.*" + "utopia-php/cache": "^0.10.0", + "utopia-php/framework": "0.*.*" }, "require-dev": { "laravel/pint": "1.2.*", @@ -2875,76 +2813,32 @@ ], "support": { "issues": "https://github.com/utopia-php/vcs/issues", - "source": "https://github.com/utopia-php/vcs/tree/0.9.0" + "source": "https://github.com/utopia-php/vcs/tree/0.8.2" }, - "time": "2024-09-05T16:44:48+00:00" - }, - { - "name": "utopia-php/view", - "version": "0.2.0", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/view.git", - "reference": "6ee55e83bc014c39ed6b69390f6d399116f65e88" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/view/zipball/6ee55e83bc014c39ed6b69390f6d399116f65e88", - "reference": "6ee55e83bc014c39ed6b69390f6d399116f65e88", - "shasum": "" - }, - "require": { - "php": ">=8.0" - }, - "require-dev": { - "laravel/pint": "^1.2", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.5.25" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\View\\": "src/View" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A simple, light and advanced PHP rendering engine", - "keywords": [ - "php", - "view" - ], - "support": { - "issues": "https://github.com/utopia-php/view/issues", - "source": "https://github.com/utopia-php/view/tree/0.2.0" - }, - "time": "2024-04-01T17:21:29+00:00" + "time": "2024-08-13T14:36:30+00:00" }, { "name": "utopia-php/websocket", - "version": "0.2.0", + "version": "0.1.0", "source": { "type": "git", "url": "https://github.com/utopia-php/websocket.git", - "reference": "e9d0919b321744a61f12563f5791c47ba9f57810" + "reference": "51fcb86171400d8aa40d76c54593481fd273dab5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/websocket/zipball/e9d0919b321744a61f12563f5791c47ba9f57810", - "reference": "e9d0919b321744a61f12563f5791c47ba9f57810", + "url": "https://api.github.com/repos/utopia-php/websocket/zipball/51fcb86171400d8aa40d76c54593481fd273dab5", + "reference": "51fcb86171400d8aa40d76c54593481fd273dab5", "shasum": "" }, "require": { "php": ">=8.0" }, "require-dev": { - "laravel/pint": "^1.15", - "phpstan/phpstan": "^1.8", "phpunit/phpunit": "^9.5.5", - "swoole/ide-helper": "5.1.2", + "swoole/ide-helper": "4.6.6", "textalk/websocket": "1.5.2", + "vimeo/psalm": "^4.8.1", "workerman/workerman": "^4.0" }, "type": "library", @@ -2957,6 +2851,16 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Eldad Fux", + "email": "eldad@appwrite.io" + }, + { + "name": "Torsten Dittmann", + "email": "torsten@appwrite.io" + } + ], "description": "A simple abstraction for WebSocket servers.", "keywords": [ "framework", @@ -2967,9 +2871,9 @@ ], "support": { "issues": "https://github.com/utopia-php/websocket/issues", - "source": "https://github.com/utopia-php/websocket/tree/0.2.0" + "source": "https://github.com/utopia-php/websocket/tree/0.1.0" }, - "time": "2024-04-09T08:28:11+00:00" + "time": "2021-12-20T10:50:09+00:00" }, { "name": "webmozart/assert", @@ -4335,65 +4239,6 @@ }, "time": "2024-09-26T07:23:32+00:00" }, - { - "name": "phpstan/phpstan", - "version": "1.8.11", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "46e223dd68a620da18855c23046ddb00940b4014" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/46e223dd68a620da18855c23046ddb00940b4014", - "reference": "46e223dd68a620da18855c23046ddb00940b4014", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "keywords": [ - "dev", - "static analysis" - ], - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.8.11" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-10-24T15:45:13+00:00" - }, { "name": "phpunit/php-code-coverage", "version": "9.2.32", diff --git a/docker-compose.yml b/docker-compose.yml index c4bdffa1d0..6ecb0ecff8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -682,7 +682,6 @@ services: entrypoint: maintenance <<: *x-logging container_name: appwrite-task-maintenance - restart: unless-stopped image: appwrite-dev networks: - appwrite @@ -783,7 +782,6 @@ services: entrypoint: schedule-functions <<: *x-logging container_name: appwrite-task-scheduler-functions - restart: unless-stopped image: appwrite-dev networks: - appwrite @@ -812,7 +810,6 @@ services: entrypoint: schedule-executions <<: *x-logging container_name: appwrite-task-scheduler-executions - restart: unless-stopped image: appwrite-dev networks: - appwrite @@ -840,7 +837,6 @@ services: entrypoint: schedule-messages <<: *x-logging container_name: appwrite-task-scheduler-messages - restart: unless-stopped image: appwrite-dev networks: - appwrite @@ -960,7 +956,7 @@ services: - MYSQL_USER=${_APP_DB_USER} - MYSQL_PASSWORD=${_APP_DB_PASS} - MARIADB_AUTO_UPGRADE=1 - command: 'mysqld --innodb-flush-method=fsync --max_connections=5000' + command: "mysqld --innodb-flush-method=fsync" redis: image: redis:7.2.4-alpine diff --git a/docs/tutorials/add-route.md b/docs/tutorials/add-route.md index 0baa51b5c0..ac6fd40bdb 100644 --- a/docs/tutorials/add-route.md +++ b/docs/tutorials/add-route.md @@ -8,7 +8,7 @@ Setting an alias allows the route to be also accessible from the alias URL. The first parameter specifies the alias URL, the second parameter specifies default values for route parameters. ```php -Http::post('/v1/storage/buckets/:bucketId/files') +App::post('/v1/storage/buckets/:bucketId/files') ->alias('/v1/storage/files', ['bucketId' => 'default']) ``` @@ -17,7 +17,7 @@ Http::post('/v1/storage/buckets/:bucketId/files') Used as an abstract description of the route. ```php -Http::post('/v1/storage/buckets/:bucketId/files') +App::post('/v1/storage/buckets/:bucketId/files') ->desc('Create File') ``` @@ -26,14 +26,14 @@ Http::post('/v1/storage/buckets/:bucketId/files') Groups array is used to group one or more routes with one or more hooks functionality. ```php -Http::post('/v1/storage/buckets/:bucketId/files') +App::post('/v1/storage/buckets/:bucketId/files') ->groups(['api']) ``` In the above example groups() is used to define the current route as part of the routes that shares a common init middleware hook. ```php -Http::init() +App::init() ->groups(['api']) ->action( some code..... @@ -52,7 +52,7 @@ Appwrite uses different labels to achieve different things, for example: - scope - Defines the route permissions scope. ```php -Http::post('/v1/storage/buckets/:bucketId/files') +App::post('/v1/storage/buckets/:bucketId/files') ->label('scope', 'files.write') ``` @@ -66,7 +66,7 @@ Http::post('/v1/storage/buckets/:bucketId/files') - audits.resource - Signals the extraction part of the resource. ```php -Http::post('/v1/account/create') +App::post('/v1/account/create') ->label('audits.event', 'account.create') ->label('audits.resource', 'user/{response.$id}') ->label('audits.userId', '{response.$id}') @@ -84,7 +84,7 @@ Http::post('/v1/account/create') * sdk.offline.response.key - JSON property name that has the ID. Defaults to $id ```php -Http::post('/v1/account/jwt') +App::post('/v1/account/jwt') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION]) ->label('sdk.namespace', 'account') ->label('sdk.method', 'createJWT') @@ -100,7 +100,7 @@ Http::post('/v1/account/jwt') - cache.resource - Identifies the cached resource. ```php -Http::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') +App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') ->label('cache', true) ->label('cache.resource', 'file/{request.fileId}') ``` @@ -115,7 +115,7 @@ When using the example below, we configure the abuse mechanism to allow this key constructed from the combination of the ip, http method, url, userId to hit the route maximum 60 times in 1 hour (60 seconds \* 60 minutes). ```php -Http::post('/v1/storage/buckets/:bucketId/files') +App::post('/v1/storage/buckets/:bucketId/files') ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') ->label('abuse-limit', 60) ->label('abuse-time', 3600) @@ -127,7 +127,7 @@ Http::post('/v1/storage/buckets/:bucketId/files') Placeholders marked as `[]` are parsed and replaced with their real values. ```php -Http::post('/v1/storage/buckets/:bucketId/files') +App::post('/v1/storage/buckets/:bucketId/files') ->label('event', 'buckets.[bucketId].files.[fileId].create') ``` @@ -145,7 +145,7 @@ As the name implies, `param()` is used to define a request parameter. - An array of injections ```php -Http::get('/v1/account/logs') +App::get('/v1/account/logs') ->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) ``` @@ -154,14 +154,14 @@ Http::get('/v1/account/logs') inject is used to inject dependencies pre-bounded to the app. ```php -Http::post('/v1/storage/buckets/:bucketId/files') +App::post('/v1/storage/buckets/:bucketId/files') ->inject('user') ``` -In the example above, the user object is injected into the route pre-bounded using `Http::setResource()`. +In the example above, the user object is injected into the route pre-bounded using `App::setResource()`. ```php -Http::setResource('user', function() { +App::setResource('user', function() { some code... }); ``` @@ -170,7 +170,7 @@ some code... Action populates the actual route code and has to be very clear and understandable. A good route stays simple and doesn't contain complex logic. An action is where we describe our business needs in code, and combine different libraries to work together and tell our story. ```php -Http::post('/v1/account/sessions/anonymous') +App::post('/v1/account/sessions/anonymous') ->action(function (Request $request) { some code... }); diff --git a/phpstan.neon b/phpstan.neon deleted file mode 100644 index 25771ef17c..0000000000 --- a/phpstan.neon +++ /dev/null @@ -1,11 +0,0 @@ -parameters: - level: 0 - scanDirectories: - - vendor/swoole/ide-helper - excludePaths: - - tests/resources - ignoreErrors: - - '#Parameter \$geodb of anonymous function has invalid type MaxMind\\Db\\Reader\.#' - - '#Parameter \$geodb of function router\(\) has invalid type MaxMind\\Db\\Reader\.#' - - '#Instantiated class MaxMind\\Db\\Reader not found.#' - - '#Function scrypt not found\.#' diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index 0135020765..1e8109622e 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -91,6 +91,37 @@ class Auth */ public const MFA_RECENT_DURATION = 1800; // 30 mins + /** + * @var string + */ + public static $cookieName = 'a_session'; + + /** + * User Unique ID. + * + * @var string + */ + public static $unique = ''; + + /** + * User Secret Key. + * + * @var string + */ + public static $secret = ''; + + /** + * Set Cookie Name. + * + * @param $string + * + * @return string + */ + public static function setCookieName($string) + { + return self::$cookieName = $string; + } + /** * Encode Session. * @@ -408,14 +439,13 @@ class Auth * Returns all roles for a user. * * @param Document $user - * @param Authorization $auth * @return array */ - public static function getRoles(Document $user, Authorization $auth): array + public static function getRoles(Document $user): array { $roles = []; - if (!self::isPrivilegedUser($auth->getRoles()) && !self::isAppUser($auth->getRoles())) { + if (!self::isPrivilegedUser(Authorization::getRoles()) && !self::isAppUser(Authorization::getRoles())) { if ($user->getId()) { $roles[] = Role::user($user->getId())->toString(); $roles[] = Role::users()->toString(); diff --git a/src/Appwrite/Auth/Authentication.php b/src/Appwrite/Auth/Authentication.php deleted file mode 100644 index ef372309da..0000000000 --- a/src/Appwrite/Auth/Authentication.php +++ /dev/null @@ -1,57 +0,0 @@ -cookieName = $string; - } - - public function getCookieName(): string - { - return $this->cookieName; - } - - public function getUnique(): string - { - return $this->unique; - } - - public function setUnique(string $unique): void - { - $this->unique = $unique; - } - - public function getSecret(): string - { - return $this->secret; - } - - public function setSecret(string $secret): void - { - $this->secret = $secret; - } - -} diff --git a/src/Appwrite/Auth/Validator/MockNumber.php b/src/Appwrite/Auth/Validator/MockNumber.php index 8f0f14c9da..ac5ba89fc5 100644 --- a/src/Appwrite/Auth/Validator/MockNumber.php +++ b/src/Appwrite/Auth/Validator/MockNumber.php @@ -2,8 +2,8 @@ namespace Appwrite\Auth\Validator; -use Utopia\Http\Validator; -use Utopia\Http\Validator\Text; +use Utopia\Validator; +use Utopia\Validator\Text; /** * MockNumber. @@ -52,7 +52,6 @@ class MockNumber extends Validator return false; } - $this->message = ''; return true; } diff --git a/src/Appwrite/Auth/Validator/Password.php b/src/Appwrite/Auth/Validator/Password.php index 913701f7a3..bfe5577889 100644 --- a/src/Appwrite/Auth/Validator/Password.php +++ b/src/Appwrite/Auth/Validator/Password.php @@ -2,7 +2,7 @@ namespace Appwrite\Auth\Validator; -use Utopia\Http\Validator; +use Utopia\Validator; /** * Password. diff --git a/src/Appwrite/Auth/Validator/Phone.php b/src/Appwrite/Auth/Validator/Phone.php index d5f6df60c8..26aa687278 100644 --- a/src/Appwrite/Auth/Validator/Phone.php +++ b/src/Appwrite/Auth/Validator/Phone.php @@ -2,7 +2,7 @@ namespace Appwrite\Auth\Validator; -use Utopia\Http\Validator; +use Utopia\Validator; /** * Phone. diff --git a/src/Appwrite/Event/Func.php b/src/Appwrite/Event/Func.php index 2fbdb7e785..0cbaf17b60 100644 --- a/src/Appwrite/Event/Func.php +++ b/src/Appwrite/Event/Func.php @@ -175,9 +175,9 @@ class Func extends Event * * @return string */ - public function getBody(): string + public function getData(): string { - return $this->body; + return $this->data; } /** diff --git a/src/Appwrite/Event/Validator/Event.php b/src/Appwrite/Event/Validator/Event.php index 2d46bd7727..a3605e4df5 100644 --- a/src/Appwrite/Event/Validator/Event.php +++ b/src/Appwrite/Event/Validator/Event.php @@ -3,7 +3,7 @@ namespace Appwrite\Event\Validator; use Utopia\Config\Config; -use Utopia\Http\Validator; +use Utopia\Validator; class Event extends Validator { diff --git a/src/Appwrite/Functions/Validator/Headers.php b/src/Appwrite/Functions/Validator/Headers.php index 4c30a98045..04003d535b 100644 --- a/src/Appwrite/Functions/Validator/Headers.php +++ b/src/Appwrite/Functions/Validator/Headers.php @@ -2,7 +2,7 @@ namespace Appwrite\Functions\Validator; -use Utopia\Http\Validator; +use Utopia\Validator; /** * Headers. diff --git a/src/Appwrite/Functions/Validator/Payload.php b/src/Appwrite/Functions/Validator/Payload.php index 3b2ff4b918..acb461eabd 100644 --- a/src/Appwrite/Functions/Validator/Payload.php +++ b/src/Appwrite/Functions/Validator/Payload.php @@ -2,7 +2,7 @@ namespace Appwrite\Functions\Validator; -use Utopia\Http\Validator\Text; +use Utopia\Validator\Text; class Payload extends Text { diff --git a/src/Appwrite/Functions/Validator/RuntimeSpecification.php b/src/Appwrite/Functions/Validator/RuntimeSpecification.php index fa6efe90a4..22311838f6 100644 --- a/src/Appwrite/Functions/Validator/RuntimeSpecification.php +++ b/src/Appwrite/Functions/Validator/RuntimeSpecification.php @@ -2,7 +2,7 @@ namespace Appwrite\Functions\Validator; -use Utopia\Http\Validator; +use Utopia\Validator; class RuntimeSpecification extends Validator { diff --git a/src/Appwrite/GraphQL/Resolvers.php b/src/Appwrite/GraphQL/Resolvers.php index 49d7c421f7..8bc72af2f8 100644 --- a/src/Appwrite/GraphQL/Resolvers.php +++ b/src/Appwrite/GraphQL/Resolvers.php @@ -6,12 +6,9 @@ use Appwrite\GraphQL\Exception as GQLException; use Appwrite\Promises\Swoole; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; -use Utopia\DI\Container; +use Utopia\App; use Utopia\Exception; -use Utopia\Http\Http; -use Utopia\Http\Request as UtopiaHttpRequest; -use Utopia\Http\Response as UtopiaHttpResponse; -use Utopia\Http\Route; +use Utopia\Route; use Utopia\System\System; class Resolvers @@ -19,21 +16,24 @@ class Resolvers /** * Create a resolver for a given API {@see Route}. * - * @param Http $http + * @param App $utopia * @param ?Route $route * @return callable */ - public function api( - Http $http, + public static function api( + App $utopia, ?Route $route, - UtopiaHttpRequest $request, - UtopiaHttpResponse $response, - Container $container, ): callable { - $resolver = $this; + return static fn ($type, $args, $context, $info) => new Swoole( + function (callable $resolve, callable $reject) use ($utopia, $route, $args, $context, $info) { + /** @var App $utopia */ + /** @var Response $response */ + /** @var Request $request */ + + $utopia = $utopia->getResource('utopia:graphql', true); + $request = $utopia->getResource('request', true); + $response = $utopia->getResource('response', true); - return fn ($type, $args, $context, $info) => new Swoole( - function (callable $resolve, callable $reject) use ($http, $route, $args, $context, $container, $info, $request, $response, $resolver) { $path = $route->getPath(); foreach ($args as $key => $value) { if (\str_contains($path, '/:' . $key)) { @@ -46,13 +46,14 @@ class Resolvers switch ($route->getMethod()) { case 'GET': - $request->setQuery($args); + $request->setQueryString($args); break; default: $request->setPayload($args); break; } - $resolver->resolve($http, $request, $response, $container, $resolve, $reject); + + self::resolve($utopia, $request, $response, $resolve, $reject); } ); } @@ -60,20 +61,20 @@ class Resolvers /** * Create a resolver for a document in a specified database and collection with a specific method type. * - * @param Http $http + * @param App $utopia * @param string $databaseId * @param string $collectionId * @param string $methodType * @return callable */ - public function document( - Http $http, + public static function document( + App $utopia, string $databaseId, string $collectionId, string $methodType, ): callable { return [self::class, 'document' . \ucfirst($methodType)]( - $http, + $utopia, $databaseId, $collectionId ); @@ -82,28 +83,28 @@ class Resolvers /** * Create a resolver for getting a document in a specified database and collection. * - * @param Http $http + * @param App $utopia * @param string $databaseId * @param string $collectionId * @param callable $url * @return callable */ - public function documentGet( - Http $http, + public static function documentGet( + App $utopia, string $databaseId, string $collectionId, callable $url, - UtopiaHttpRequest $request, - UtopiaHttpResponse $response, - Container $container, ): callable { - $resolver = $this; - return fn ($type, $args, $context, $info) => new Swoole( - function (callable $resolve, callable $reject) use ($http, $databaseId, $collectionId, $url, $type, $args, $container, $request, $response, $resolver) { + return static fn ($type, $args, $context, $info) => new Swoole( + function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $type, $args) { + $utopia = $utopia->getResource('utopia:graphql', true); + $request = $utopia->getResource('request', true); + $response = $utopia->getResource('response', true); + $request->setMethod('GET'); $request->setURI($url($databaseId, $collectionId, $args)); - $resolver->resolve($http, $request, $response, $container, $resolve, $reject); + self::resolve($utopia, $request, $response, $resolve, $reject); } ); } @@ -111,35 +112,35 @@ class Resolvers /** * Create a resolver for listing documents in a specified database and collection. * - * @param Http $http + * @param App $utopia * @param string $databaseId * @param string $collectionId * @param callable $url * @param callable $params * @return callable */ - public function documentList( - Http $http, + public static function documentList( + App $utopia, string $databaseId, string $collectionId, callable $url, callable $params, - UtopiaHttpRequest $request, - UtopiaHttpResponse $response, - Container $container, ): callable { - $resolver = $this; - return fn ($type, $args, $context, $info) => new Swoole( - function (callable $resolve, callable $reject) use ($http, $databaseId, $collectionId, $url, $params, $type, $args, $container, $request, $response, $resolver) { + return static fn ($type, $args, $context, $info) => new Swoole( + function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $type, $args) { + $utopia = $utopia->getResource('utopia:graphql', true); + $request = $utopia->getResource('request', true); + $response = $utopia->getResource('response', true); + $request->setMethod('GET'); $request->setURI($url($databaseId, $collectionId, $args)); - $request->setQuery($params($databaseId, $collectionId, $args)); + $request->setQueryString($params($databaseId, $collectionId, $args)); $beforeResolve = function ($payload) { return $payload['documents']; }; - $resolver->resolve($http, $request, $response, $container, $resolve, $reject, $beforeResolve); + self::resolve($utopia, $request, $response, $resolve, $reject, $beforeResolve); } ); } @@ -147,31 +148,31 @@ class Resolvers /** * Create a resolver for creating a document in a specified database and collection. * - * @param Http $http + * @param App $utopia * @param string $databaseId * @param string $collectionId * @param callable $url * @param callable $params * @return callable */ - public function documentCreate( - Http $http, + public static function documentCreate( + App $utopia, string $databaseId, string $collectionId, callable $url, callable $params, - UtopiaHttpRequest $request, - UtopiaHttpResponse $response, - Container $container, ): callable { - $resolver = $this; - return fn ($type, $args, $context, $info) => new Swoole( - function (callable $resolve, callable $reject) use ($http, $databaseId, $collectionId, $url, $params, $type, $args, $container, $request, $response, $resolver) { + return static fn ($type, $args, $context, $info) => new Swoole( + function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $type, $args) { + $utopia = $utopia->getResource('utopia:graphql', true); + $request = $utopia->getResource('request', true); + $response = $utopia->getResource('response', true); + $request->setMethod('POST'); $request->setURI($url($databaseId, $collectionId, $args)); $request->setPayload($params($databaseId, $collectionId, $args)); - $resolver->resolve($http, $request, $response, $container, $resolve, $reject); + self::resolve($utopia, $request, $response, $resolve, $reject); } ); } @@ -179,31 +180,31 @@ class Resolvers /** * Create a resolver for updating a document in a specified database and collection. * - * @param Http $http + * @param App $utopia * @param string $databaseId * @param string $collectionId * @param callable $url * @param callable $params * @return callable */ - public function documentUpdate( - Http $http, + public static function documentUpdate( + App $utopia, string $databaseId, string $collectionId, callable $url, callable $params, - UtopiaHttpRequest $request, - UtopiaHttpResponse $response, - Container $container, ): callable { - $resolver = $this; - return fn ($type, $args, $context, $info) => new Swoole( - function (callable $resolve, callable $reject) use ($http, $databaseId, $collectionId, $url, $params, $type, $args, $container, $request, $response, $resolver) { + return static fn ($type, $args, $context, $info) => new Swoole( + function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $type, $args) { + $utopia = $utopia->getResource('utopia:graphql', true); + $request = $utopia->getResource('request', true); + $response = $utopia->getResource('response', true); + $request->setMethod('PATCH'); $request->setURI($url($databaseId, $collectionId, $args)); $request->setPayload($params($databaseId, $collectionId, $args)); - $resolver->resolve($http, $request, $response, $container, $resolve, $reject); + self::resolve($utopia, $request, $response, $resolve, $reject); } ); } @@ -211,34 +212,34 @@ class Resolvers /** * Create a resolver for deleting a document in a specified database and collection. * - * @param Http $http + * @param App $utopia * @param string $databaseId * @param string $collectionId * @param callable $url * @return callable */ - public function documentDelete( - Http $http, + public static function documentDelete( + App $utopia, string $databaseId, string $collectionId, callable $url, - UtopiaHttpRequest $request, - UtopiaHttpResponse $response, - Container $container, ): callable { - $resolver = $this; - return fn ($type, $args, $context, $info) => new Swoole( - function (callable $resolve, callable $reject) use ($http, $databaseId, $collectionId, $url, $type, $args, $container, $request, $response, $resolver) { + return static fn ($type, $args, $context, $info) => new Swoole( + function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $type, $args) { + $utopia = $utopia->getResource('utopia:graphql', true); + $request = $utopia->getResource('request', true); + $response = $utopia->getResource('response', true); + $request->setMethod('DELETE'); $request->setURI($url($databaseId, $collectionId, $args)); - $resolver->resolve($http, $request, $response, $container, $resolve, $reject); + self::resolve($utopia, $request, $response, $resolve, $reject); } ); } /** - * @param Http $http + * @param App $utopia * @param Request $request * @param Response $response * @param callable $resolve @@ -248,11 +249,10 @@ class Resolvers * @return void * @throws Exception */ - private function resolve( - Http $http, + private static function resolve( + App $utopia, Request $request, Response $response, - Container $context, callable $resolve, callable $reject, ?callable $beforeResolve = null, @@ -263,16 +263,14 @@ class Resolvers $request->removeHeader('content-type'); } + $request = clone $request; + $utopia->setResource('request', static fn () => $request); $response->setContentType(Response::CONTENT_TYPE_NULL); try { - $context - ->refresh('cache') - ->refresh('dbForProject') - ->refresh('dbForConsole') - ->refresh('getProjectDb'); + $route = $utopia->match($request, fresh: true); - $http->run(clone $context); + $utopia->execute($route, $request, $response); } catch (\Throwable $e) { if ($beforeReject) { $e = $beforeReject($e); @@ -287,12 +285,10 @@ class Resolvers if ($beforeReject) { $payload = $beforeReject($payload); } - $reject( - new GQLException( - message: $payload['message'], - code: $response->getStatusCode() - ) - ); + $reject(new GQLException( + message: $payload['message'], + code: $response->getStatusCode() + )); return; } diff --git a/src/Appwrite/GraphQL/Schema.php b/src/Appwrite/GraphQL/Schema.php index 2b05f08aee..833ea9d032 100644 --- a/src/Appwrite/GraphQL/Schema.php +++ b/src/Appwrite/GraphQL/Schema.php @@ -3,63 +3,54 @@ namespace Appwrite\GraphQL; use Appwrite\GraphQL\Types\Mapper; -use Appwrite\Utopia\Response\Models; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Schema as GQLSchema; -use Utopia\DI\Container; +use Utopia\App; use Utopia\Exception; -use Utopia\Http\Adapter\Swoole\Response as UtopiaSwooleResponse; -use Utopia\Http\Http; -use Utopia\Http\Request; -use Utopia\Http\Response as UtopiaHttpResponse; -use Utopia\Http\Route; +use Utopia\Route; class Schema { - protected ?GQLSchema $schema = null; - protected array $dirty = []; + protected static ?GQLSchema $schema = null; + protected static array $dirty = []; /** * - * @param Http $http - * @param callable $complexity Function to calculate complexity - * @param callable $attributes Function to get attributes - * @param array $urls Array of functions to get urls for specific method types - * @param array $params Array of functions to build parameters for specific method types + * @param App $utopia + * @param callable $complexity Function to calculate complexity + * @param callable $attributes Function to get attributes + * @param array $urls Array of functions to get urls for specific method types + * @param array $params Array of functions to build parameters for specific method types * @return GQLSchema * @throws Exception */ - public function build( - Http $http, - Request $request, - UtopiaHttpResponse $response, - Container $container, + public static function build( + App $utopia, callable $complexity, callable $attributes, array $urls, array $params, ): GQLSchema { - if (!empty($this->schema)) { - return $this->schema; + App::setResource('utopia:graphql', static function () use ($utopia) { + return $utopia; + }); + + if (!empty(self::$schema)) { + return self::$schema; } - $api = $this->api( - $http, - $request, - $response, - $container, + $api = static::api( + $utopia, $complexity ); - // $collections = $this->collections( - // $http, - // $complexity, - // $request, - // $response, - // $attributes, - // $urls, - // $params, - // ); + //$collections = static::collections( + // $utopia, + // $complexity, + // $attributes, + // $urls, + // $params, + //); $queries = \array_merge_recursive( $api['query'], @@ -73,7 +64,7 @@ class Schema \ksort($queries); \ksort($mutations); - return $this->schema = new GQLSchema([ + return static::$schema = new GQLSchema([ 'query' => new ObjectType([ 'name' => 'Query', 'fields' => $queries @@ -89,23 +80,21 @@ class Schema * This function iterates all API routes and builds a GraphQL * schema defining types and resolvers for all response models. * - * @param Http $http - * @param Request $request - * @param UtopiaSwooleResponse $response + * @param App $utopia * @param callable $complexity * @return array - * @throws \Exception + * @throws Exception */ - protected function api(Http $http, Request $request, UtopiaHttpResponse $response, Container $container, callable $complexity): array + protected static function api(App $utopia, callable $complexity): array { - Mapper::init(Models::getModels()); - - $mapper = new Mapper(); + Mapper::init($utopia + ->getResource('response') + ->getModels()); $queries = []; $mutations = []; - foreach ($http->getRoutes() as $routes) { + foreach ($utopia->getRoutes() as $routes) { foreach ($routes as $route) { /** @var Route $route */ @@ -117,7 +106,7 @@ class Schema continue; } - foreach ($mapper->route($http, $route, $request, $response, $container, $complexity) as $field) { + foreach (Mapper::route($utopia, $route, $complexity) as $field) { switch ($route->getMethod()) { case 'GET': $queries[$name] = $field; @@ -145,7 +134,7 @@ class Schema * Iterates all of a projects attributes and builds GraphQL * queries and mutations for the collections they make up. * - * @param Http $http + * @param App $utopia * @param callable $complexity * @param callable $attributes * @param array $urls @@ -154,7 +143,7 @@ class Schema * @throws \Exception */ protected static function collections( - Http $http, + App $utopia, callable $complexity, callable $attributes, array $urls, @@ -205,36 +194,36 @@ class Schema $queryFields[$collectionId . 'Get'] = [ 'type' => $objectType, 'args' => Mapper::args('id'), - /*'resolve' => Resolvers::documentGet( - $http, + 'resolve' => Resolvers::documentGet( + $utopia, $databaseId, $collectionId, $urls['get'], - )*/ + ) ]; $queryFields[$collectionId . 'List'] = [ 'type' => Type::listOf($objectType), 'args' => Mapper::args('list'), - /*'resolve' => Resolvers::documentList( - $http, + 'resolve' => Resolvers::documentList( + $utopia, $databaseId, $collectionId, $urls['list'], $params['list'], - ),*/ + ), 'complexity' => $complexity, ]; $mutationFields[$collectionId . 'Create'] = [ 'type' => $objectType, 'args' => $attributes, - /*'resolve' => Resolvers::documentCreate( - $http, + 'resolve' => Resolvers::documentCreate( + $utopia, $databaseId, $collectionId, $urls['create'], $params['create'], - )*/ + ) ]; $mutationFields[$collectionId . 'Update'] = [ 'type' => $objectType, @@ -245,23 +234,23 @@ class Schema $attributes ) ), - /*'resolve' => Resolvers::documentUpdate( - $http, + 'resolve' => Resolvers::documentUpdate( + $utopia, $databaseId, $collectionId, $urls['update'], $params['update'], - )*/ + ) ]; $mutationFields[$collectionId . 'Delete'] = [ 'type' => Mapper::model('none'), 'args' => Mapper::args('id'), - /*'resolve' => Resolvers::documentDelete( - $http, + 'resolve' => Resolvers::documentDelete( + $utopia, $databaseId, $collectionId, $urls['delete'], - )*/ + ) ]; } $offset += $limit; @@ -273,8 +262,8 @@ class Schema ]; } - public function setDirty(string $projectId): void + public static function setDirty(string $projectId): void { - $this->dirty[$projectId] = true; + self::$dirty[$projectId] = true; } } diff --git a/src/Appwrite/GraphQL/Types/Mapper.php b/src/Appwrite/GraphQL/Types/Mapper.php index d4478e3301..d8f1d7da09 100644 --- a/src/Appwrite/GraphQL/Types/Mapper.php +++ b/src/Appwrite/GraphQL/Types/Mapper.php @@ -8,13 +8,10 @@ use Exception; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; -use Utopia\DI\Container; -use Utopia\Http\Adapter\Swoole\Response as UtopiaSwooleResponse; -use Utopia\Http\Http; -use Utopia\Http\Request; -use Utopia\Http\Route; -use Utopia\Http\Validator; -use Utopia\Http\Validator\Nullable; +use Utopia\App; +use Utopia\Route; +use Utopia\Validator; +use Utopia\Validator\Nullable; class Mapper { @@ -78,15 +75,12 @@ class Mapper return self::$args[$key] ?? []; } - public function route( - Http $http, + public static function route( + App $utopia, Route $route, - Request $request, - UtopiaSwooleResponse $response, - Container $container, callable $complexity ): iterable { - foreach (static::$blacklist as $blacklist) { + foreach (self::$blacklist as $blacklist) { if (\str_starts_with($route->getPath(), $blacklist)) { return; } @@ -108,7 +102,7 @@ class Mapper $list = true; } $parameterType = Mapper::param( - $container, + $utopia, $parameter['validator'], !$parameter['optional'], $parameter['injections'] @@ -123,7 +117,7 @@ class Mapper 'type' => $type, 'description' => $description, 'args' => $params, - 'resolve' => (new Resolvers())->api($http, $route, $request, $response, $container) + 'resolve' => Resolvers::api($utopia, $route) ]; if ($list) { @@ -212,7 +206,7 @@ class Mapper /** * Map a {@see Route} parameter to a GraphQL Type * - * @param Container $container + * @param App $utopia * @param Validator|callable $validator * @param bool $required * @param array $injections @@ -220,13 +214,13 @@ class Mapper * @throws Exception */ public static function param( - Container $container, + App $utopia, Validator|callable $validator, bool $required, array $injections ): Type { $validator = \is_callable($validator) - ? \call_user_func_array($validator, array_map(fn ($injection) => $container->get($injection), $injections)) + ? \call_user_func_array($validator, $utopia->getResources($injections)) : $validator; $isNullable = $validator instanceof Nullable; @@ -239,20 +233,20 @@ class Mapper case 'Appwrite\Network\Validator\CNAME': case 'Appwrite\Task\Validator\Cron': case 'Appwrite\Utopia\Database\Validator\CustomId': - case 'Utopia\Http\Validator\Domain': + case 'Utopia\Validator\Domain': case 'Appwrite\Network\Validator\Email': case 'Appwrite\Event\Validator\Event': case 'Appwrite\Event\Validator\FunctionEvent': - case 'Utopia\Http\Validator\HexColor': - case 'Utopia\Http\Validator\Host': - case 'Utopia\Http\Validator\IP': + case 'Utopia\Validator\HexColor': + case 'Utopia\Validator\Host': + case 'Utopia\Validator\IP': case 'Utopia\Database\Validator\Key': - case 'Utopia\Http\Validator\Origin': + case 'Utopia\Validator\Origin': case 'Appwrite\Auth\Validator\Password': - case 'Utopia\Http\Validator\Text': + case 'Utopia\Validator\Text': case 'Utopia\Database\Validator\UID': - case 'Utopia\Http\Validator\URL': - case 'Utopia\Http\Validator\WhiteList': + case 'Utopia\Validator\URL': + case 'Utopia\Validator\WhiteList': default: $type = Type::string(); break; @@ -280,29 +274,29 @@ class Mapper case 'Appwrite\Utopia\Database\Validator\Queries\Variables': $type = Type::listOf(Type::string()); break; - case 'Utopia\Http\Validator\Boolean': + case 'Utopia\Validator\Boolean': $type = Type::boolean(); break; - case 'Utopia\Http\Validator\ArrayList': + case 'Utopia\Validator\ArrayList': $type = Type::listOf(self::param( - $container, + $utopia, $validator->getValidator(), $required, $injections )); break; - case 'Utopia\Http\Validator\Integer': - case 'Utopia\Http\Validator\Numeric': - case 'Utopia\Http\Validator\Range': + case 'Utopia\Validator\Integer': + case 'Utopia\Validator\Numeric': + case 'Utopia\Validator\Range': $type = Type::int(); break; - case 'Utopia\Http\Validator\FloatValidator': + case 'Utopia\Validator\FloatValidator': $type = Type::float(); break; - case 'Utopia\Http\Validator\Assoc': + case 'Utopia\Validator\Assoc': $type = Types::assoc(); break; - case 'Utopia\Http\Validator\JSON': + case 'Utopia\Validator\JSON': $type = Types::json(); break; case 'Utopia\Storage\Validator\File': diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php index 5b215ec04f..cee1b2d263 100644 --- a/src/Appwrite/Migration/Migration.php +++ b/src/Appwrite/Migration/Migration.php @@ -98,10 +98,10 @@ abstract class Migration */ protected array $collections; - public function __construct(Authorization $auth) + public function __construct() { - $auth->disable(); - $auth->setDefaultStatus(false); + Authorization::disable(); + Authorization::setDefaultStatus(false); $this->collections = Config::getParam('collections', []); @@ -176,23 +176,25 @@ abstract class Migration Console::log('Migrating Collection ' . $collection['$id'] . ':'); foreach ($this->documentsIterator($collection['$id']) as $document) { - if (empty($document->getId()) || empty($document->getCollection())) { - return; - } + go(function (Document $document, callable $callback) { + if (empty($document->getId()) || empty($document->getCollection())) { + return; + } - $old = $document->getArrayCopy(); - $new = call_user_func($callback, $document); + $old = $document->getArrayCopy(); + $new = call_user_func($callback, $document); - if (is_null($new) || $new->getArrayCopy() == $old) { - return; - } + if (is_null($new) || $new->getArrayCopy() == $old) { + return; + } - try { - $this->projectDB->updateDocument($document->getCollection(), $document->getId(), $document); - } catch (\Throwable $th) { - Console::error('Failed to update document: ' . $th->getMessage()); - return; - } + try { + $this->projectDB->updateDocument($document->getCollection(), $document->getId(), $document); + } catch (\Throwable $th) { + Console::error('Failed to update document: ' . $th->getMessage()); + return; + } + }, $document, $callback); } } } diff --git a/src/Appwrite/Network/Validator/CNAME.php b/src/Appwrite/Network/Validator/CNAME.php index e9e2b586a5..e1ae061c84 100644 --- a/src/Appwrite/Network/Validator/CNAME.php +++ b/src/Appwrite/Network/Validator/CNAME.php @@ -2,7 +2,7 @@ namespace Appwrite\Network\Validator; -use Utopia\Http\Validator; +use Utopia\Validator; class CNAME extends Validator { diff --git a/src/Appwrite/Network/Validator/Email.php b/src/Appwrite/Network/Validator/Email.php index bae0ff0bbf..3209a4aada 100644 --- a/src/Appwrite/Network/Validator/Email.php +++ b/src/Appwrite/Network/Validator/Email.php @@ -2,14 +2,14 @@ namespace Appwrite\Network\Validator; -use Utopia\Http\Validator; +use Utopia\Validator; /** * Email * * Validate that an variable is a valid email address * - * @package Utopia\Http\Validator + * @package Utopia\Validator */ class Email extends Validator { diff --git a/src/Appwrite/Network/Validator/Origin.php b/src/Appwrite/Network/Validator/Origin.php index 573a59b844..d41e9af2ad 100644 --- a/src/Appwrite/Network/Validator/Origin.php +++ b/src/Appwrite/Network/Validator/Origin.php @@ -2,8 +2,8 @@ namespace Appwrite\Network\Validator; -use Utopia\Http\Validator; -use Utopia\Http\Validator\Hostname; +use Utopia\Validator; +use Utopia\Validator\Hostname; class Origin extends Validator { diff --git a/src/Appwrite/Platform/Tasks/Doctor.php b/src/Appwrite/Platform/Tasks/Doctor.php index 3bf9e0d33b..82d1ca2d59 100644 --- a/src/Appwrite/Platform/Tasks/Doctor.php +++ b/src/Appwrite/Platform/Tasks/Doctor.php @@ -3,17 +3,13 @@ namespace Appwrite\Platform\Tasks; use Appwrite\ClamAV\Network; -use Appwrite\Utopia\Queue\Connections; +use Utopia\App; use Utopia\CLI\Console; use Utopia\Config\Config; -use Utopia\Database\Adapter\MariaDB; -use Utopia\Database\Adapter\MySQL; use Utopia\Domains\Domain; use Utopia\DSN\DSN; -use Utopia\Http\Http; use Utopia\Logger\Logger; use Utopia\Platform\Action; -use Utopia\Queue\Connection\Redis; use Utopia\Registry\Registry; use Utopia\Storage\Device\Local; use Utopia\Storage\Storage; @@ -31,11 +27,10 @@ class Doctor extends Action $this ->desc('Validate server health') ->inject('register') - ->inject('connections') - ->callback(fn (Registry $register, Connections $connections) => $this->action($register, $connections)); + ->callback(fn (Registry $register) => $this->action($register)); } - public function action(Registry $register, Connections $connections): void + public function action(Registry $register): void { Console::log(" __ ____ ____ _ _ ____ __ ____ ____ __ __ / _\ ( _ \( _ \/ )( \( _ \( )(_ _)( __) ( )/ \ @@ -130,73 +125,17 @@ class Doctor extends Action //throw $th; } - /** @var array $pools */ - $pools = $register->get('pools'); + $pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */ $configs = [ - 'Console.DB' => [ - 'prefix' => 'console', - 'databases' => Config::getParam('pools-console') - ], - 'Database.DB' => [ - 'prefix' => 'database', - 'databases' => Config::getParam('pools-database') - ], + 'Console.DB' => Config::getParam('pools-console'), + 'Projects.DB' => Config::getParam('pools-database'), ]; - foreach ($configs as $key => $config) { - foreach ($config['databases'] as $database) { + foreach ($config as $database) { try { - $pool = $pools['pools-' . $config['prefix'] . '-' . $database]['pool']; - $dsn = $pools['pools-' . $config['prefix'] . '-' . $database]['dsn']; - - $connection = $pool->get(); - $connections->add($connection, $pool); - $adapter = match ($dsn->getScheme()) { - 'mariadb' => new MariaDB($connection), - 'mysql' => new MySQL($connection), - default => null - }; - $adapter->setDatabase($dsn->getPath()); - - - if ($adapter->ping()) { - Console::success('🟢 ' . str_pad("$key({$database})", 50, '.') . 'connected'); - } else { - Console::error('🔴 ' . str_pad("$key({$database})", 47, '.') . 'disconnected'); - } - } catch (\Throwable $th) { - Console::error('🔴 ' . str_pad("{$key}.({$database})", 47, '.') . 'disconnected'); - } - } - } - - /** @var array $pools */ - $pools = $register->get('pools'); - $configs = [ - 'Cache' => [ - 'prefix' => 'cache', - 'databases' => Config::getParam('pools-cache') - ], - 'Queue' => [ - 'prefix' => 'queue', - 'databases' => Config::getParam('pools-queue') - ], - 'PubSub' => [ - 'prefix' => 'pubsub', - 'databases' => Config::getParam('pools-pubsub') - ], - ]; - foreach ($configs as $key => $config) { - foreach ($config['databases'] as $database) { - try { - $pool = $pools['pools-' . $config['prefix'] . '-' . $database]['pool']; - $dsn = $pools['pools-' . $config['prefix'] . '-' . $database]['dsn']; - $connection = $pool->get(); - $connections->add($connection, $pool); - - $adapter = new Redis($dsn->getHost(), $dsn->getPort()); + $adapter = $pools->get($database)->pop()->getResource(); if ($adapter->ping()) { Console::success('🟢 ' . str_pad("{$key}({$database})", 50, '.') . 'connected'); @@ -204,7 +143,30 @@ class Doctor extends Action Console::error('🔴 ' . str_pad("{$key}({$database})", 47, '.') . 'disconnected'); } } catch (\Throwable $th) { - Console::error('🔴 ' . str_pad("{$key}({$database})", 47, '.') . 'disconnected'); + Console::error('🔴 ' . str_pad("{$key}.({$database})", 47, '.') . 'disconnected'); + } + } + } + + $pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */ + $configs = [ + 'Cache' => Config::getParam('pools-cache'), + 'Queue' => Config::getParam('pools-queue'), + 'PubSub' => Config::getParam('pools-pubsub'), + ]; + + foreach ($configs as $key => $config) { + foreach ($config as $pool) { + try { + $adapter = $pools->get($pool)->pop()->getResource(); + + if ($adapter->ping()) { + Console::success('🟢 ' . str_pad("{$key}({$pool})", 50, '.') . 'connected'); + } else { + Console::error('🔴 ' . str_pad("{$key}({$pool})", 47, '.') . 'disconnected'); + } + } catch (\Throwable $th) { + Console::error('🔴 ' . str_pad("{$key}({$pool})", 47, '.') . 'disconnected'); } } } @@ -296,7 +258,7 @@ class Doctor extends Action } try { - if (Http::isProduction()) { + if (App::isProduction()) { Console::log(''); $version = \json_decode(@\file_get_contents(System::getEnv('_APP_HOME', 'http://localhost') . '/version'), true); diff --git a/src/Appwrite/Platform/Tasks/Install.php b/src/Appwrite/Platform/Tasks/Install.php index a1a73e6385..4abd267684 100644 --- a/src/Appwrite/Platform/Tasks/Install.php +++ b/src/Appwrite/Platform/Tasks/Install.php @@ -8,9 +8,9 @@ use Appwrite\Docker\Env; use Appwrite\Utopia\View; use Utopia\CLI\Console; use Utopia\Config\Config; -use Utopia\Http\Validator\Boolean; -use Utopia\Http\Validator\Text; use Utopia\Platform\Action; +use Utopia\Validator\Boolean; +use Utopia\Validator\Text; class Install extends Action { @@ -213,7 +213,8 @@ class Install extends Action } $env = ''; - $output = ''; + $stdout = ''; + $stderr = ''; foreach ($input as $key => $value) { if ($value) { @@ -224,13 +225,13 @@ class Install extends Action $exit = 0; if (!$noStart) { Console::log("Running \"docker compose up -d --remove-orphans --renew-anon-volumes\""); - $exit = Console::execute("$env docker compose --project-directory $this->path up -d --remove-orphans --renew-anon-volumes", '', $output); + $exit = Console::execute("$env docker compose --project-directory $this->path up -d --remove-orphans --renew-anon-volumes", '', $stdout, $stderr); } if ($exit !== 0) { $message = 'Failed to install Appwrite dockers'; Console::error($message); - Console::error($output); + Console::error($stderr); Console::exit($exit); } else { $message = 'Appwrite installed successfully'; diff --git a/src/Appwrite/Platform/Tasks/Migrate.php b/src/Appwrite/Platform/Tasks/Migrate.php index 55be0b81c2..dcba59bb1d 100644 --- a/src/Appwrite/Platform/Tasks/Migrate.php +++ b/src/Appwrite/Platform/Tasks/Migrate.php @@ -10,10 +10,10 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; -use Utopia\Http\Validator\Text; use Utopia\Platform\Action; use Utopia\Registry\Registry; use Utopia\System\System; +use Utopia\Validator\Text; class Migrate extends Action { @@ -30,15 +30,12 @@ class Migrate extends Action ->desc('Migrate Appwrite to new version') /** @TODO APP_VERSION_STABLE needs to be defined */ ->param('version', APP_VERSION_STABLE, new Text(8), 'Version to migrate to.', true) - ->inject('cache') ->inject('dbForConsole') ->inject('getProjectDB') ->inject('register') - ->inject('authorization') - ->inject('console') - ->callback(function ($version, $dbForConsole, $getProjectDB, Registry $register, Authorization $authorization, Document $console) { - \Co\run(function () use ($version, $dbForConsole, $getProjectDB, $register, $authorization, $console) { - $this->action($version, $dbForConsole, $getProjectDB, $register, $authorization, $console); + ->callback(function ($version, $dbForConsole, $getProjectDB, Registry $register) { + \Co\run(function () use ($version, $dbForConsole, $getProjectDB, $register) { + $this->action($version, $dbForConsole, $getProjectDB, $register); }); }); } @@ -61,12 +58,13 @@ class Migrate extends Action } } - public function action(string $version, Database $dbForConsole, callable $getProjectDB, Registry $register, Authorization $auth, Document $console) + public function action(string $version, Database $dbForConsole, callable $getProjectDB, Registry $register) { - $auth->disable(); + Authorization::disable(); if (!array_key_exists($version, Migration::$versions)) { Console::error("Version {$version} not found."); Console::exit(1); + return; } @@ -79,8 +77,12 @@ class Migrate extends Action 10 ); + $app = new App('UTC'); + Console::success('Starting Data Migration to version ' . $version); + $console = $app->getResource('console'); + $limit = 30; $sum = 30; $offset = 0; @@ -99,7 +101,7 @@ class Migrate extends Action $class = 'Appwrite\\Migration\\Version\\' . Migration::$versions[$version]; /** @var Migration $migration */ - $migration = new $class($auth, ); + $migration = new $class(); while (!empty($projects)) { foreach ($projects as $project) { diff --git a/src/Appwrite/Platform/Tasks/QueueCount.php b/src/Appwrite/Platform/Tasks/QueueCount.php index 63f829073a..b02165c1d2 100644 --- a/src/Appwrite/Platform/Tasks/QueueCount.php +++ b/src/Appwrite/Platform/Tasks/QueueCount.php @@ -3,11 +3,11 @@ namespace Appwrite\Platform\Tasks; use Utopia\CLI\Console; -use Utopia\Http\Validator\Text; -use Utopia\Http\Validator\WhiteList; use Utopia\Platform\Action; use Utopia\Queue\Client; use Utopia\Queue\Connection; +use Utopia\Validator\Text; +use Utopia\Validator\WhiteList; class QueueCount extends Action { diff --git a/src/Appwrite/Platform/Tasks/QueueRetry.php b/src/Appwrite/Platform/Tasks/QueueRetry.php index 63f6c8e11e..b6139dc177 100644 --- a/src/Appwrite/Platform/Tasks/QueueRetry.php +++ b/src/Appwrite/Platform/Tasks/QueueRetry.php @@ -3,11 +3,11 @@ namespace Appwrite\Platform\Tasks; use Utopia\CLI\Console; -use Utopia\Http\Validator\Text; -use Utopia\Http\Validator\Wildcard; use Utopia\Platform\Action; use Utopia\Queue\Client; use Utopia\Queue\Connection; +use Utopia\Validator\Text; +use Utopia\Validator\Wildcard; class QueueRetry extends Action { diff --git a/src/Appwrite/Platform/Tasks/SSL.php b/src/Appwrite/Platform/Tasks/SSL.php index ad4098d9ee..5af0cb6cd8 100644 --- a/src/Appwrite/Platform/Tasks/SSL.php +++ b/src/Appwrite/Platform/Tasks/SSL.php @@ -5,10 +5,10 @@ namespace Appwrite\Platform\Tasks; use Appwrite\Event\Certificate; use Utopia\CLI\Console; use Utopia\Database\Document; -use Utopia\Http\Validator\Boolean; -use Utopia\Http\Validator\Hostname; use Utopia\Platform\Action; use Utopia\System\System; +use Utopia\Validator\Boolean; +use Utopia\Validator\Hostname; class SSL extends Action { diff --git a/src/Appwrite/Platform/Tasks/ScheduleBase.php b/src/Appwrite/Platform/Tasks/ScheduleBase.php index c2636f82e6..a1b85c341f 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleBase.php +++ b/src/Appwrite/Platform/Tasks/ScheduleBase.php @@ -2,45 +2,41 @@ namespace Appwrite\Platform\Tasks; -use Appwrite\Utopia\Queue\Connections; use Swoole\Timer; use Utopia\CLI\Console; +use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Exception; use Utopia\Database\Query; use Utopia\Platform\Action; +use Utopia\Pools\Group; use Utopia\System\System; +use function Swoole\Coroutine\run; + abstract class ScheduleBase extends Action { protected const UPDATE_TIMER = 10; //seconds protected const ENQUEUE_TIMER = 60; //seconds protected array $schedules = []; - protected Connections $connections; abstract public static function getName(): string; - abstract public static function getSupportedResource(): string; abstract public static function getCollectionId(): string; - - abstract protected function enqueueResources( - array $pools, - callable $getConsoleDB - ); + abstract protected function enqueueResources(Group $pools, Database $dbForConsole, callable $getProjectDB): void; public function __construct() { - $this->connections = new Connections(); $type = static::getSupportedResource(); $this ->desc("Execute {$type}s scheduled in Appwrite") ->inject('pools') - ->inject('getConsoleDB') + ->inject('dbForConsole') ->inject('getProjectDB') - ->callback(fn (array $pools, callable $getConsoleDB, callable $getProjectDB) => $this->action($pools, $getConsoleDB, $getProjectDB)); + ->callback(fn (Group $pools, Database $dbForConsole, callable $getProjectDB) => $this->action($pools, $dbForConsole, $getProjectDB)); } /** @@ -48,12 +44,11 @@ abstract class ScheduleBase extends Action * 2. Create timer that sync all changes from 'schedules' collection to local copy. Only reading changes thanks to 'resourceUpdatedAt' attribute * 3. Create timer that prepares coroutines for soon-to-execute schedules. When it's ready, coroutine sleeps until exact time before sending request to worker. */ - public function action(array $pools, callable $getConsoleDB, callable $getProjectDB): void + public function action(Group $pools, Database $dbForConsole, callable $getProjectDB): void { Console::title(\ucfirst(static::getSupportedResource()) . ' scheduler V1'); Console::success(APP_NAME . ' ' . \ucfirst(static::getSupportedResource()) . ' scheduler v1 has started'); - [$_, $_, $dbForConsole] = $getConsoleDB(); /** * Extract only necessary attributes to lower memory used. * @@ -64,12 +59,6 @@ abstract class ScheduleBase extends Action $getSchedule = function (Document $schedule) use ($dbForConsole, $getProjectDB): array { $project = $dbForConsole->getDocument('projects', $schedule->getAttribute('projectId')); - $collectionId = match ($schedule->getAttribute('resourceType')) { - 'function' => 'functions', - 'message' => 'messages', - 'execution' => 'executions' - }; - $resource = $getProjectDB($project)->getDocument( static::getCollectionId(), $schedule->getAttribute('resourceId') @@ -124,73 +113,76 @@ abstract class ScheduleBase extends Action $latestDocument = \end($results); } + $pools->reclaim(); Console::success("{$total} resources were loaded in " . (\microtime(true) - $loadStart) . " seconds"); Console::success("Starting timers at " . DateTime::now()); - Timer::tick(static::UPDATE_TIMER * 1000, function () use ($getConsoleDB, &$lastSyncUpdate, $getSchedule, $pools) { - [$connection,$pool, $dbForConsole] = $getConsoleDB(); - $connections = new Connections(); - $connections->add($connection, $pool); + run(function () use ($dbForConsole, &$lastSyncUpdate, $getSchedule, $pools, $getProjectDB) { + /** + * The timer synchronize $schedules copy with database collection. + */ + Timer::tick(static::UPDATE_TIMER * 1000, function () use ($dbForConsole, &$lastSyncUpdate, $getSchedule, $pools) { + $time = DateTime::now(); + $timerStart = \microtime(true); - $time = DateTime::now(); - $timerStart = \microtime(true); + $limit = 1000; + $sum = $limit; + $total = 0; + $latestDocument = null; - $limit = 1000; - $sum = $limit; - $total = 0; - $latestDocument = null; + Console::log("Sync tick: Running at $time"); - Console::log("Sync tick: Running at $time"); + while ($sum === $limit) { + $paginationQueries = [Query::limit($limit)]; - while ($sum === $limit) { - $paginationQueries = [Query::limit($limit)]; - - if ($latestDocument) { - $paginationQueries[] = Query::cursorAfter($latestDocument); - } - - $results = $dbForConsole->find('schedules', \array_merge($paginationQueries, [ - Query::equal('region', [System::getEnv('_APP_REGION', 'default')]), - Query::equal('resourceType', [static::getSupportedResource()]), - Query::greaterThanEqual('resourceUpdatedAt', $lastSyncUpdate), - ])); - - $sum = count($results); - $total = $total + $sum; - - foreach ($results as $document) { - $localDocument = $this->schedules[$document->getInternalId()] ?? null; - - // Check if resource has been updated since last sync - $org = $localDocument !== null ? \strtotime($localDocument['resourceUpdatedAt']) : null; - $new = \strtotime($document['resourceUpdatedAt']); - - if (!$document['active']) { - Console::info("Removing: {$document['resourceType']}::{$document['resourceId']}"); - unset($this->schedules[$document->getInternalId()]); - } elseif ($new !== $org) { - Console::info("Updating: {$document['resourceType']}::{$document['resourceId']}"); - $this->schedules[$document->getInternalId()] = $getSchedule($document); + if ($latestDocument) { + $paginationQueries[] = Query::cursorAfter($latestDocument); } + + $results = $dbForConsole->find('schedules', \array_merge($paginationQueries, [ + Query::equal('region', [System::getEnv('_APP_REGION', 'default')]), + Query::equal('resourceType', [static::getSupportedResource()]), + Query::greaterThanEqual('resourceUpdatedAt', $lastSyncUpdate), + ])); + + $sum = count($results); + $total = $total + $sum; + + foreach ($results as $document) { + $localDocument = $this->schedules[$document->getInternalId()] ?? null; + + // Check if resource has been updated since last sync + $org = $localDocument !== null ? \strtotime($localDocument['resourceUpdatedAt']) : null; + $new = \strtotime($document['resourceUpdatedAt']); + + if (!$document['active']) { + Console::info("Removing: {$document['resourceType']}::{$document['resourceId']}"); + unset($this->schedules[$document->getInternalId()]); + } elseif ($new !== $org) { + Console::info("Updating: {$document['resourceType']}::{$document['resourceId']}"); + $this->schedules[$document->getInternalId()] = $getSchedule($document); + } + } + + $latestDocument = \end($results); } - $latestDocument = \end($results); - } + $lastSyncUpdate = $time; + $timerEnd = \microtime(true); - $lastSyncUpdate = $time; - $timerEnd = \microtime(true); + $pools->reclaim(); - $connections->reclaim(); - Console::log("Sync tick: {$total} schedules were updated in " . ($timerEnd - $timerStart) . " seconds"); + Console::log("Sync tick: {$total} schedules were updated in " . ($timerEnd - $timerStart) . " seconds"); + }); + + Timer::tick( + static::ENQUEUE_TIMER * 1000, + fn () => $this->enqueueResources($pools, $dbForConsole, $getProjectDB) + ); + + $this->enqueueResources($pools, $dbForConsole, $getProjectDB); }); - - Timer::tick( - static::ENQUEUE_TIMER * 1000, - fn () => $this->enqueueResources($pools, $getConsoleDB) - ); - - $this->enqueueResources($pools, $getConsoleDB); } } diff --git a/src/Appwrite/Platform/Tasks/ScheduleExecutions.php b/src/Appwrite/Platform/Tasks/ScheduleExecutions.php index 42bc98a406..73a2814397 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleExecutions.php +++ b/src/Appwrite/Platform/Tasks/ScheduleExecutions.php @@ -4,7 +4,8 @@ namespace Appwrite\Platform\Tasks; use Appwrite\Event\Func; use Swoole\Coroutine as Co; -use Utopia\Queue\Connection\Redis; +use Utopia\Database\Database; +use Utopia\Pools\Group; class ScheduleExecutions extends ScheduleBase { @@ -26,16 +27,11 @@ class ScheduleExecutions extends ScheduleBase return 'executions'; } - protected function enqueueResources(array $pools, callable $getConsoleDB): void + protected function enqueueResources(Group $pools, Database $dbForConsole, callable $getProjectDB): void { - [$connection,$pool, $dbForConsole] = $getConsoleDB(); - $this->connections->add($connection, $pool); - - $queuePool = $pools['pools-queue-queue']['pool']; - $queueConnection = $queuePool->get(); - $this->connections->add($queueConnection, $queuePool); - - $queueForFunctions = new Func(new Redis($queueConnection)); + $queue = $pools->get('queue')->pop(); + $connection = $queue->getResource(); + $queueForFunctions = new Func($connection); $intervalEnd = (new \DateTime())->modify('+' . self::ENQUEUE_TIMER . ' seconds'); foreach ($this->schedules as $schedule) { @@ -61,7 +57,7 @@ class ScheduleExecutions extends ScheduleBase $delay = $scheduledAt->getTimestamp() - (new \DateTime())->getTimestamp(); - \go(function () use ($queueForFunctions, $schedule, $delay, $data, $dbForConsole) { + \go(function () use ($queueForFunctions, $schedule, $delay, $data) { Co::sleep($delay); $queueForFunctions->setType('schedule') @@ -76,16 +72,16 @@ class ScheduleExecutions extends ScheduleBase ->setProject($schedule['project']) ->setUserId($data['userId'] ?? '') ->trigger(); - - $dbForConsole->deleteDocument( - 'schedules', - $schedule['$id'], - ); }); + $dbForConsole->deleteDocument( + 'schedules', + $schedule['$id'], + ); + unset($this->schedules[$schedule['$internalId']]); } - $this->connections->reclaim(); + $queue->reclaim(); } } diff --git a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php index a035f5ab41..4d57902330 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php +++ b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php @@ -5,8 +5,9 @@ namespace Appwrite\Platform\Tasks; use Appwrite\Event\Func; use Cron\CronExpression; use Utopia\CLI\Console; +use Utopia\Database\Database; use Utopia\Database\DateTime; -use Utopia\Queue\Connection\Redis; +use Utopia\Pools\Group; class ScheduleFunctions extends ScheduleBase { @@ -30,7 +31,7 @@ class ScheduleFunctions extends ScheduleBase return 'functions'; } - protected function enqueueResources(array $pools, callable $getConsoleDB): void + protected function enqueueResources(Group $pools, Database $dbForConsole, callable $getProjectDB): void { $timerStart = \microtime(true); $time = DateTime::now(); @@ -72,11 +73,8 @@ class ScheduleFunctions extends ScheduleBase \go(function () use ($delay, $scheduleKeys, $pools) { \sleep($delay); // in seconds - $pool = $pools['pools-queue-queue']['pool']; - $connection = $pool->get(); - $this->connections->add($connection, $pool); - - $queueConnection = new Redis($connection); + $queue = $pools->get('queue')->pop(); + $connection = $queue->getResource(); foreach ($scheduleKeys as $scheduleKey) { // Ensure schedule was not deleted @@ -86,7 +84,7 @@ class ScheduleFunctions extends ScheduleBase $schedule = $this->schedules[$scheduleKey]; - $queueForFunctions = new Func($queueConnection); + $queueForFunctions = new Func($connection); $queueForFunctions ->setType('schedule') @@ -97,8 +95,7 @@ class ScheduleFunctions extends ScheduleBase ->trigger(); } - $this->connections->reclaim(); - // $queue->reclaim(); // TODO: Do in try/catch/finally, or add to connectons resource + $queue->reclaim(); }); } diff --git a/src/Appwrite/Platform/Tasks/ScheduleMessages.php b/src/Appwrite/Platform/Tasks/ScheduleMessages.php index 386c10582f..b9d8e2a282 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleMessages.php +++ b/src/Appwrite/Platform/Tasks/ScheduleMessages.php @@ -3,7 +3,8 @@ namespace Appwrite\Platform\Tasks; use Appwrite\Event\Messaging; -use Utopia\Queue\Connection\Redis; +use Utopia\Database\Database; +use Utopia\Pools\Group; class ScheduleMessages extends ScheduleBase { @@ -25,11 +26,8 @@ class ScheduleMessages extends ScheduleBase return 'messages'; } - protected function enqueueResources(array $pools, callable $getConsoleDB): void + protected function enqueueResources(Group $pools, Database $dbForConsole, callable $getProjectDB): void { - [$connection,$pool, $dbForConsole] = $getConsoleDB(); - $this->connections->add($connection, $pool); - foreach ($this->schedules as $schedule) { if (!$schedule['active']) { continue; @@ -42,15 +40,10 @@ class ScheduleMessages extends ScheduleBase continue; } - \go(function () use ($now, $schedule, $pools, $dbForConsole) { - $pool = $pools['pools-queue-queue']['pool']; - $dsn = $pools['pools-queue-queue']['dsn']; - $connection = $pool->get(); - $this->connections->add($connection, $pool); - - $queueConnection = new Redis($dsn->getHost(), $dsn->getPort()); - - $queueForMessaging = new Messaging($queueConnection); + \go(function () use ($schedule, $pools, $dbForConsole) { + $queue = $pools->get('queue')->pop(); + $connection = $queue->getResource(); + $queueForMessaging = new Messaging($connection); $queueForMessaging ->setType(MESSAGE_SEND_TYPE_EXTERNAL) @@ -63,7 +56,8 @@ class ScheduleMessages extends ScheduleBase $schedule['$id'], ); - $this->connections->reclaim(); + $queue->reclaim(); + unset($this->schedules[$schedule['$internalId']]); }); } diff --git a/src/Appwrite/Platform/Tasks/Specs.php b/src/Appwrite/Platform/Tasks/Specs.php index 776410e4e4..f71de98d95 100644 --- a/src/Appwrite/Platform/Tasks/Specs.php +++ b/src/Appwrite/Platform/Tasks/Specs.php @@ -5,27 +5,25 @@ namespace Appwrite\Platform\Tasks; use Appwrite\Specification\Format\OpenAPI3; use Appwrite\Specification\Format\Swagger2; use Appwrite\Specification\Specification; -use Appwrite\Utopia\Response; -use Appwrite\Utopia\Response\Models; +use Appwrite\Utopia\Request as AppwriteRequest; +use Appwrite\Utopia\Response as AppwriteResponse; use Exception; -use Swoole\Http\Request as SwooleHttpRequest; -use Swoole\Http\Response as SwooleHttpResponse; +use Swoole\Http\Request as SwooleRequest; +use Swoole\Http\Response as SwooleResponse; +use Utopia\App; use Utopia\Cache\Adapter\None; use Utopia\Cache\Cache; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Database\Adapter\MySQL; use Utopia\Database\Database; -use Utopia\DI\Container; -use Utopia\DI\Dependency; -use Utopia\Http\Adapter\FPM\Server; -use Utopia\Http\Adapter\Swoole\Request; -use Utopia\Http\Adapter\Swoole\Response as HttpResponse; -use Utopia\Http\Http; -use Utopia\Http\Validator\Text; -use Utopia\Http\Validator\WhiteList; use Utopia\Platform\Action; +use Utopia\Registry\Registry; +use Utopia\Request as UtopiaRequest; +use Utopia\Response as UtopiaResponse; use Utopia\System\System; +use Utopia\Validator\Text; +use Utopia\Validator\WhiteList; class Specs extends Action { @@ -34,38 +32,37 @@ class Specs extends Action return 'specs'; } + public function getRequest(): UtopiaRequest + { + return new AppwriteRequest(new SwooleRequest()); + } + + public function getResponse(): UtopiaResponse + { + return new AppwriteResponse(new SwooleResponse()); + } + public function __construct() { $this ->desc('Generate Appwrite API specifications') ->param('version', 'latest', new Text(16), 'Spec version', true) ->param('mode', 'normal', new WhiteList(['normal', 'mocks']), 'Spec Mode', true) - ->inject('context') - ->callback(fn (string $version, string $mode, Container $context) => $this->action($version, $mode, $context)); + ->inject('register') + ->callback(fn (string $version, string $mode, Registry $register) => $this->action($version, $mode, $register)); } - public function action(string $version, string $mode, Container $container): void + public function action(string $version, string $mode, Registry $register): void { - $appRoutes = Http::getRoutes(); - $response = new Response(new HttpResponse(new SwooleHttpResponse())); + $appRoutes = App::getRoutes(); + $response = $this->getResponse(); $mocks = ($mode === 'mocks'); - $requestDependency = new Dependency(); - $responseDependency = new Dependency(); - $dbForConsole = new Dependency(); - $dbForProject = new Dependency(); - // Mock dependencies - $requestDependency->setName('request')->setCallback(fn () => new Request(new SwooleHttpRequest())); - $responseDependency->setName('response')->setCallback(fn () => $response); - $dbForConsole->setName('dbForConsole')->setCallback(fn () => new Database(new MySQL(''), new Cache(new None()))); - $dbForProject->setName('dbForProject')->setCallback(fn () => new Database(new MySQL(''), new Cache(new None()))); - - $container - ->set($requestDependency) - ->set($responseDependency) - ->set($dbForProject) - ->set($dbForConsole); + App::setResource('request', fn () => $this->getRequest()); + App::setResource('response', fn () => $response); + App::setResource('dbForConsole', fn () => new Database(new MySQL(''), new Cache(new None()))); + App::setResource('dbForProject', fn () => new Database(new MySQL(''), new Cache(new None()))); $platforms = [ 'client' => APP_PLATFORM_CLIENT, @@ -255,7 +252,7 @@ class Specs extends Action ]; } - $models = Models::getModels(); + $models = $response->getModels(); foreach ($models as $key => $value) { if ($platform !== APP_PLATFORM_CONSOLE && !$value->isPublic()) { @@ -263,7 +260,7 @@ class Specs extends Action } } - $arguments = [new Http(new Server(), $container, 'UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0]; + $arguments = [new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0]; foreach (['swagger2', 'open-api3'] as $format) { $formatInstance = match ($format) { 'swagger2' => new Swagger2(...$arguments), diff --git a/src/Appwrite/Platform/Tasks/Upgrade.php b/src/Appwrite/Platform/Tasks/Upgrade.php index 608b924c06..341ce42fc4 100644 --- a/src/Appwrite/Platform/Tasks/Upgrade.php +++ b/src/Appwrite/Platform/Tasks/Upgrade.php @@ -3,8 +3,8 @@ namespace Appwrite\Platform\Tasks; use Utopia\CLI\Console; -use Utopia\Http\Validator\Boolean; -use Utopia\Http\Validator\Text; +use Utopia\Validator\Boolean; +use Utopia\Validator\Text; class Upgrade extends Install { diff --git a/src/Appwrite/Platform/Workers/Audits.php b/src/Appwrite/Platform/Workers/Audits.php index d21f1c267d..86ca59d3fd 100644 --- a/src/Appwrite/Platform/Workers/Audits.php +++ b/src/Appwrite/Platform/Workers/Audits.php @@ -9,7 +9,6 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Authorization; use Utopia\Database\Exception\Structure; -use Utopia\Database\Validator\Authorization as ValidatorAuthorization; use Utopia\Platform\Action; use Utopia\Queue\Message; @@ -29,8 +28,7 @@ class Audits extends Action ->desc('Audits worker') ->inject('message') ->inject('dbForProject') - ->inject('authorization') - ->callback(fn ($message, $dbForProject, ValidatorAuthorization $authorization) => $this->action($message, $dbForProject, $authorization)); + ->callback(fn ($message, $dbForProject) => $this->action($message, $dbForProject)); } @@ -43,7 +41,7 @@ class Audits extends Action * @throws Authorization * @throws Structure */ - public function action(Message $message, Database $dbForProject, ValidatorAuthorization $auth): void + public function action(Message $message, Database $dbForProject): void { $payload = $message->getPayload() ?? []; diff --git a/src/Appwrite/Platform/Workers/Builds.php b/src/Appwrite/Platform/Workers/Builds.php index 9274e7430c..5dd2f7f886 100644 --- a/src/Appwrite/Platform/Workers/Builds.php +++ b/src/Appwrite/Platform/Workers/Builds.php @@ -54,8 +54,7 @@ class Builds extends Action ->inject('dbForProject') ->inject('deviceForFunctions') ->inject('log') - ->inject('authorization') - ->callback(fn ($message, Database $dbForConsole, Event $queueForEvents, Func $queueForFunctions, Usage $usage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, Log $log, Authorization $authorization) => $this->action($message, $dbForConsole, $queueForEvents, $queueForFunctions, $usage, $cache, $dbForProject, $deviceForFunctions, $log, $authorization)); + ->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)); } /** @@ -68,11 +67,10 @@ class Builds extends Action * @param Database $dbForProject * @param Device $deviceForFunctions * @param Log $log - * @param Authorization $auth * @return void * @throws \Utopia\Database\Exception */ - public function action(Message $message, Database $dbForConsole, Event $queueForEvents, Func $queueForFunctions, Usage $queueForUsage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, Log $log, Authorization $auth): void + public function action(Message $message, Database $dbForConsole, Event $queueForEvents, Func $queueForFunctions, Usage $queueForUsage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, Log $log): void { $payload = $message->getPayload() ?? []; @@ -94,7 +92,7 @@ class Builds extends Action case BUILD_TYPE_RETRY: Console::info('Creating build for deployment: ' . $deployment->getId()); $github = new GitHub($cache); - $this->buildDeployment($deviceForFunctions, $queueForFunctions, $queueForEvents, $queueForUsage, $dbForConsole, $dbForProject, $github, $project, $resource, $deployment, $template, $log, $auth); + $this->buildDeployment($deviceForFunctions, $queueForFunctions, $queueForEvents, $queueForUsage, $dbForConsole, $dbForProject, $github, $project, $resource, $deployment, $template, $log); break; default: @@ -115,12 +113,11 @@ class Builds extends Action * @param Document $deployment * @param Document $template * @param Log $log - * @param Authorization $auth * @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 $function, Document $deployment, Document $template, Log $log, Authorization $auth): 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 { $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST')); @@ -224,18 +221,20 @@ class Builds extends Action $templateRootDirectory = \ltrim($templateRootDirectory, '/'); if (!empty($templateRepositoryName) && !empty($templateOwnerName) && !empty($templateVersion)) { - $output = ''; + $stdout = ''; + $stderr = ''; + // Clone template repo $tmpTemplateDirectory = '/tmp/builds/' . $buildId . '-template'; $gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateVersion, GitHub::CLONE_TYPE_TAG, $tmpTemplateDirectory, $templateRootDirectory); - $exit = Console::execute($gitCloneCommandForTemplate, '', $output); + $exit = Console::execute($gitCloneCommandForTemplate, '', $stdout, $stderr); if ($exit !== 0) { - throw new \Exception('Unable to clone code repository: ' . $output); + throw new \Exception('Unable to clone code repository: ' . $stderr); } // Ensure directories - Console::execute('mkdir -p ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory), '', $output); + Console::execute('mkdir -p ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory), '', $stdout, $stderr); $tmpPathFile = $tmpTemplateDirectory . '/code.tar.gz'; @@ -246,7 +245,7 @@ class Builds extends Action } $tarParamDirectory = \escapeshellarg($tmpTemplateDirectory . (empty($templateRootDirectory) ? '' : '/' . $templateRootDirectory)); - Console::execute('tar --exclude code.tar.gz -czf ' . \escapeshellarg($tmpPathFile) . ' -C ' . \escapeshellcmd($tarParamDirectory) . ' .', '', $output); // TODO: Replace escapeshellcmd with escapeshellarg if we find a way that doesnt break syntax + Console::execute('tar --exclude code.tar.gz -czf ' . \escapeshellarg($tmpPathFile) . ' -C ' . \escapeshellcmd($tarParamDirectory) . ' .', '', $stdout, $stderr); // TODO: Replace escapeshellcmd with escapeshellarg if we find a way that doesnt break syntax $source = $deviceForFunctions->getPath($deployment->getId() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION)); $result = $localDevice->transfer($tmpPathFile, $source, $deviceForFunctions); @@ -255,7 +254,7 @@ class Builds extends Action throw new \Exception("Unable to move file"); } - Console::execute('rm -rf ' . \escapeshellarg($tmpTemplateDirectory), '', $output); + Console::execute('rm -rf ' . \escapeshellarg($tmpTemplateDirectory), '', $stdout, $stderr); $directorySize = $deviceForFunctions->getFileSize($source); $build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttribute('source', $source)); @@ -277,7 +276,6 @@ class Builds extends Action $branchName = $deployment->getAttribute('providerBranch'); $commitHash = $deployment->getAttribute('providerCommitHash', ''); - $output = ''; $cloneVersion = $branchName; $cloneType = GitHub::CLONE_TYPE_BRANCH; @@ -285,18 +283,22 @@ class Builds extends Action $cloneVersion = $commitHash; $cloneType = GitHub::CLONE_TYPE_COMMIT; } + $gitCloneCommand = $github->generateCloneCommand($cloneOwner, $cloneRepository, $cloneVersion, $cloneType, $tmpDirectory, $rootDirectory); - Console::execute('mkdir -p ' . \escapeshellarg('/tmp/builds/' . $buildId), '', $output); + $stdout = ''; + $stderr = ''; + + Console::execute('mkdir -p ' . \escapeshellarg('/tmp/builds/' . $buildId), '', $stdout, $stderr); if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') { Console::info('Build has been canceled'); return; } - $exit = Console::execute($gitCloneCommand, '', $output); + $exit = Console::execute($gitCloneCommand, '', $stdout, $stderr); if ($exit !== 0) { - throw new \Exception('Unable to clone code repository: ' . $output); + throw new \Exception('Unable to clone code repository: ' . $stderr); } // Local refactoring for function folder with spaces @@ -304,10 +306,10 @@ class Builds extends Action $rootDirectoryWithoutSpaces = str_replace(' ', '', $rootDirectory); $from = $tmpDirectory . '/' . $rootDirectory; $to = $tmpDirectory . '/' . $rootDirectoryWithoutSpaces; - $exit = Console::execute('mv "' . \escapeshellarg($from) . '" "' . \escapeshellarg($to) . '"', '', $output); + $exit = Console::execute('mv "' . \escapeshellarg($from) . '" "' . \escapeshellarg($to) . '"', '', $stdout, $stderr); if ($exit !== 0) { - throw new \Exception('Unable to move function with spaces' . $output); + throw new \Exception('Unable to move function with spaces' . $stderr); } $rootDirectory = $rootDirectoryWithoutSpaces; } @@ -328,33 +330,33 @@ class Builds extends Action $tmpTemplateDirectory = '/tmp/builds/' . $buildId . '/template'; $gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateVersion, GitHub::CLONE_TYPE_TAG, $tmpTemplateDirectory, $templateRootDirectory); - $exit = Console::execute($gitCloneCommandForTemplate, '', $output); + $exit = Console::execute($gitCloneCommandForTemplate, '', $stdout, $stderr); if ($exit !== 0) { - throw new \Exception('Unable to clone code repository: ' . $output); + throw new \Exception('Unable to clone code repository: ' . $stderr); } // Ensure directories - Console::execute('mkdir -p ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory), '', $output); - Console::execute('mkdir -p ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $output); + Console::execute('mkdir -p ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory), '', $stdout, $stderr); + Console::execute('mkdir -p ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $stdout, $stderr); // Merge template into user repo - Console::execute('rsync -av --exclude \'.git\' ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory . '/') . ' ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $output); + 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($function->getAttribute('name', '')) . ' function" && git push origin ' . \escapeshellarg($branchName), '', $output); + $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: ' . $output); + throw new \Exception('Unable to push code repository: ' . $stderr); } - $exit = Console::execute('cd ' . \escapeshellarg($tmpDirectory) . ' && git rev-parse HEAD', '', $output); + $exit = Console::execute('cd ' . \escapeshellarg($tmpDirectory) . ' && git rev-parse HEAD', '', $stdout, $stderr); if ($exit !== 0) { - throw new \Exception('Unable to get vcs commit SHA: ' . $output); + throw new \Exception('Unable to get vcs commit SHA: ' . $stderr); } - $providerCommitHash = \trim($output); + $providerCommitHash = \trim($stdout); $authorUrl = "https://github.com/$cloneOwner"; $deployment->setAttribute('providerCommitHash', $providerCommitHash ?? ''); @@ -397,7 +399,7 @@ class Builds extends Action } $tarParamDirectory = '/tmp/builds/' . $buildId . '/code' . (empty($rootDirectory) ? '' : '/' . $rootDirectory); - Console::execute('tar --exclude code.tar.gz -czf ' . \escapeshellarg($tmpPathFile) . ' -C ' . \escapeshellcmd($tarParamDirectory) . ' .', '', $output); // TODO: Replace escapeshellcmd with escapeshellarg if we find a way that doesnt break syntax + Console::execute('tar --exclude code.tar.gz -czf ' . \escapeshellarg($tmpPathFile) . ' -C ' . \escapeshellcmd($tarParamDirectory) . ' .', '', $stdout, $stderr); // TODO: Replace escapeshellcmd with escapeshellarg if we find a way that doesnt break syntax $source = $deviceForFunctions->getPath($deployment->getId() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION)); $result = $localDevice->transfer($tmpPathFile, $source, $deviceForFunctions); @@ -406,7 +408,7 @@ class Builds extends Action throw new \Exception("Unable to move file"); } - Console::execute('rm -rf ' . \escapeshellarg($tmpPath), '', $output); + Console::execute('rm -rf ' . \escapeshellarg($tmpPath), '', $stdout, $stderr); $build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttribute('source', $source)); @@ -661,7 +663,7 @@ class Builds extends Action ->setAttribute('resourceUpdatedAt', DateTime::now()) ->setAttribute('schedule', $function->getAttribute('schedule')) ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment'))); - $auth->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); + 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'); diff --git a/src/Appwrite/Platform/Workers/Certificates.php b/src/Appwrite/Platform/Workers/Certificates.php index 07e1dcdaa6..58dc1dd28a 100644 --- a/src/Appwrite/Platform/Workers/Certificates.php +++ b/src/Appwrite/Platform/Workers/Certificates.php @@ -11,6 +11,7 @@ use Appwrite\Template\Template; use Appwrite\Utopia\Response\Model\Rule; use Exception; use Throwable; +use Utopia\App; use Utopia\CLI\Console; use Utopia\Database\Database; use Utopia\Database\DateTime; @@ -21,7 +22,6 @@ use Utopia\Database\Exception\Structure; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Domains\Domain; -use Utopia\Http\Http; use Utopia\Locale\Locale; use Utopia\Logger\Log; use Utopia\Platform\Action; @@ -126,7 +126,7 @@ class Certificates extends Action $certificate = $dbForConsole->findOne('certificates', [Query::equal('domain', [$domain->get()])]); // If we don't have certificate for domain yet, let's create new document. At the end we save it - if ($certificate->isEmpty()) { + if (!$certificate) { $certificate = new Document(); $certificate->setAttribute('domain', $domain->get()); } @@ -216,7 +216,7 @@ class Certificates extends Action { // Check if update or insert required $certificateDocument = $dbForConsole->findOne('certificates', [Query::equal('domain', [$domain])]); - if (!$certificateDocument->isEmpty()) { + if (!empty($certificateDocument) && !$certificateDocument->isEmpty()) { // Merge new data with current data $certificate = new Document(\array_merge($certificateDocument->getArrayCopy(), $certificate->getArrayCopy())); $certificate = $dbForConsole->updateDocument('certificates', $certificate->getId(), $certificate); @@ -330,26 +330,30 @@ class Certificates extends Action * * @param string $folder Folder into which certificates should be generated * @param string $domain Domain to generate certificate for - * @return string output + * @return array Named array with keys 'stdout' and 'stderr', both string * @throws Exception */ - private function issueCertificate(string $folder, string $domain, string $email): string + private function issueCertificate(string $folder, string $domain, string $email): array { - $output = ''; + $stdout = ''; + $stderr = ''; - $staging = (Http::isProduction()) ? '' : ' --dry-run'; + $staging = (App::isProduction()) ? '' : ' --dry-run'; $exit = Console::execute("certbot certonly -v --webroot --noninteractive --agree-tos{$staging}" . " --email " . $email . " --cert-name " . $folder . " -w " . APP_STORAGE_CERTIFICATES - . " -d {$domain}", '', $output); + . " -d {$domain}", '', $stdout, $stderr); // Unexpected error, usually 5XX, API limits, ... if ($exit !== 0) { - throw new Exception('Failed to issue a certificate with message: ' . $output); + throw new Exception('Failed to issue a certificate with message: ' . $stderr); } - return $output; + return [ + 'stdout' => $stdout, + 'stderr' => $stderr + ]; } /** @@ -377,7 +381,7 @@ class Certificates extends Action * @return void * @throws Exception */ - private function applyCertificateFiles(string $folder, string $domain, string $letsEncryptData): void + private function applyCertificateFiles(string $folder, string $domain, array $letsEncryptData): void { // Prepare folder in storage for domain @@ -390,19 +394,19 @@ class Certificates extends Action // Move generated files if (!@\rename('/etc/letsencrypt/live/' . $folder . '/cert.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem')) { - throw new Exception('Failed to rename certificate cert.pem. Let\'s Encrypt log: ' . $letsEncryptData); + throw new Exception('Failed to rename certificate cert.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']); } if (!@\rename('/etc/letsencrypt/live/' . $folder . '/chain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/chain.pem')) { - throw new Exception('Failed to rename certificate chain.pem. Let\'s Encrypt log: ' . $letsEncryptData); + throw new Exception('Failed to rename certificate chain.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']); } if (!@\rename('/etc/letsencrypt/live/' . $folder . '/fullchain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/fullchain.pem')) { - throw new Exception('Failed to rename certificate fullchain.pem. Let\'s Encrypt log: ' . $letsEncryptData); + throw new Exception('Failed to rename certificate fullchain.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']); } if (!@\rename('/etc/letsencrypt/live/' . $folder . '/privkey.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/privkey.pem')) { - throw new Exception('Failed to rename certificate privkey.pem. Let\'s Encrypt log: ' . $letsEncryptData); + throw new Exception('Failed to rename certificate privkey.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']); } $config = \implode(PHP_EOL, [ @@ -478,7 +482,7 @@ class Certificates extends Action Query::equal('domain', [$domain]), ]); - if (!$rule->isEmpty()) { + if ($rule !== false && !$rule->isEmpty()) { $rule->setAttribute('certificateId', $certificateId); $rule->setAttribute('status', $success ? 'verified' : 'unverified'); $dbForConsole->updateDocument('rules', $rule->getId(), $rule); diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 53a839254d..62fd8cd177 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -22,7 +22,6 @@ use Utopia\Database\Exception\Conflict; use Utopia\Database\Exception\Restricted; use Utopia\Database\Exception\Structure; use Utopia\Database\Query; -use Utopia\Database\Validator\Authorization as ValidatorAuthorization; use Utopia\DSN\DSN; use Utopia\Logger\Log; use Utopia\Platform\Action; @@ -55,15 +54,14 @@ class Deletes extends Action ->inject('executionRetention') ->inject('auditRetention') ->inject('log') - ->inject('authorization') - ->callback(fn ($message, $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, string $abuseRetention, string $executionRetention, string $auditRetention, Log $log, ValidatorAuthorization $authorization) => $this->action($message, $dbForConsole, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $abuseRetention, $executionRetention, $auditRetention, $log, $authorization)); + ->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 $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, string $abuseRetention, string $executionRetention, string $auditRetention, Log $log, ValidatorAuthorization $auth): 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() ?? []; @@ -119,7 +117,7 @@ class Deletes extends Action break; case DELETE_TYPE_AUDIT: if (!$project->isEmpty()) { - $this->deleteAuditLogs($project, $getProjectDB, $auditRetention, $auth); + $this->deleteAuditLogs($project, $getProjectDB, $auditRetention); } if (!$document->isEmpty()) { @@ -127,7 +125,7 @@ class Deletes extends Action } break; case DELETE_TYPE_ABUSE: - $this->deleteAbuseLogs($project, $getProjectDB, $abuseRetention, $auth); + $this->deleteAbuseLogs($project, $getProjectDB, $abuseRetention); break; case DELETE_TYPE_REALTIME: $this->deleteRealtimeUsage($dbForConsole, $datetime); @@ -701,7 +699,7 @@ class Deletes extends Action * @return void * @throws Exception */ - private function deleteAbuseLogs(Document $project, callable $getProjectDB, string $abuseRetention, ValidatorAuthorization $auth): void + private function deleteAbuseLogs(Document $project, callable $getProjectDB, string $abuseRetention): void { $projectId = $project->getId(); $dbForProject = $getProjectDB($project); @@ -722,7 +720,7 @@ class Deletes extends Action * @return void * @throws Exception */ - private function deleteAuditLogs(Document $project, callable $getProjectDB, string $auditRetention, ValidatorAuthorization $auth): void + private function deleteAuditLogs(Document $project, callable $getProjectDB, string $auditRetention): void { $projectId = $project->getId(); $dbForProject = $getProjectDB($project); diff --git a/src/Appwrite/Platform/Workers/Mails.php b/src/Appwrite/Platform/Workers/Mails.php index 51bdb56cf0..e48f96ea90 100644 --- a/src/Appwrite/Platform/Workers/Mails.php +++ b/src/Appwrite/Platform/Workers/Mails.php @@ -84,13 +84,13 @@ class Mails extends Action $bodyTemplate = __DIR__ . '/../../../../app/config/locale/templates/email-base.tpl'; } $bodyTemplate = Template::fromFile($bodyTemplate); - $bodyTemplate->setParam('{{body}}', $body, escape: false); + $bodyTemplate->setParam('{{body}}', $body, escapeHtml: false); foreach ($variables as $key => $value) { // TODO: hotfix for redirect param - $bodyTemplate->setParam('{{' . $key . '}}', $value, escape: $key !== 'redirect'); + $bodyTemplate->setParam('{{' . $key . '}}', $value, escapeHtml: $key !== 'redirect'); } foreach ($this->richTextParams as $key => $value) { - $bodyTemplate->setParam('{{' . $key . '}}', $value, escape: false); + $bodyTemplate->setParam('{{' . $key . '}}', $value, escapeHtml: false); } $body = $bodyTemplate->render(); diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index ddb72803e3..510fec0431 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -178,7 +178,7 @@ class Messaging extends Action Query::equal('type', [$providerType]), ]); - if ($default->isEmpty()) { + if ($default === false || $default->isEmpty()) { $dbForProject->updateDocument('messages', $message->getId(), $message->setAttributes([ 'status' => MessageStatus::FAILED, 'deliveryErrors' => ['No enabled provider found.'] @@ -275,7 +275,7 @@ class Messaging extends Action Query::equal('identifier', [$result['recipient']]) ]); - if (!$target->isEmpty()) { + if ($target instanceof Document && !$target->isEmpty()) { $dbForProject->updateDocument( 'targets', $target->getId(), diff --git a/src/Appwrite/Promises/Promise.php b/src/Appwrite/Promises/Promise.php index f12590dfed..a6b1aa79d5 100644 --- a/src/Appwrite/Promises/Promise.php +++ b/src/Appwrite/Promises/Promise.php @@ -12,7 +12,7 @@ abstract class Promise private mixed $result; - final public function __construct(?callable $executor = null) + public function __construct(?callable $executor = null) { if (\is_null($executor)) { return; diff --git a/src/Appwrite/Promises/Swoole.php b/src/Appwrite/Promises/Swoole.php index 8cddd567f3..c258ef6a5e 100644 --- a/src/Appwrite/Promises/Swoole.php +++ b/src/Appwrite/Promises/Swoole.php @@ -6,16 +6,23 @@ use Swoole\Coroutine\Channel; class Swoole extends Promise { + public function __construct(?callable $executor = null) + { + parent::__construct($executor); + } + protected function execute( callable $executor, callable $resolve, callable $reject ): void { - try { - $executor($resolve, $reject); - } catch (\Throwable $exception) { - $reject($exception); - } + \go(function () use ($executor, $resolve, $reject) { + try { + $executor($resolve, $reject); + } catch (\Throwable $exception) { + $reject($exception); + } + }); } /** diff --git a/src/Appwrite/Specification/Format.php b/src/Appwrite/Specification/Format.php index e2048971be..30ce6470e1 100644 --- a/src/Appwrite/Specification/Format.php +++ b/src/Appwrite/Specification/Format.php @@ -3,13 +3,13 @@ namespace Appwrite\Specification; use Appwrite\Utopia\Response\Model; +use Utopia\App; use Utopia\Config\Config; -use Utopia\Http\Http; -use Utopia\Http\Route; +use Utopia\Route; abstract class Format { - protected Http $http; + protected App $app; /** * @var Route[] @@ -50,9 +50,9 @@ abstract class Format ] ]; - public function __construct(Http $http, array $services, array $routes, array $models, array $keys, int $authCount) + public function __construct(App $app, array $services, array $routes, array $models, array $keys, int $authCount) { - $this->http = $http; + $this->app = $app; $this->services = $services; $this->routes = $routes; $this->models = $models; diff --git a/src/Appwrite/Specification/Format/OpenAPI3.php b/src/Appwrite/Specification/Format/OpenAPI3.php index 46a2552fd8..f7430ec70e 100644 --- a/src/Appwrite/Specification/Format/OpenAPI3.php +++ b/src/Appwrite/Specification/Format/OpenAPI3.php @@ -7,11 +7,11 @@ use Appwrite\Template\Template; use Appwrite\Utopia\Response\Model; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; -use Utopia\Http\Validator; -use Utopia\Http\Validator\ArrayList; -use Utopia\Http\Validator\Nullable; -use Utopia\Http\Validator\Range; -use Utopia\Http\Validator\WhiteList; +use Utopia\Validator; +use Utopia\Validator\ArrayList; +use Utopia\Validator\Nullable; +use Utopia\Validator\Range; +use Utopia\Validator\WhiteList; class OpenAPI3 extends Format { @@ -238,11 +238,8 @@ class OpenAPI3 extends Format } if ($route->getLabel('sdk.response.code', 500) === 204) { - $labelCode = (string)$route->getLabel('sdk.response.code', '500'); - $temp['responses'][$labelCode]['description'] = 'No content'; - if (isset($temp['responses'][$labelCode]['schema'])) { - unset($temp['responses'][$labelCode]['schema']); - } + $temp['responses'][(string)$route->getLabel('sdk.response.code', '500')]['description'] = 'No content'; + unset($temp['responses'][(string)$route->getLabel('sdk.response.code', '500')]['schema']); } if ((!empty($scope))) { // && 'public' != $scope @@ -272,10 +269,10 @@ class OpenAPI3 extends Format $bodyRequired = []; foreach ($route->getParams() as $name => $param) { // Set params - $injections = array_map(fn ($injection) => $this->http->getContainer()->get($injection), $param['injections'] ?? []); - - /** @var Validator $validator */ - $validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $injections) : $param['validator']; + /** + * @var \Utopia\Validator $validator + */ + $validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $this->app->getResources($param['injections'])) : $param['validator']; $node = [ 'name' => $name, @@ -292,11 +289,11 @@ class OpenAPI3 extends Format switch ((!empty($validator)) ? \get_class($validator) : '') { case 'Utopia\Database\Validator\UID': - case 'Utopia\Http\Validator\Text': + case 'Utopia\Validator\Text': $node['schema']['type'] = $validator->getType(); $node['schema']['x-example'] = '<' . \strtoupper(Template::fromCamelCaseToSnake($node['name'])) . '>'; break; - case 'Utopia\Http\Validator\Boolean': + case 'Utopia\Validator\Boolean': $node['schema']['type'] = $validator->getType(); $node['schema']['x-example'] = false; break; @@ -317,15 +314,15 @@ class OpenAPI3 extends Format $node['schema']['format'] = 'email'; $node['schema']['x-example'] = 'email@example.com'; break; - case 'Utopia\Http\Validator\Host': - case 'Utopia\Http\Validator\URL': + case 'Utopia\Validator\Host': + case 'Utopia\Validator\URL': $node['schema']['type'] = $validator->getType(); $node['schema']['format'] = 'url'; $node['schema']['x-example'] = 'https://example.com'; break; - case 'Utopia\Http\Validator\JSON': - case 'Utopia\Http\Validator\Mock': - case 'Utopia\Http\Validator\Assoc': + case 'Utopia\Validator\JSON': + case 'Utopia\Validator\Mock': + case 'Utopia\Validator\Assoc': $param['default'] = (empty($param['default'])) ? new \stdClass() : $param['default']; $node['schema']['type'] = 'object'; $node['schema']['x-example'] = '{}'; @@ -335,7 +332,7 @@ class OpenAPI3 extends Format $node['schema']['type'] = $validator->getType(); $node['schema']['format'] = 'binary'; break; - case 'Utopia\Http\Validator\ArrayList': + case 'Utopia\Validator\ArrayList': /** @var ArrayList $validator */ $node['schema']['type'] = 'array'; $node['schema']['items'] = [ @@ -397,25 +394,25 @@ class OpenAPI3 extends Format $node['schema']['format'] = 'phone'; $node['schema']['x-example'] = '+12065550100'; // In the US, 555 is reserved like example.com break; - case 'Utopia\Http\Validator\Range': + case 'Utopia\Validator\Range': /** @var Range $validator */ $node['schema']['type'] = $validator->getType() === Validator::TYPE_FLOAT ? 'number' : $validator->getType(); $node['schema']['format'] = $validator->getType() == Validator::TYPE_INTEGER ? 'int32' : 'float'; $node['schema']['x-example'] = $validator->getMin(); break; - case 'Utopia\Http\Validator\Numeric': - case 'Utopia\Http\Validator\Integer': + case 'Utopia\Validator\Numeric': + case 'Utopia\Validator\Integer': $node['schema']['type'] = $validator->getType(); $node['schema']['format'] = 'int32'; break; - case 'Utopia\Http\Validator\FloatValidator': + case 'Utopia\Validator\FloatValidator': $node['schema']['type'] = 'number'; $node['schema']['format'] = 'float'; break; - case 'Utopia\Http\Validator\Length': + case 'Utopia\Validator\Length': $node['schema']['type'] = $validator->getType(); break; - case 'Utopia\Http\Validator\WhiteList': + case 'Utopia\Validator\WhiteList': /** @var WhiteList $validator */ $node['schema']['type'] = $validator->getType(); $node['schema']['x-example'] = $validator->getList()[0]; diff --git a/src/Appwrite/Specification/Format/Swagger2.php b/src/Appwrite/Specification/Format/Swagger2.php index a12e9a4ed6..f2e324c71f 100644 --- a/src/Appwrite/Specification/Format/Swagger2.php +++ b/src/Appwrite/Specification/Format/Swagger2.php @@ -7,10 +7,10 @@ use Appwrite\Template\Template; use Appwrite\Utopia\Response\Model; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; -use Utopia\Http\Validator; -use Utopia\Http\Validator\ArrayList; -use Utopia\Http\Validator\Nullable; -use Utopia\Http\Validator\Range; +use Utopia\Validator; +use Utopia\Validator\ArrayList; +use Utopia\Validator\Nullable; +use Utopia\Validator\Range; class Swagger2 extends Format { @@ -270,10 +270,8 @@ class Swagger2 extends Format ); foreach ($parameters as $name => $param) { // Set params - $injections = array_map(fn ($injection) => $this->http->getContainer()->get($injection), $param['injections'] ?? []); - /** @var Validator $validator */ - $validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $injections) : $param['validator']; + $validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $this->app->getResources($param['injections'])) : $param['validator']; $node = [ 'name' => $name, @@ -308,12 +306,12 @@ class Swagger2 extends Format } switch ($class) { - case 'Utopia\Http\Validator\Text': + case 'Utopia\Validator\Text': case 'Utopia\Database\Validator\UID': $node['type'] = $validator->getType(); $node['x-example'] = '<' . \strtoupper(Template::fromCamelCaseToSnake($node['name'])) . '>'; break; - case 'Utopia\Http\Validator\Boolean': + case 'Utopia\Validator\Boolean': $node['type'] = $validator->getType(); $node['x-example'] = false; break; @@ -334,13 +332,13 @@ class Swagger2 extends Format $node['format'] = 'email'; $node['x-example'] = 'email@example.com'; break; - case 'Utopia\Http\Validator\Host': - case 'Utopia\Http\Validator\URL': + case 'Utopia\Validator\Host': + case 'Utopia\Validator\URL': $node['type'] = $validator->getType(); $node['format'] = 'url'; $node['x-example'] = 'https://example.com'; break; - case 'Utopia\Http\Validator\ArrayList': + case 'Utopia\Validator\ArrayList': /** @var ArrayList $validator */ $node['type'] = 'array'; $node['collectionFormat'] = 'multi'; @@ -348,9 +346,9 @@ class Swagger2 extends Format 'type' => $validator->getValidator()->getType(), ]; break; - case 'Utopia\Http\Validator\JSON': - case 'Utopia\Http\Validator\Mock': - case 'Utopia\Http\Validator\Assoc': + case 'Utopia\Validator\JSON': + case 'Utopia\Validator\Mock': + case 'Utopia\Validator\Assoc': $node['type'] = 'object'; $node['default'] = (empty($param['default'])) ? new \stdClass() : $param['default']; $node['x-example'] = '{}'; @@ -399,26 +397,26 @@ class Swagger2 extends Format $node['format'] = 'phone'; $node['x-example'] = '+12065550100'; break; - case 'Utopia\Http\Validator\Range': + case 'Utopia\Validator\Range': /** @var Range $validator */ $node['type'] = $validator->getType() === Validator::TYPE_FLOAT ? 'number' : $validator->getType(); $node['format'] = $validator->getType() == Validator::TYPE_INTEGER ? 'int32' : 'float'; $node['x-example'] = $validator->getMin(); break; - case 'Utopia\Http\Validator\Numeric': - case 'Utopia\Http\Validator\Integer': + case 'Utopia\Validator\Numeric': + case 'Utopia\Validator\Integer': $node['type'] = $validator->getType(); $node['format'] = 'int32'; break; - case 'Utopia\Http\Validator\FloatValidator': + case 'Utopia\Validator\FloatValidator': $node['type'] = 'number'; $node['format'] = 'float'; break; - case 'Utopia\Http\Validator\Length': + case 'Utopia\Validator\Length': $node['type'] = $validator->getType(); break; - case 'Utopia\Http\Validator\WhiteList': - /** @var \Utopia\Http\Validator\WhiteList $validator */ + case 'Utopia\Validator\WhiteList': + /** @var \Utopia\Validator\WhiteList $validator */ $node['type'] = $validator->getType(); $node['x-example'] = $validator->getList()[0]; diff --git a/src/Appwrite/Task/Validator/Cron.php b/src/Appwrite/Task/Validator/Cron.php index afa19c50a6..03bd1c5220 100644 --- a/src/Appwrite/Task/Validator/Cron.php +++ b/src/Appwrite/Task/Validator/Cron.php @@ -3,7 +3,7 @@ namespace Appwrite\Task\Validator; use Cron\CronExpression; -use Utopia\Http\Validator; +use Utopia\Validator; class Cron extends Validator { diff --git a/src/Appwrite/Utopia/Database/Validator/CompoundUID.php b/src/Appwrite/Utopia/Database/Validator/CompoundUID.php index b851d8ba85..3f23500952 100644 --- a/src/Appwrite/Utopia/Database/Validator/CompoundUID.php +++ b/src/Appwrite/Utopia/Database/Validator/CompoundUID.php @@ -3,7 +3,7 @@ namespace Appwrite\Utopia\Database\Validator; use Utopia\Database\Validator\UID; -use Utopia\Http\Validator; +use Utopia\Validator; class CompoundUID extends Validator { diff --git a/src/Appwrite/Utopia/Database/Validator/ProjectId.php b/src/Appwrite/Utopia/Database/Validator/ProjectId.php index 28abe176fe..46b0cdf53e 100644 --- a/src/Appwrite/Utopia/Database/Validator/ProjectId.php +++ b/src/Appwrite/Utopia/Database/Validator/ProjectId.php @@ -2,7 +2,7 @@ namespace Appwrite\Utopia\Database\Validator; -use Utopia\Http\Validator; +use Utopia\Validator; class ProjectId extends Validator { diff --git a/src/Appwrite/Utopia/Queue/Connections.php b/src/Appwrite/Utopia/Queue/Connections.php deleted file mode 100644 index e873373566..0000000000 --- a/src/Appwrite/Utopia/Queue/Connections.php +++ /dev/null @@ -1,36 +0,0 @@ -connections[] = ['connection' => $connection, 'pool' => $pool]; - return $this; - } - - /** - * @return self - */ - public function reclaim(): self - { - foreach ($this->connections as $id => $resource) { - $pool = $resource['pool']; - $connection = $resource['connection']; - $pool->put($connection); - unset($this->connections[$id]); - } - - return $this; - } -} diff --git a/src/Appwrite/Utopia/Request.php b/src/Appwrite/Utopia/Request.php index 939a95b022..26c1baf188 100644 --- a/src/Appwrite/Utopia/Request.php +++ b/src/Appwrite/Utopia/Request.php @@ -3,10 +3,11 @@ namespace Appwrite\Utopia; use Appwrite\Utopia\Request\Filter; -use Utopia\Http\Adapter\Swoole\Request as HttpRequest; -use Utopia\Http\Route; +use Swoole\Http\Request as SwooleRequest; +use Utopia\Route; +use Utopia\Swoole\Request as UtopiaRequest; -class Request extends HttpRequest +class Request extends UtopiaRequest { /** * @var array @@ -14,12 +15,9 @@ class Request extends HttpRequest private array $filters = []; private static ?Route $route = null; - /** - * Request constructor. - */ - public function __construct(HttpRequest $request) + public function __construct(SwooleRequest $request) { - parent::__construct($request->swoole); + parent::__construct($request); } /** @@ -115,16 +113,6 @@ class Request extends HttpRequest return self::$route !== null; } - - public function removeHeader(string $key): static - { - if (isset($this->headers[$key])) { - unset($this->headers[$key]); - } - - return parent::removeHeader($key); - } - /** * Get headers * @@ -134,18 +122,14 @@ class Request extends HttpRequest */ public function getHeaders(): array { - if ($this->headers !== null) { - return $this->headers; - } - try { - $this->headers = $this->generateHeaders(); + $headers = $this->generateHeaders(); } catch (\Throwable) { - $this->headers = []; + $headers = []; } if (empty($this->swoole->cookie)) { - return $this->headers; + return $headers; } $cookieHeaders = []; @@ -154,10 +138,10 @@ class Request extends HttpRequest } if (!empty($cookieHeaders)) { - $this->headers['cookie'] = \implode('; ', $cookieHeaders); + $headers['cookie'] = \implode('; ', $cookieHeaders); } - return $this->headers; + return $headers; } /** diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index 376c327b37..6cc2639f51 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -4,20 +4,122 @@ namespace Appwrite\Utopia; use Appwrite\Utopia\Fetch\BodyMultipart; use Appwrite\Utopia\Response\Filter; -use Appwrite\Utopia\Response\Models; +use Appwrite\Utopia\Response\Model; +use Appwrite\Utopia\Response\Model\Account; +use Appwrite\Utopia\Response\Model\AlgoArgon2; +use Appwrite\Utopia\Response\Model\AlgoBcrypt; +use Appwrite\Utopia\Response\Model\AlgoMd5; +use Appwrite\Utopia\Response\Model\AlgoPhpass; +use Appwrite\Utopia\Response\Model\AlgoScrypt; +use Appwrite\Utopia\Response\Model\AlgoScryptModified; +use Appwrite\Utopia\Response\Model\AlgoSha; +use Appwrite\Utopia\Response\Model\Any; +use Appwrite\Utopia\Response\Model\Attribute; +use Appwrite\Utopia\Response\Model\AttributeBoolean; +use Appwrite\Utopia\Response\Model\AttributeDatetime; +use Appwrite\Utopia\Response\Model\AttributeEmail; +use Appwrite\Utopia\Response\Model\AttributeEnum; +use Appwrite\Utopia\Response\Model\AttributeFloat; +use Appwrite\Utopia\Response\Model\AttributeInteger; +use Appwrite\Utopia\Response\Model\AttributeIP; +use Appwrite\Utopia\Response\Model\AttributeList; +use Appwrite\Utopia\Response\Model\AttributeRelationship; +use Appwrite\Utopia\Response\Model\AttributeString; +use Appwrite\Utopia\Response\Model\AttributeURL; +use Appwrite\Utopia\Response\Model\AuthProvider; +use Appwrite\Utopia\Response\Model\BaseList; +use Appwrite\Utopia\Response\Model\Branch; +use Appwrite\Utopia\Response\Model\Bucket; +use Appwrite\Utopia\Response\Model\Build; +use Appwrite\Utopia\Response\Model\Collection; +use Appwrite\Utopia\Response\Model\ConsoleVariables; +use Appwrite\Utopia\Response\Model\Continent; +use Appwrite\Utopia\Response\Model\Country; +use Appwrite\Utopia\Response\Model\Currency; +use Appwrite\Utopia\Response\Model\Database; +use Appwrite\Utopia\Response\Model\Deployment; +use Appwrite\Utopia\Response\Model\Detection; +use Appwrite\Utopia\Response\Model\Document as ModelDocument; +use Appwrite\Utopia\Response\Model\Error; +use Appwrite\Utopia\Response\Model\ErrorDev; +use Appwrite\Utopia\Response\Model\Execution; +use Appwrite\Utopia\Response\Model\File; +use Appwrite\Utopia\Response\Model\Func; +use Appwrite\Utopia\Response\Model\Headers; +use Appwrite\Utopia\Response\Model\HealthAntivirus; +use Appwrite\Utopia\Response\Model\HealthCertificate; +use Appwrite\Utopia\Response\Model\HealthQueue; +use Appwrite\Utopia\Response\Model\HealthStatus; +use Appwrite\Utopia\Response\Model\HealthTime; +use Appwrite\Utopia\Response\Model\HealthVersion; +use Appwrite\Utopia\Response\Model\Identity; +use Appwrite\Utopia\Response\Model\Index; +use Appwrite\Utopia\Response\Model\Installation; +use Appwrite\Utopia\Response\Model\JWT; +use Appwrite\Utopia\Response\Model\Key; +use Appwrite\Utopia\Response\Model\Language; +use Appwrite\Utopia\Response\Model\Locale; +use Appwrite\Utopia\Response\Model\LocaleCode; +use Appwrite\Utopia\Response\Model\Log; +use Appwrite\Utopia\Response\Model\Membership; +use Appwrite\Utopia\Response\Model\Message; +use Appwrite\Utopia\Response\Model\Metric; +use Appwrite\Utopia\Response\Model\MetricBreakdown; +use Appwrite\Utopia\Response\Model\MFAChallenge; +use Appwrite\Utopia\Response\Model\MFAFactors; +use Appwrite\Utopia\Response\Model\MFARecoveryCodes; +use Appwrite\Utopia\Response\Model\MFAType; +use Appwrite\Utopia\Response\Model\Migration; +use Appwrite\Utopia\Response\Model\MigrationFirebaseProject; +use Appwrite\Utopia\Response\Model\MigrationReport; +use Appwrite\Utopia\Response\Model\Mock; +use Appwrite\Utopia\Response\Model\MockNumber; +use Appwrite\Utopia\Response\Model\None; +use Appwrite\Utopia\Response\Model\Phone; +use Appwrite\Utopia\Response\Model\Platform; +use Appwrite\Utopia\Response\Model\Preferences; +use Appwrite\Utopia\Response\Model\Project; +use Appwrite\Utopia\Response\Model\Provider; +use Appwrite\Utopia\Response\Model\ProviderRepository; +use Appwrite\Utopia\Response\Model\Rule; +use Appwrite\Utopia\Response\Model\Runtime; +use Appwrite\Utopia\Response\Model\Session; +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\TemplateFunction; +use Appwrite\Utopia\Response\Model\TemplateRuntime; +use Appwrite\Utopia\Response\Model\TemplateSMS; +use Appwrite\Utopia\Response\Model\TemplateVariable; +use Appwrite\Utopia\Response\Model\Token; +use Appwrite\Utopia\Response\Model\Topic; +use Appwrite\Utopia\Response\Model\UsageBuckets; +use Appwrite\Utopia\Response\Model\UsageCollection; +use Appwrite\Utopia\Response\Model\UsageDatabase; +use Appwrite\Utopia\Response\Model\UsageDatabases; +use Appwrite\Utopia\Response\Model\UsageFunction; +use Appwrite\Utopia\Response\Model\UsageFunctions; +use Appwrite\Utopia\Response\Model\UsageProject; +use Appwrite\Utopia\Response\Model\UsageStorage; +use Appwrite\Utopia\Response\Model\UsageUsers; +use Appwrite\Utopia\Response\Model\User; +use Appwrite\Utopia\Response\Model\Variable; +use Appwrite\Utopia\Response\Model\VcsContent; +use Appwrite\Utopia\Response\Model\Webhook; use Exception; use JsonException; +use Swoole\Http\Response as SwooleHTTPResponse; // Keep last use Utopia\Database\Document; -use Utopia\Http\Adapter\Swoole\Response as HttpResponse; - -// Keep last +use Utopia\Swoole\Response as SwooleResponse; /** * @method int getStatusCode() * @method Response setStatusCode(int $code = 200) */ -class Response extends HttpResponse +class Response extends SwooleResponse { // General public const MODEL_NONE = 'none'; @@ -228,9 +330,162 @@ class Response extends HttpResponse * * @param float $time */ - public function __construct(HttpResponse $response) + public function __construct(SwooleHTTPResponse $response) { - parent::__construct($response->swoole); + $this + // General + ->setModel(new None()) + ->setModel(new Any()) + ->setModel(new Error()) + ->setModel(new ErrorDev()) + // Lists + ->setModel(new BaseList('Documents List', self::MODEL_DOCUMENT_LIST, 'documents', self::MODEL_DOCUMENT)) + ->setModel(new BaseList('Collections List', self::MODEL_COLLECTION_LIST, 'collections', self::MODEL_COLLECTION)) + ->setModel(new BaseList('Databases List', self::MODEL_DATABASE_LIST, 'databases', self::MODEL_DATABASE)) + ->setModel(new BaseList('Indexes List', self::MODEL_INDEX_LIST, 'indexes', self::MODEL_INDEX)) + ->setModel(new BaseList('Users List', self::MODEL_USER_LIST, 'users', self::MODEL_USER)) + ->setModel(new BaseList('Sessions List', self::MODEL_SESSION_LIST, 'sessions', self::MODEL_SESSION)) + ->setModel(new BaseList('Identities List', self::MODEL_IDENTITY_LIST, 'identities', self::MODEL_IDENTITY)) + ->setModel(new BaseList('Logs List', self::MODEL_LOG_LIST, 'logs', self::MODEL_LOG)) + ->setModel(new BaseList('Files List', self::MODEL_FILE_LIST, 'files', self::MODEL_FILE)) + ->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('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('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)) + ->setModel(new BaseList('Builds List', self::MODEL_BUILD_LIST, 'builds', self::MODEL_BUILD)) // Not used anywhere yet + ->setModel(new BaseList('Projects List', self::MODEL_PROJECT_LIST, 'projects', self::MODEL_PROJECT, true, false)) + ->setModel(new BaseList('Webhooks List', self::MODEL_WEBHOOK_LIST, 'webhooks', self::MODEL_WEBHOOK, true, false)) + ->setModel(new BaseList('API Keys List', self::MODEL_KEY_LIST, 'keys', self::MODEL_KEY, true, false)) + ->setModel(new BaseList('Auth Providers List', self::MODEL_AUTH_PROVIDER_LIST, 'platforms', self::MODEL_AUTH_PROVIDER, true, false)) + ->setModel(new BaseList('Platforms List', self::MODEL_PLATFORM_LIST, 'platforms', self::MODEL_PLATFORM, true, false)) + ->setModel(new BaseList('Countries List', self::MODEL_COUNTRY_LIST, 'countries', self::MODEL_COUNTRY)) + ->setModel(new BaseList('Continents List', self::MODEL_CONTINENT_LIST, 'continents', self::MODEL_CONTINENT)) + ->setModel(new BaseList('Languages List', self::MODEL_LANGUAGE_LIST, 'languages', self::MODEL_LANGUAGE)) + ->setModel(new BaseList('Currencies List', self::MODEL_CURRENCY_LIST, 'currencies', self::MODEL_CURRENCY)) + ->setModel(new BaseList('Phones List', self::MODEL_PHONE_LIST, 'phones', self::MODEL_PHONE)) + ->setModel(new BaseList('Metric List', self::MODEL_METRIC_LIST, 'metrics', self::MODEL_METRIC, true, false)) + ->setModel(new BaseList('Variables List', self::MODEL_VARIABLE_LIST, 'variables', self::MODEL_VARIABLE)) + ->setModel(new BaseList('Status List', self::MODEL_HEALTH_STATUS_LIST, 'statuses', self::MODEL_HEALTH_STATUS)) + ->setModel(new BaseList('Rule List', self::MODEL_PROXY_RULE_LIST, 'rules', self::MODEL_PROXY_RULE)) + ->setModel(new BaseList('Locale codes list', self::MODEL_LOCALE_CODE_LIST, 'localeCodes', self::MODEL_LOCALE_CODE)) + ->setModel(new BaseList('Provider list', self::MODEL_PROVIDER_LIST, 'providers', self::MODEL_PROVIDER)) + ->setModel(new BaseList('Message list', self::MODEL_MESSAGE_LIST, 'messages', self::MODEL_MESSAGE)) + ->setModel(new BaseList('Topic list', self::MODEL_TOPIC_LIST, 'topics', self::MODEL_TOPIC)) + ->setModel(new BaseList('Subscriber list', self::MODEL_SUBSCRIBER_LIST, 'subscribers', self::MODEL_SUBSCRIBER)) + ->setModel(new BaseList('Target list', self::MODEL_TARGET_LIST, 'targets', self::MODEL_TARGET)) + ->setModel(new BaseList('Migrations List', self::MODEL_MIGRATION_LIST, 'migrations', self::MODEL_MIGRATION)) + ->setModel(new BaseList('Migrations Firebase Projects List', self::MODEL_MIGRATION_FIREBASE_PROJECT_LIST, 'projects', self::MODEL_MIGRATION_FIREBASE_PROJECT)) + ->setModel(new BaseList('Specifications List', self::MODEL_SPECIFICATION_LIST, 'specifications', self::MODEL_SPECIFICATION)) + ->setModel(new BaseList('VCS Content List', self::MODEL_VCS_CONTENT_LIST, 'contents', self::MODEL_VCS_CONTENT)) + // Entities + ->setModel(new Database()) + ->setModel(new Collection()) + ->setModel(new Attribute()) + ->setModel(new AttributeList()) + ->setModel(new AttributeString()) + ->setModel(new AttributeInteger()) + ->setModel(new AttributeFloat()) + ->setModel(new AttributeBoolean()) + ->setModel(new AttributeEmail()) + ->setModel(new AttributeEnum()) + ->setModel(new AttributeIP()) + ->setModel(new AttributeURL()) + ->setModel(new AttributeDatetime()) + ->setModel(new AttributeRelationship()) + ->setModel(new Index()) + ->setModel(new ModelDocument()) + ->setModel(new Log()) + ->setModel(new User()) + ->setModel(new AlgoMd5()) + ->setModel(new AlgoSha()) + ->setModel(new AlgoPhpass()) + ->setModel(new AlgoBcrypt()) + ->setModel(new AlgoScrypt()) + ->setModel(new AlgoScryptModified()) + ->setModel(new AlgoArgon2()) + ->setModel(new Account()) + ->setModel(new Preferences()) + ->setModel(new Session()) + ->setModel(new Identity()) + ->setModel(new Token()) + ->setModel(new JWT()) + ->setModel(new Locale()) + ->setModel(new LocaleCode()) + ->setModel(new File()) + ->setModel(new Bucket()) + ->setModel(new Team()) + ->setModel(new Membership()) + ->setModel(new Func()) + ->setModel(new TemplateFunction()) + ->setModel(new TemplateRuntime()) + ->setModel(new TemplateVariable()) + ->setModel(new Installation()) + ->setModel(new ProviderRepository()) + ->setModel(new Detection()) + ->setModel(new VcsContent()) + ->setModel(new Branch()) + ->setModel(new Runtime()) + ->setModel(new Deployment()) + ->setModel(new Execution()) + ->setModel(new Build()) + ->setModel(new Project()) + ->setModel(new Webhook()) + ->setModel(new Key()) + ->setModel(new MockNumber()) + ->setModel(new AuthProvider()) + ->setModel(new Platform()) + ->setModel(new Variable()) + ->setModel(new Country()) + ->setModel(new Continent()) + ->setModel(new Language()) + ->setModel(new Currency()) + ->setModel(new Phone()) + ->setModel(new HealthAntivirus()) + ->setModel(new HealthQueue()) + ->setModel(new HealthStatus()) + ->setModel(new HealthCertificate()) + ->setModel(new HealthTime()) + ->setModel(new HealthVersion()) + ->setModel(new Metric()) + ->setModel(new MetricBreakdown()) + ->setModel(new UsageDatabases()) + ->setModel(new UsageDatabase()) + ->setModel(new UsageCollection()) + ->setModel(new UsageUsers()) + ->setModel(new UsageStorage()) + ->setModel(new UsageBuckets()) + ->setModel(new UsageFunctions()) + ->setModel(new UsageFunction()) + ->setModel(new UsageProject()) + ->setModel(new Headers()) + ->setModel(new Specification()) + ->setModel(new Rule()) + ->setModel(new TemplateSMS()) + ->setModel(new TemplateEmail()) + ->setModel(new ConsoleVariables()) + ->setModel(new MFAChallenge()) + ->setModel(new MFARecoveryCodes()) + ->setModel(new MFAType()) + ->setModel(new MFAFactors()) + ->setModel(new Provider()) + ->setModel(new Message()) + ->setModel(new Topic()) + ->setModel(new Subscriber()) + ->setModel(new Target()) + ->setModel(new Migration()) + ->setModel(new MigrationReport()) + ->setModel(new MigrationFirebaseProject()) + // Tests (keep last) + ->setModel(new Mock()); + + parent::__construct($response); } /** @@ -240,6 +495,49 @@ class Response extends HttpResponse public const CONTENT_TYPE_NULL = 'null'; public const CONTENT_TYPE_MULTIPART = 'multipart/form-data'; + /** + * List of defined output objects + */ + protected $models = []; + + /** + * Set Model Object + * + * @return self + */ + public function setModel(Model $instance) + { + $this->models[$instance->getType()] = $instance; + + return $this; + } + + /** + * Get Model Object + * + * @param string $key + * @return Model + * @throws Exception + */ + public function getModel(string $key): Model + { + if (!isset($this->models[$key])) { + throw new Exception('Undefined model: ' . $key); + } + + return $this->models[$key]; + } + + /** + * Get Models List + * + * @return Model[] + */ + public function getModels(): array + { + return $this->models; + } + public function applyFilters(array $data, string $model): array { foreach ($this->filters as $filter) { @@ -307,7 +605,7 @@ class Response extends HttpResponse public function output(Document $document, string $model): array { $data = clone $document; - $model = Models::getModel($model); + $model = $this->getModel($model); $output = []; $data = $model->filter($data); @@ -342,7 +640,7 @@ class Response extends HttpResponse if (\is_array($rule['type'])) { foreach ($rule['type'] as $type) { $condition = false; - foreach (Models::getModel($type)->conditions as $attribute => $val) { + foreach ($this->getModel($type)->conditions as $attribute => $val) { $condition = $item->getAttribute($attribute) === $val; if (!$condition) { break; @@ -357,7 +655,7 @@ class Response extends HttpResponse $ruleType = $rule['type']; } - if (!array_key_exists($ruleType, Models::getModels())) { + if (!array_key_exists($ruleType, $this->models)) { throw new Exception('Missing model for rule: ' . $ruleType); } diff --git a/src/Appwrite/Utopia/Response/Models.php b/src/Appwrite/Utopia/Response/Models.php deleted file mode 100644 index 2a0393321c..0000000000 --- a/src/Appwrite/Utopia/Response/Models.php +++ /dev/null @@ -1,304 +0,0 @@ -getType()] = $instance; - } - - /** - * Get Model Object - * - * @param string $key - * @return Model - * @throws Exception - */ - public static function getModel(string $key): Model - { - if (!isset(self::$models[$key])) { - throw new Exception('Undefined model: ' . $key); - } - - return self::$models[$key]; - } - - public static function getModels(): array - { - return self::$models; - } -} diff --git a/src/Appwrite/Utopia/View.php b/src/Appwrite/Utopia/View.php index 60cafb1ca8..e4ed8164c9 100644 --- a/src/Appwrite/Utopia/View.php +++ b/src/Appwrite/Utopia/View.php @@ -2,7 +2,7 @@ namespace Appwrite\Utopia; -use Utopia\View\View as OldView; +use Utopia\View as OldView; class View extends OldView { diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index 28c3227979..04f2dbd8c8 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -12,7 +12,7 @@ use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Datetime as DatetimeValidator; -use Utopia\Http\Validator\JSON; +use Utopia\Validator\JSON; trait DatabasesBase { @@ -2869,7 +2869,7 @@ trait DatabasesBase $this->assertEquals('Invalid document structure: Attribute "floatRange" has invalid format. Value must be a valid range between 1 and 1', $badFloatRange['body']['message']); $this->assertEquals('Invalid document structure: Attribute "probability" has invalid format. Value must be a valid range between 0 and 1', $badProbability['body']['message']); $this->assertEquals('Invalid document structure: Attribute "upperBound" has invalid format. Value must be a valid range between -9,223,372,036,854,775,808 and 10', $tooHigh['body']['message']); - $this->assertEquals('Invalid document structure: Attribute "lowerBound" has invalid format. Value must be a valid range between 5 and '.\number_format(PHP_INT_MAX), $tooLow['body']['message']); + $this->assertEquals('Invalid document structure: Attribute "lowerBound" has invalid format. Value must be a valid range between 5 and 9,223,372,036,854,775,807', $tooLow['body']['message']); } /** diff --git a/tests/e2e/Services/Databases/DatabasesCustomClientTest.php b/tests/e2e/Services/Databases/DatabasesCustomClientTest.php index 9b7be063db..8484996058 100644 --- a/tests/e2e/Services/Databases/DatabasesCustomClientTest.php +++ b/tests/e2e/Services/Databases/DatabasesCustomClientTest.php @@ -64,10 +64,9 @@ class DatabasesCustomClientTest extends Scope 'required' => true, ]); - $this->assertEquals(202, $response['headers']['status-code']); - sleep(1); + $this->assertEquals(202, $response['headers']['status-code']); // Document aliases write to update, delete $document1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/documents', array_merge([ @@ -83,7 +82,6 @@ class DatabasesCustomClientTest extends Scope ] ]); - $this->assertEquals(201, $document1['headers']['status-code']); $this->assertNotContains(Permission::create(Role::user($this->getUser()['$id'])), $document1['body']['$permissions']); $this->assertContains(Permission::update(Role::user($this->getUser()['$id'])), $document1['body']['$permissions']); $this->assertContains(Permission::delete(Role::user($this->getUser()['$id'])), $document1['body']['$permissions']); diff --git a/tests/e2e/Services/Databases/DatabasesPermissionsGuestTest.php b/tests/e2e/Services/Databases/DatabasesPermissionsGuestTest.php index 4288b92613..ca8753f374 100644 --- a/tests/e2e/Services/Databases/DatabasesPermissionsGuestTest.php +++ b/tests/e2e/Services/Databases/DatabasesPermissionsGuestTest.php @@ -17,14 +17,6 @@ class DatabasesPermissionsGuestTest extends Scope use SideClient; use DatabasesPermissionsScope; - protected Authorization $auth; - - public function setUp(): void - { - parent::setUp(); - $this->auth = new Authorization(); - } - public function createCollection(): array { $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ @@ -119,8 +111,8 @@ class DatabasesPermissionsGuestTest extends Scope $this->assertEquals(201, $publicResponse['headers']['status-code']); $this->assertEquals(201, $privateResponse['headers']['status-code']); - $roles = $this->auth->getRoles(); - $this->auth->cleanRoles(); + $roles = Authorization::getRoles(); + Authorization::cleanRoles(); $publicDocuments = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $publicCollectionId . '/documents', [ 'content-type' => 'application/json', @@ -142,7 +134,7 @@ class DatabasesPermissionsGuestTest extends Scope } foreach ($roles as $role) { - $this->auth->addRole($role); + Authorization::setRole($role); } } @@ -153,8 +145,8 @@ class DatabasesPermissionsGuestTest extends Scope $privateCollectionId = $data['privateCollectionId']; $databaseId = $data['databaseId']; - $roles = $this->auth->getRoles(); - $this->auth->cleanRoles(); + $roles = Authorization::getRoles(); + Authorization::cleanRoles(); $publicResponse = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $publicCollectionId . '/documents', [ 'content-type' => 'application/json', @@ -230,7 +222,7 @@ class DatabasesPermissionsGuestTest extends Scope $this->assertEquals(401, $privateDocument['headers']['status-code']); foreach ($roles as $role) { - $this->auth->addRole($role); + Authorization::setRole($role); } } diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index cd0fcee6ba..a1bb8f2b21 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -11,7 +11,8 @@ trait FunctionsBase { use Async; - protected string $output = ''; + protected string $stdout = ''; + protected string $stderr = ''; protected function setupFunction(mixed $params): string { @@ -145,7 +146,8 @@ trait FunctionsBase { $folderPath = realpath(__DIR__ . '/../../../resources/functions') . "/$function"; $tarPath = "$folderPath/code.tar.gz"; - Console::execute("cd $folderPath && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->output); + + Console::execute("cd $folderPath && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr); if (filesize($tarPath) > 1024 * 1024 * 5) { throw new \Exception('Code package is too large. Use the chunked upload method instead.'); diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index f605564726..59a3041a3f 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -553,7 +553,7 @@ class FunctionsCustomServerTest extends Scope $folder = 'php-large'; $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->output); + Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr); $chunkSize = 5 * 1024 * 1024; $handle = @fopen($code, "rb"); @@ -1859,11 +1859,10 @@ class FunctionsCustomServerTest extends Scope 'x-appwrite-response-format' => '1.4.0', // Set response format for 1.4 syntax ], $this->getHeaders()), [ - 'queries' => [ - Query::equal('name', ['Test2'])->toString(), - ] + 'queries' => [ 'equal("name", ["Test2"])' ] ] ); + $this->assertEquals(200, $response['headers']['status-code']); $this->assertCount(1, $response['body']['functions']); $this->assertEquals('Test2', $response['body']['functions'][0]['name']); diff --git a/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php b/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php index 4f4b0c960d..b1315103b1 100644 --- a/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php +++ b/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php @@ -1,13 +1,12 @@ stdout); + Console::execute("cd $folderPath && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr); if (filesize($tarPath) > 1024 * 1024 * 5) { throw new \Exception('Code package is too large. Use the chunked upload method instead.'); diff --git a/tests/e2e/Services/GraphQL/StorageClientTest.php b/tests/e2e/Services/GraphQL/StorageClientTest.php index 7d6d617c87..9896598c2d 100644 --- a/tests/e2e/Services/GraphQL/StorageClientTest.php +++ b/tests/e2e/Services/GraphQL/StorageClientTest.php @@ -183,6 +183,7 @@ class StorageClientTest extends Scope /** * @depends testCreateFile * @param $file + * @return array * @throws \Exception */ public function testGetFileDownload($file) diff --git a/tests/e2e/Services/GraphQL/StorageServerTest.php b/tests/e2e/Services/GraphQL/StorageServerTest.php index 6be103629e..7fea895b1c 100644 --- a/tests/e2e/Services/GraphQL/StorageServerTest.php +++ b/tests/e2e/Services/GraphQL/StorageServerTest.php @@ -232,6 +232,7 @@ class StorageServerTest extends Scope /** * @depends testCreateFile * @param $file + * @return array * @throws \Exception */ public function testGetFileDownload($file) diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index c4ef8f9d1c..05893be3a8 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -1691,7 +1691,7 @@ class ProjectsConsoleClientTest extends Scope ]); $this->assertEquals(400, $response['headers']['status-code']); - $this->assertEquals('Invalid `numbers` param: Value must a valid array no longer than 10 items', $response['body']['message']); + $this->assertEquals('Invalid `numbers` param: Value must a valid array no longer than 10 items and Phone number must start with a \'+\' can have a maximum of fifteen digits.', $response['body']['message']); /** * Test for success diff --git a/tests/e2e/Services/Storage/StorageCustomClientTest.php b/tests/e2e/Services/Storage/StorageCustomClientTest.php index 55340ab849..c723fba50a 100644 --- a/tests/e2e/Services/Storage/StorageCustomClientTest.php +++ b/tests/e2e/Services/Storage/StorageCustomClientTest.php @@ -1089,7 +1089,7 @@ class StorageCustomClientTest extends Scope $this->assertEquals(200, $file['headers']['status-code']); - // Team 2 view success + // Team 1 view success $file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/view', [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1112,8 +1112,6 @@ class StorageCustomClientTest extends Scope 'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'permissions.png'), ]); - $this->assertEquals($file['headers']['status-code'], 401); - // Team 2 create failure $file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', [ 'content-type' => 'multipart/form-data', diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php index 54d55e5e68..d2f132e960 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php @@ -486,10 +486,11 @@ class WebhooksCustomServerTest extends Scope /** * Test for SUCCESS */ - $output = ''; + $stderr = ''; + $stdout = ''; $folder = 'timeout'; $code = realpath(__DIR__ . '/../../../resources/functions') . "/{$folder}/code.tar.gz"; - Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/{$folder} && tar --exclude code.tar.gz -czf code.tar.gz .", '', $output); + Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/{$folder} && tar --exclude code.tar.gz -czf code.tar.gz .", '', $stdout, $stderr); $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', diff --git a/tests/resources/docker/docker-compose.yml b/tests/resources/docker/docker-compose.yml index 8f86fb8374..a34b4fcf88 100644 --- a/tests/resources/docker/docker-compose.yml +++ b/tests/resources/docker/docker-compose.yml @@ -324,7 +324,7 @@ services: - MYSQL_USER=user - MYSQL_PASSWORD=password - MARIADB_AUTO_UPGRADE=1 - command: 'mysqld --innodb-flush-method=fsync --max_connections=5000' + command: 'mysqld --innodb-flush-method=fsync' maildev: image: djfarrelly/maildev diff --git a/tests/unit/Auth/AuthTest.php b/tests/unit/Auth/AuthTest.php index 1ff5850402..705da42879 100644 --- a/tests/unit/Auth/AuthTest.php +++ b/tests/unit/Auth/AuthTest.php @@ -3,7 +3,6 @@ namespace Tests\Unit\Auth; use Appwrite\Auth\Auth; -use Appwrite\Auth\Authentication; use PHPUnit\Framework\TestCase; use Utopia\Database\DateTime; use Utopia\Database\Document; @@ -14,31 +13,21 @@ use Utopia\Database\Validator\Roles; class AuthTest extends TestCase { - protected Authorization $auth; - protected Authentication $authentication; - - public function setUp(): void - { - parent::setUp(); - $this->auth = new Authorization(); - $this->authentication = new Authentication(); - } - /** * Reset Roles */ public function tearDown(): void { - $this->auth->cleanRoles(); - $this->auth->addRole(Role::any()->toString()); + Authorization::cleanRoles(); + Authorization::setRole(Role::any()->toString()); } public function testCookieName(): void { $name = 'cookie-name'; - $this->assertEquals($this->authentication->setCookieName($name), $name); - $this->assertEquals($this->authentication->getCookieName(), $name); + $this->assertEquals(Auth::setCookieName($name), $name); + $this->assertEquals(Auth::$cookieName, $name); } public function testEncodeDecodeSession(): void @@ -358,7 +347,7 @@ class AuthTest extends TestCase '$id' => '' ]); - $roles = Auth::getRoles($user, $this->auth); + $roles = Auth::getRoles($user); $this->assertCount(1, $roles); $this->assertContains(Role::guests()->toString(), $roles); } @@ -394,7 +383,7 @@ class AuthTest extends TestCase ] ]); - $roles = Auth::getRoles($user, $this->auth); + $roles = Auth::getRoles($user); $this->assertCount(13, $roles); $this->assertContains(Role::users()->toString(), $roles); @@ -415,21 +404,21 @@ class AuthTest extends TestCase $user['emailVerification'] = false; $user['phoneVerification'] = false; - $roles = Auth::getRoles($user, $this->auth); + $roles = Auth::getRoles($user); $this->assertContains(Role::users(Roles::DIMENSION_UNVERIFIED)->toString(), $roles); $this->assertContains(Role::user(ID::custom('123'), Roles::DIMENSION_UNVERIFIED)->toString(), $roles); // Enable single verification type $user['emailVerification'] = true; - $roles = Auth::getRoles($user, $this->auth); + $roles = Auth::getRoles($user); $this->assertContains(Role::users(Roles::DIMENSION_VERIFIED)->toString(), $roles); $this->assertContains(Role::user(ID::custom('123'), Roles::DIMENSION_VERIFIED)->toString(), $roles); } public function testPrivilegedUserRoles(): void { - $this->auth->addRole(Auth::USER_ROLE_OWNER); + Authorization::setRole(Auth::USER_ROLE_OWNER); $user = new Document([ '$id' => ID::custom('123'), 'emailVerification' => true, @@ -455,7 +444,7 @@ class AuthTest extends TestCase ] ]); - $roles = Auth::getRoles($user, $this->auth); + $roles = Auth::getRoles($user); $this->assertCount(7, $roles); $this->assertNotContains(Role::users()->toString(), $roles); @@ -473,7 +462,7 @@ class AuthTest extends TestCase public function testAppUserRoles(): void { - $this->auth->addRole(Auth::USER_ROLE_APPS); + Authorization::setRole(Auth::USER_ROLE_APPS); $user = new Document([ '$id' => ID::custom('123'), 'memberships' => [ @@ -497,7 +486,7 @@ class AuthTest extends TestCase ] ]); - $roles = Auth::getRoles($user, $this->auth); + $roles = Auth::getRoles($user); $this->assertCount(7, $roles); $this->assertNotContains(Role::users()->toString(), $roles); diff --git a/tests/unit/Event/EventTest.php b/tests/unit/Event/EventTest.php index 38534b9b16..dd9833378f 100644 --- a/tests/unit/Event/EventTest.php +++ b/tests/unit/Event/EventTest.php @@ -66,13 +66,9 @@ class EventTest extends TestCase $this->assertEquals('eventValue1', $this->object->getParam('eventKey1')); $this->assertEquals('eventValue2', $this->object->getParam('eventKey2')); $this->assertEquals(null, $this->object->getParam('eventKey3')); - - global $registry; - $pools = $registry->get('pools'); - $dsn = $pools['pools-queue-queue']['dsn']; - $queue = new Queue\Connection\Redis($dsn->getHost(), $dsn->getPort()); - - $client = new Client($this->object->getQueue(), $queue); + global $register; + $pools = $register->get('pools'); + $client = new Client($this->object->getQueue(), $pools->get('queue')->pop()->getResource()); $this->assertEquals($client->getQueueSize(), 1); } diff --git a/tests/unit/GraphQL/BuilderTest.php b/tests/unit/GraphQL/BuilderTest.php index f11045f318..d79a104c90 100644 --- a/tests/unit/GraphQL/BuilderTest.php +++ b/tests/unit/GraphQL/BuilderTest.php @@ -4,10 +4,8 @@ namespace Tests\Unit\GraphQL; use Appwrite\GraphQL\Types\Mapper; use Appwrite\Utopia\Response; -use Appwrite\Utopia\Response\Models; use PHPUnit\Framework\TestCase; use Swoole\Http\Response as SwooleResponse; -use Utopia\Http\Adapter\Swoole\Response as UtopiaSwooleResponse; class BuilderTest extends TestCase { @@ -15,9 +13,8 @@ class BuilderTest extends TestCase public function setUp(): void { - Models::init(); - $this->response = new Response(new UtopiaSwooleResponse(new SwooleResponse())); - Mapper::init(Models::getModels()); + $this->response = new Response(new SwooleResponse()); + Mapper::init($this->response->getModels()); } /** @@ -25,7 +22,7 @@ class BuilderTest extends TestCase */ public function testCreateTypeMapping() { - $model = Models::getModel(Response::MODEL_COLLECTION); + $model = $this->response->getModel(Response::MODEL_COLLECTION); $type = Mapper::model(\ucfirst($model->getType())); $this->assertEquals('Collection', $type->name); } diff --git a/tests/unit/Messaging/MessagingChannelsTest.php b/tests/unit/Messaging/MessagingChannelsTest.php index 77b3cee7d6..8ba0374093 100644 --- a/tests/unit/Messaging/MessagingChannelsTest.php +++ b/tests/unit/Messaging/MessagingChannelsTest.php @@ -8,7 +8,6 @@ use PHPUnit\Framework\TestCase; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; -use Utopia\Database\Validator\Authorization as ValidatorAuthorization; class MessagingChannelsTest extends TestCase { @@ -35,12 +34,8 @@ class MessagingChannelsTest extends TestCase 'functions.1', ]; - protected ValidatorAuthorization $auth; - public function setUp(): void { - $this->auth = new ValidatorAuthorization(); - /** * Setup global Counts */ @@ -71,7 +66,7 @@ class MessagingChannelsTest extends TestCase ] ]); - $roles = Auth::getRoles($user, $this->auth); + $roles = Auth::getRoles($user); $parsedChannels = Realtime::convertChannels([0 => $channel], $user->getId()); @@ -95,7 +90,7 @@ class MessagingChannelsTest extends TestCase '$id' => '' ]); - $roles = Auth::getRoles($user, $this->auth); + $roles = Auth::getRoles($user); $parsedChannels = Realtime::convertChannels([0 => $channel], $user->getId()); diff --git a/tests/unit/Migration/MigrationTest.php b/tests/unit/Migration/MigrationTest.php index 8043ec9f94..536278d55b 100644 --- a/tests/unit/Migration/MigrationTest.php +++ b/tests/unit/Migration/MigrationTest.php @@ -36,7 +36,7 @@ abstract class MigrationTest extends TestCase */ public function testMigrationVersions(): void { - //require_once __DIR__ . '/../../../app/init.php'; + require_once __DIR__ . '/../../../app/init.php'; foreach (Migration::$versions as $class) { $this->assertTrue(class_exists('Appwrite\\Migration\\Version\\' . $class)); diff --git a/tests/unit/Utopia/RequestTest.php b/tests/unit/Utopia/RequestTest.php index 6124a7a0c1..73daaa88bc 100644 --- a/tests/unit/Utopia/RequestTest.php +++ b/tests/unit/Utopia/RequestTest.php @@ -7,8 +7,7 @@ use PHPUnit\Framework\TestCase; use Swoole\Http\Request as SwooleRequest; use Tests\Unit\Utopia\Request\Filters\First; use Tests\Unit\Utopia\Request\Filters\Second; -use Utopia\Http\Adapter\Swoole\Request as UtopiaSwooleRequest; -use Utopia\Http\Route; +use Utopia\Route; class RequestTest extends TestCase { @@ -16,7 +15,7 @@ class RequestTest extends TestCase public function setUp(): void { - $this->request = new Request(new UtopiaSwooleRequest(new SwooleRequest())); + $this->request = new Request(new SwooleRequest()); } public function testFilters(): void @@ -37,7 +36,7 @@ class RequestTest extends TestCase // set test header to prevent header populaten inside the request class $this->request->addHeader('EXAMPLE', 'VALUE'); $this->request->setRoute($route); - $this->request->setQuery([ + $this->request->setQueryString([ 'initial' => true, 'first' => false ]); diff --git a/tests/unit/Utopia/ResponseTest.php b/tests/unit/Utopia/ResponseTest.php index 67253e48a0..452119fafb 100644 --- a/tests/unit/Utopia/ResponseTest.php +++ b/tests/unit/Utopia/ResponseTest.php @@ -3,14 +3,12 @@ namespace Tests\Unit\Utopia; use Appwrite\Utopia\Response; -use Appwrite\Utopia\Response\Models; use Exception; use PHPUnit\Framework\TestCase; use Swoole\Http\Response as SwooleResponse; use Tests\Unit\Utopia\Response\Filters\First; use Tests\Unit\Utopia\Response\Filters\Second; use Utopia\Database\Document; -use Utopia\Http\Adapter\Swoole\Response as UtopiaSwooleResponse; class ResponseTest extends TestCase { @@ -18,10 +16,10 @@ class ResponseTest extends TestCase public function setUp(): void { - $this->response = new Response(new UtopiaSwooleResponse(new SwooleResponse())); - Models::setModel(new Single()); - Models::setModel(new Lists()); - Models::setModel(new Nested()); + $this->response = new Response(new SwooleResponse()); + $this->response->setModel(new Single()); + $this->response->setModel(new Lists()); + $this->response->setModel(new Nested()); } public function testFilters(): void From c8cbcfefbb708d4d1c2a2c91e361147d61bec895 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 8 Oct 2024 23:01:54 +1300 Subject: [PATCH 037/101] Update database --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index c176d383f3..5174e98e6b 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "utopia-php/cache": "0.10.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.53.5", + "utopia-php/database": "0.53.*", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", diff --git a/composer.lock b/composer.lock index d2c82a65d7..9400119ca7 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "1884e3a2966762c4a955842426b64f6c", + "content-hash": "df0694d5a7ed8bf57e00dd30a4d0d421", "packages": [ { "name": "adhocore/jwt", @@ -1724,16 +1724,16 @@ }, { "name": "utopia-php/database", - "version": "0.53.5", + "version": "0.53.6", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "689ba22063bf46def385da8695ba7a921e81e38d" + "reference": "feddc8e808eaea9a11c65cca3f01683def422f52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/689ba22063bf46def385da8695ba7a921e81e38d", - "reference": "689ba22063bf46def385da8695ba7a921e81e38d", + "url": "https://api.github.com/repos/utopia-php/database/zipball/feddc8e808eaea9a11c65cca3f01683def422f52", + "reference": "feddc8e808eaea9a11c65cca3f01683def422f52", "shasum": "" }, "require": { @@ -1774,9 +1774,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.53.5" + "source": "https://github.com/utopia-php/database/tree/0.53.6" }, - "time": "2024-09-24T08:43:10+00:00" + "time": "2024-10-08T02:18:46+00:00" }, { "name": "utopia-php/domains", From 325c9d61cc1c1274ace8d573fef00360dbf3ae66 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 8 Oct 2024 23:02:55 +1300 Subject: [PATCH 038/101] Lock --- composer.json | 2 +- composer.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 5174e98e6b..50881bec87 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "utopia-php/cache": "0.10.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.53.*", + "utopia-php/database": "0.53.6", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", diff --git a/composer.lock b/composer.lock index 9400119ca7..df1d2c8380 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "df0694d5a7ed8bf57e00dd30a4d0d421", + "content-hash": "a81b2ddbd465059b986947b63435e2bc", "packages": [ { "name": "adhocore/jwt", From a6a67301a415a9f5dd272b603da07fd3af84ab3b Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 8 Oct 2024 23:09:19 +1300 Subject: [PATCH 039/101] Update packages --- composer.lock | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/composer.lock b/composer.lock index df1d2c8380..bba2391d2c 100644 --- a/composer.lock +++ b/composer.lock @@ -1624,16 +1624,16 @@ }, { "name": "utopia-php/cli", - "version": "0.15.0", + "version": "0.15.1", "source": { "type": "git", "url": "https://github.com/utopia-php/cli.git", - "reference": "ccb7c8125ffe0254fef8f25744bfa376eb7bd0ea" + "reference": "d69bbe51a6a94dc4e5bcdd542b5938038b985a65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cli/zipball/ccb7c8125ffe0254fef8f25744bfa376eb7bd0ea", - "reference": "ccb7c8125ffe0254fef8f25744bfa376eb7bd0ea", + "url": "https://api.github.com/repos/utopia-php/cli/zipball/d69bbe51a6a94dc4e5bcdd542b5938038b985a65", + "reference": "d69bbe51a6a94dc4e5bcdd542b5938038b985a65", "shasum": "" }, "require": { @@ -1667,9 +1667,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cli/issues", - "source": "https://github.com/utopia-php/cli/tree/0.15.0" + "source": "https://github.com/utopia-php/cli/tree/0.15.1" }, - "time": "2023-03-01T05:55:14+00:00" + "time": "2024-10-04T13:55:36+00:00" }, { "name": "utopia-php/config", @@ -2175,16 +2175,16 @@ }, { "name": "utopia-php/migration", - "version": "0.6.4", + "version": "0.6.5", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "e43ef283f1386084e11d1ffe093fb6c6d7a6ce6c" + "reference": "7b2d40d526b82e9b92a17ea681b8103222e3c86a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/e43ef283f1386084e11d1ffe093fb6c6d7a6ce6c", - "reference": "e43ef283f1386084e11d1ffe093fb6c6d7a6ce6c", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/7b2d40d526b82e9b92a17ea681b8103222e3c86a", + "reference": "7b2d40d526b82e9b92a17ea681b8103222e3c86a", "shasum": "" }, "require": { @@ -2225,9 +2225,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/0.6.4" + "source": "https://github.com/utopia-php/migration/tree/0.6.5" }, - "time": "2024-10-02T15:16:36+00:00" + "time": "2024-10-07T08:54:05+00:00" }, { "name": "utopia-php/mongo", @@ -3002,16 +3002,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.39.22", + "version": "0.39.23", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "bdbb1607527550e67283ff0533522d1410c2c0df" + "reference": "0acceabb7593c9c07c5db85a84a5ebac60896763" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/bdbb1607527550e67283ff0533522d1410c2c0df", - "reference": "bdbb1607527550e67283ff0533522d1410c2c0df", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/0acceabb7593c9c07c5db85a84a5ebac60896763", + "reference": "0acceabb7593c9c07c5db85a84a5ebac60896763", "shasum": "" }, "require": { @@ -3047,9 +3047,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/0.39.22" + "source": "https://github.com/appwrite/sdk-generator/tree/0.39.23" }, - "time": "2024-10-01T16:16:26+00:00" + "time": "2024-10-08T00:38:57+00:00" }, { "name": "doctrine/annotations", From 5287e70f9ab0c14984636c696f60bb97af7e987b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 8 Oct 2024 16:20:04 +0200 Subject: [PATCH 040/101] Add new runtimes --- app/config/function-templates.php | 11 ++++++++--- composer.json | 2 +- composer.lock | 14 +++++++------- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/app/config/function-templates.php b/app/config/function-templates.php index db26ff2c19..7254c602cd 100644 --- a/app/config/function-templates.php +++ b/app/config/function-templates.php @@ -3,7 +3,7 @@ const TEMPLATE_RUNTIMES = [ 'NODE' => [ 'name' => 'node', - 'versions' => ['21.0', '20.0', '19.0', '18.0', '16.0', '14.5'] + 'versions' => ['22.0', '21.0', '20.0', '19.0', '18.0', '16.0', '14.5'] ], 'PYTHON' => [ 'name' => 'python', @@ -11,7 +11,7 @@ const TEMPLATE_RUNTIMES = [ ], 'DART' => [ 'name' => 'dart', - 'versions' => ['3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16', '2.16'] + 'versions' => ['3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16', '2.16'] ], 'GO' => [ 'name' => 'go', @@ -23,12 +23,16 @@ const TEMPLATE_RUNTIMES = [ ], 'BUN' => [ 'name' => 'bun', - 'versions' => ['1.0'] + 'versions' => ['1.1', '1.0'] ], 'RUBY' => [ 'name' => 'ruby', 'versions' => ['3.3', '3.2', '3.1', '3.0'] ], + 'DENO' => [ + 'name' => 'deno', + 'versions' => ['2.0', '1.46', '1.40', '1.35', '1.24', '1.21'] + ], ]; function getRuntimes($runtime, $commands, $entrypoint, $providerRootDirectory, $versionsDenyList = []) @@ -75,6 +79,7 @@ return [ ), ...getRuntimes(TEMPLATE_RUNTIMES['BUN'], 'bun install', 'src/main.ts', 'bun/starter'), ...getRuntimes(TEMPLATE_RUNTIMES['RUBY'], 'bundle install', 'lib/main.rb', 'ruby/starter'), + ...getRuntimes(TEMPLATE_RUNTIMES['DENO'], 'deno cache src/main.ts', 'src/main.ts', 'deno/starter'), ], 'instructions' => 'For documentation and instructions check out file.', 'vcsProvider' => 'github', diff --git a/composer.json b/composer.json index 50881bec87..e7c7743da7 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,7 @@ "ext-openssl": "*", "ext-zlib": "*", "ext-sockets": "*", - "appwrite/php-runtimes": "0.15.*", + "appwrite/php-runtimes": "0.16.*", "appwrite/php-clamav": "2.0.*", "utopia-php/abuse": "0.43.0", "utopia-php/analytics": "0.10.*", diff --git a/composer.lock b/composer.lock index bba2391d2c..a70e681ccc 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "a81b2ddbd465059b986947b63435e2bc", + "content-hash": "89ebc75f08cd9ee5a5cccd64d0f9938a", "packages": [ { "name": "adhocore/jwt", @@ -157,16 +157,16 @@ }, { "name": "appwrite/php-runtimes", - "version": "0.15.0", + "version": "0.16.0", "source": { "type": "git", "url": "https://github.com/appwrite/runtimes.git", - "reference": "68ea5bcc24c513a6d641ddf9412bbab13e5dfb94" + "reference": "0ccc89e5ed2d2fec757139a50626179b76c579d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/runtimes/zipball/68ea5bcc24c513a6d641ddf9412bbab13e5dfb94", - "reference": "68ea5bcc24c513a6d641ddf9412bbab13e5dfb94", + "url": "https://api.github.com/repos/appwrite/runtimes/zipball/0ccc89e5ed2d2fec757139a50626179b76c579d9", + "reference": "0ccc89e5ed2d2fec757139a50626179b76c579d9", "shasum": "" }, "require": { @@ -206,9 +206,9 @@ ], "support": { "issues": "https://github.com/appwrite/runtimes/issues", - "source": "https://github.com/appwrite/runtimes/tree/0.15.0" + "source": "https://github.com/appwrite/runtimes/tree/0.16.0" }, - "time": "2024-08-21T10:23:45+00:00" + "time": "2024-10-08T12:49:05+00:00" }, { "name": "beberlei/assert", From d38383bf7893abd35392dcf3369bcbcf99cb31bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 8 Oct 2024 18:16:38 +0200 Subject: [PATCH 041/101] Upgrade console SDK version --- app/config/platforms.php | 2 +- src/Appwrite/Utopia/Response/Model/Execution.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/config/platforms.php b/app/config/platforms.php index e7eb1180cd..deac92ddee 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -199,7 +199,7 @@ return [ [ 'key' => 'web', 'name' => 'Console', - 'version' => '1.1.0', + 'version' => '1.2.1', 'url' => 'https://github.com/appwrite/sdk-for-console', 'package' => '', 'enabled' => true, diff --git a/src/Appwrite/Utopia/Response/Model/Execution.php b/src/Appwrite/Utopia/Response/Model/Execution.php index dc5d41c02c..80b65af696 100644 --- a/src/Appwrite/Utopia/Response/Model/Execution.php +++ b/src/Appwrite/Utopia/Response/Model/Execution.php @@ -81,7 +81,7 @@ class Execution extends Model 'example' => 200, ]) ->addRule('responseBody', [ - 'type' => self::TYPE_PAYLOAD, + 'type' => self::TYPE_STRING, 'description' => 'HTTP response body. This will return empty unless execution is created as synchronous.', 'default' => '', ]) From 5524fdb7b68c28c6f2857f279e6ec2ec66bbad85 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 8 Oct 2024 18:48:11 +0100 Subject: [PATCH 042/101] fix: cache-docker --- .github/workflows/tests.yml | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6e7012527d..a733814dfb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,7 +6,6 @@ concurrency: env: IMAGE: appwrite-dev - CACHE_KEY: appwrite-dev-${{ github.event.pull_request.head.sha }} on: [pull_request] @@ -30,8 +29,8 @@ jobs: push: false tags: ${{ env.IMAGE }} load: true - cache-from: type=gha,scope=appwrite - cache-to: type=gha,mode=max,scope=appwrite + cache-from: type=gha,scope=${{ env.CACHE_KEY }} + cache-to: type=gha,mode=max,scope=${{ env.CACHE_KEY }} outputs: type=docker,dest=/tmp/${{ env.IMAGE }}.tar build-args: | DEBUG=false @@ -41,9 +40,7 @@ jobs: - name: Cache Docker Image uses: actions/cache@v4 with: - key: ${{ env.CACHE_KEY }} - restore-keys: | - appwrite-dev- + key: appwrite-dev-${{ hashFiles('**') }} path: /tmp/${{ env.IMAGE }}.tar unit_test: @@ -58,7 +55,7 @@ jobs: - name: Load Cache uses: actions/cache@v4 with: - key: ${{ env.CACHE_KEY }} + key: appwrite-dev-${{ hashFiles('**') }} path: /tmp/${{ env.IMAGE }}.tar fail-on-cache-miss: true @@ -88,7 +85,7 @@ jobs: - name: Load Cache uses: actions/cache@v4 with: - key: ${{ env.CACHE_KEY }} + key: appwrite-dev-${{ hashFiles('**') }} path: /tmp/${{ env.IMAGE }}.tar fail-on-cache-miss: true @@ -136,7 +133,7 @@ jobs: - name: Load Cache uses: actions/cache@v4 with: - key: ${{ env.CACHE_KEY }} + key: appwrite-dev-${{ hashFiles('**') }} path: /tmp/${{ env.IMAGE }}.tar fail-on-cache-miss: true @@ -162,7 +159,7 @@ jobs: - name: Load Cache uses: actions/cache@v4 with: - key: ${{ env.CACHE_KEY }} + key: appwrite-dev-${{ hashFiles('**') }} path: /tmp/${{ env.IMAGE }}.tar fail-on-cache-miss: true - name: Load and Start Appwrite From a63acb7f231399bdf939f8210cbb2bc71ffd7f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 8 Oct 2024 21:11:25 +0200 Subject: [PATCH 043/101] Revert unwanted changes --- src/Appwrite/Utopia/Response/Model/Execution.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Utopia/Response/Model/Execution.php b/src/Appwrite/Utopia/Response/Model/Execution.php index 80b65af696..dc5d41c02c 100644 --- a/src/Appwrite/Utopia/Response/Model/Execution.php +++ b/src/Appwrite/Utopia/Response/Model/Execution.php @@ -81,7 +81,7 @@ class Execution extends Model 'example' => 200, ]) ->addRule('responseBody', [ - 'type' => self::TYPE_STRING, + 'type' => self::TYPE_PAYLOAD, 'description' => 'HTTP response body. This will return empty unless execution is created as synchronous.', 'default' => '', ]) From 24283dff7f7237f5c361e2cf31e54b8ced028553 Mon Sep 17 00:00:00 2001 From: shimon Date: Wed, 9 Oct 2024 10:13:49 +0300 Subject: [PATCH 044/101] removed audit deletion leftovers in deletes worker --- src/Appwrite/Platform/Workers/Deletes.php | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 62fd8cd177..48e4014f1e 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -119,10 +119,6 @@ class Deletes extends Action if (!$project->isEmpty()) { $this->deleteAuditLogs($project, $getProjectDB, $auditRetention); } - - if (!$document->isEmpty()) { - $this->deleteAuditLogsByResource($getProjectDB, 'document/' . $document->getId(), $project); - } break; case DELETE_TYPE_ABUSE: $this->deleteAbuseLogs($project, $getProjectDB, $abuseRetention); @@ -733,22 +729,6 @@ class Deletes extends Action } } - /** - * @param callable $getProjectDB - * @param string $resource - * @param Document $project - * @return void - * @throws Exception - */ - private function deleteAuditLogsByResource(callable $getProjectDB, string $resource, Document $project): void - { - $dbForProject = $getProjectDB($project); - - $this->deleteByGroup(Audit::COLLECTION, [ - Query::equal('resource', [$resource]) - ], $dbForProject); - } - /** * @param callable $getProjectDB * @param Device $deviceForFunctions From d1ab8c4df8909e665155c3c601826eee624f0bc2 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Wed, 9 Oct 2024 14:04:08 +0530 Subject: [PATCH 045/101] Move createDocument from api to worker --- app/controllers/general.php | 28 +++++++++++++-------- composer.lock | 26 +++++++++---------- src/Appwrite/Platform/Workers/Functions.php | 5 ++++ 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 04554a940e..f31d0522d8 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -6,6 +6,7 @@ use Ahc\Jwt\JWT; use Appwrite\Auth\Auth; use Appwrite\Event\Certificate; use Appwrite\Event\Event; +use Appwrite\Event\Func; use Appwrite\Event\Usage; use Appwrite\Extend\Exception as AppwriteException; use Appwrite\Network\Validator\Origin; @@ -44,7 +45,7 @@ Config::setParam('domainVerification', false); Config::setParam('cookieDomain', 'localhost'); Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE); -function router(App $utopia, Database $dbForConsole, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Event $queueForEvents, Usage $queueForUsage, Reader $geodb) +function router(App $utopia, Database $dbForConsole, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb) { $utopia->getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml'); @@ -366,7 +367,10 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo ->trigger() ; - $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution)); + $queueForFunctions + ->setType('delayed_execution_write') + ->setExecution($execution) + ->trigger(); } $execution->setAttribute('logs', ''); @@ -450,7 +454,8 @@ App::init() ->inject('queueForUsage') ->inject('queueForEvents') ->inject('queueForCertificates') - ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, Reader $geodb, Usage $queueForUsage, Event $queueForEvents, Certificate $queueForCertificates) { + ->inject('queueForFunctions') + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, Reader $geodb, Usage $queueForUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions) { /* * Appwrite Router */ @@ -458,7 +463,7 @@ App::init() $mainDomain = System::getEnv('_APP_DOMAIN', ''); // Only run Router when external domain if ($host !== $mainDomain) { - if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb)) { + if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb)) { return; } } @@ -666,8 +671,9 @@ App::options() ->inject('getProjectDB') ->inject('queueForEvents') ->inject('queueForUsage') + ->inject('queueForFunctions') ->inject('geodb') - ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb) { + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb) { /* * Appwrite Router */ @@ -675,7 +681,7 @@ App::options() $mainDomain = System::getEnv('_APP_DOMAIN', ''); // Only run Router when external domain if ($host !== $mainDomain) { - if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb)) { + if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb)) { return; } } @@ -957,8 +963,9 @@ App::get('/robots.txt') ->inject('getProjectDB') ->inject('queueForEvents') ->inject('queueForUsage') + ->inject('queueForFunctions') ->inject('geodb') - ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb) { + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb) { $host = $request->getHostname() ?? ''; $mainDomain = System::getEnv('_APP_DOMAIN', ''); @@ -966,7 +973,7 @@ App::get('/robots.txt') $template = new View(__DIR__ . '/../views/general/robots.phtml'); $response->text($template->render(false)); } else { - router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb); + router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb); } }); @@ -982,8 +989,9 @@ App::get('/humans.txt') ->inject('getProjectDB') ->inject('queueForEvents') ->inject('queueForUsage') + ->inject('queueForFunctions') ->inject('geodb') - ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb) { + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb) { $host = $request->getHostname() ?? ''; $mainDomain = System::getEnv('_APP_DOMAIN', ''); @@ -991,7 +999,7 @@ App::get('/humans.txt') $template = new View(__DIR__ . '/../views/general/humans.phtml'); $response->text($template->render(false)); } else { - router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb); + router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb); } }); diff --git a/composer.lock b/composer.lock index bba2391d2c..4db97e4506 100644 --- a/composer.lock +++ b/composer.lock @@ -2124,16 +2124,16 @@ }, { "name": "utopia-php/messaging", - "version": "0.12.0", + "version": "0.12.1", "source": { "type": "git", "url": "https://github.com/utopia-php/messaging.git", - "reference": "6e466d3511981291843c6ebf9ce3f44fc75e37b0" + "reference": "b9dfafb5efc1d12cbee01d03dc98853ef026e35b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/messaging/zipball/6e466d3511981291843c6ebf9ce3f44fc75e37b0", - "reference": "6e466d3511981291843c6ebf9ce3f44fc75e37b0", + "url": "https://api.github.com/repos/utopia-php/messaging/zipball/b9dfafb5efc1d12cbee01d03dc98853ef026e35b", + "reference": "b9dfafb5efc1d12cbee01d03dc98853ef026e35b", "shasum": "" }, "require": { @@ -2169,9 +2169,9 @@ ], "support": { "issues": "https://github.com/utopia-php/messaging/issues", - "source": "https://github.com/utopia-php/messaging/tree/0.12.0" + "source": "https://github.com/utopia-php/messaging/tree/0.12.1" }, - "time": "2024-05-30T14:58:25+00:00" + "time": "2024-10-09T08:17:07+00:00" }, { "name": "utopia-php/migration", @@ -3573,16 +3573,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.3.0", + "version": "v5.3.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a" + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3abf7425cd284141dc5d8d14a9ee444de3345d1a", - "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", "shasum": "" }, "require": { @@ -3625,9 +3625,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" }, - "time": "2024-09-29T13:56:26+00:00" + "time": "2024-10-08T18:51:32+00:00" }, { "name": "phar-io/manifest", @@ -7028,5 +7028,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/src/Appwrite/Platform/Workers/Functions.php b/src/Appwrite/Platform/Workers/Functions.php index dfe7435426..5b917b5ac9 100644 --- a/src/Appwrite/Platform/Workers/Functions.php +++ b/src/Appwrite/Platform/Workers/Functions.php @@ -209,6 +209,11 @@ class Functions extends Action executionId: $execution->getId() ?? null ); break; + case 'delayed_execution_write': + $execution = new Document($payload['execution'] ?? []); + $execution->setAttribute('$collection', 'executions'); + $execution = $dbForProject->createDocument('executions', $execution); + return; } } From 18c25aa03a045517293855e7dfc1d2deaf9bdd33 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 9 Oct 2024 22:25:55 +1300 Subject: [PATCH 046/101] Move new attributes --- app/config/collections.php | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index cba3100922..69750e9427 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -4160,28 +4160,6 @@ $projectCollections = array_merge([ 'array' => true, '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'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], [ '$id' => ID::custom('statusCounters'), 'type' => Database::VAR_STRING, From 473f97fe92640b1c160e3e23fe3a9d2c253bcae7 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 9 Oct 2024 22:55:50 +1300 Subject: [PATCH 047/101] Update migration --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index bba2391d2c..aa489de579 100644 --- a/composer.lock +++ b/composer.lock @@ -2175,16 +2175,16 @@ }, { "name": "utopia-php/migration", - "version": "0.6.5", + "version": "0.6.6", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "7b2d40d526b82e9b92a17ea681b8103222e3c86a" + "reference": "667997c7ca6c445001d56f70205b6cf13c6b7343" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/7b2d40d526b82e9b92a17ea681b8103222e3c86a", - "reference": "7b2d40d526b82e9b92a17ea681b8103222e3c86a", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/667997c7ca6c445001d56f70205b6cf13c6b7343", + "reference": "667997c7ca6c445001d56f70205b6cf13c6b7343", "shasum": "" }, "require": { @@ -2225,9 +2225,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/0.6.5" + "source": "https://github.com/utopia-php/migration/tree/0.6.6" }, - "time": "2024-10-07T08:54:05+00:00" + "time": "2024-10-09T09:51:43+00:00" }, { "name": "utopia-php/mongo", From 919bb9f4bd09d9dce44ab52e93192fc9a7f8a963 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 9 Oct 2024 11:43:38 +0100 Subject: [PATCH 048/101] chore: remove restore-keys --- .github/workflows/tests.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a733814dfb..46c82910cb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,6 +6,7 @@ concurrency: env: IMAGE: appwrite-dev + CACHE_KEY: appwrite-dev-${{ github.event.pull_request.head.sha }} on: [pull_request] @@ -29,8 +30,8 @@ jobs: push: false tags: ${{ env.IMAGE }} load: true - cache-from: type=gha,scope=${{ env.CACHE_KEY }} - cache-to: type=gha,mode=max,scope=${{ env.CACHE_KEY }} + cache-from: type=gha,scope=appwrite + cache-to: type=gha,mode=max,scope=appwrite outputs: type=docker,dest=/tmp/${{ env.IMAGE }}.tar build-args: | DEBUG=false @@ -40,7 +41,7 @@ jobs: - name: Cache Docker Image uses: actions/cache@v4 with: - key: appwrite-dev-${{ hashFiles('**') }} + key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar unit_test: @@ -55,7 +56,7 @@ jobs: - name: Load Cache uses: actions/cache@v4 with: - key: appwrite-dev-${{ hashFiles('**') }} + key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar fail-on-cache-miss: true @@ -85,7 +86,7 @@ jobs: - name: Load Cache uses: actions/cache@v4 with: - key: appwrite-dev-${{ hashFiles('**') }} + key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar fail-on-cache-miss: true @@ -133,7 +134,7 @@ jobs: - name: Load Cache uses: actions/cache@v4 with: - key: appwrite-dev-${{ hashFiles('**') }} + key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar fail-on-cache-miss: true @@ -159,7 +160,7 @@ jobs: - name: Load Cache uses: actions/cache@v4 with: - key: appwrite-dev-${{ hashFiles('**') }} + key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar fail-on-cache-miss: true - name: Load and Start Appwrite From 7c2b6d9330f8920361a486b778914086bec68c52 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 9 Oct 2024 13:05:13 +0100 Subject: [PATCH 049/101] fix: remove cache scope --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 46c82910cb..7c53b03b52 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,8 +30,8 @@ jobs: push: false tags: ${{ env.IMAGE }} load: true - cache-from: type=gha,scope=appwrite - cache-to: type=gha,mode=max,scope=appwrite + cache-from: type=gha + cache-to: type=gha,mode=max outputs: type=docker,dest=/tmp/${{ env.IMAGE }}.tar build-args: | DEBUG=false From e90df389dd3f83ccb66247af98adac94667841c4 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 9 Oct 2024 14:02:55 +0100 Subject: [PATCH 050/101] debug: non override --- .../Functions/FunctionsCustomClientTest.php | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 31cc05f423..adce8e2cfe 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -6,6 +6,7 @@ use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; +use Utopia\CLI\Console; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; @@ -249,20 +250,29 @@ class FunctionsCustomClientTest extends Scope 'activate' => true ]); - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'x-appwrite-event' => "OVERRIDDEN", - 'x-appwrite-trigger' => "OVERRIDDEN", - 'x-appwrite-user-id' => "OVERRIDDEN", - 'x-appwrite-user-jwt' => "OVERRIDDEN", - ]); - $output = json_decode($execution['body']['responseBody'], true); - $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_JWT']); - $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_EVENT']); - $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_TRIGGER']); - $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_USER_ID']); + try { + $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'x-appwrite-event' => "OVERRIDDEN", + 'x-appwrite-trigger' => "OVERRIDDEN", + 'x-appwrite-user-id' => "OVERRIDDEN", + 'x-appwrite-user-jwt' => "OVERRIDDEN", + ]); + + $output = json_decode($execution['body']['responseBody'], true); + $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_JWT']); + $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_EVENT']); + $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_TRIGGER']); + $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_USER_ID']); + } catch (\Exception $e) { + // output docker logs + $out = ''; + Console::execute('docker compose logs appwrite', '', $out, $out); + fwrite(STDOUT, print_r($out, true)); + } + $this->cleanupFunction($functionId); } From 8f4594babb3e6381cf03a54544246be89df2d83b Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 9 Oct 2024 14:12:46 +0100 Subject: [PATCH 051/101] debug: ci docker logs --- .github/workflows/tests.yml | 8 ++++ .../Functions/FunctionsCustomClientTest.php | 37 ++++++++----------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7c53b03b52..9ddba90595 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -147,9 +147,17 @@ jobs: - name: Run ${{matrix.service}} Tests run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug + - name: Log docker-compose logs + run: docker compose logs appwrite + if: failure() + - 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: Log docker-compose logs + run: docker compose logs appwrite + if: failure() + benchmarking: name: Benchmark runs-on: ubuntu-latest diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index adce8e2cfe..c809d3a15a 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -250,28 +250,21 @@ class FunctionsCustomClientTest extends Scope 'activate' => true ]); - try { - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'x-appwrite-event' => "OVERRIDDEN", - 'x-appwrite-trigger' => "OVERRIDDEN", - 'x-appwrite-user-id' => "OVERRIDDEN", - 'x-appwrite-user-jwt' => "OVERRIDDEN", - ]); - - $output = json_decode($execution['body']['responseBody'], true); - $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_JWT']); - $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_EVENT']); - $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_TRIGGER']); - $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_USER_ID']); - } catch (\Exception $e) { - // output docker logs - $out = ''; - Console::execute('docker compose logs appwrite', '', $out, $out); - fwrite(STDOUT, print_r($out, true)); - } + $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'x-appwrite-event' => "OVERRIDDEN", + 'x-appwrite-trigger' => "OVERRIDDEN", + 'x-appwrite-user-id' => "OVERRIDDEN", + 'x-appwrite-user-jwt' => "OVERRIDDEN", + ]); + + $output = json_decode($execution['body']['responseBody'], true); + $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_JWT']); + $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_EVENT']); + $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_TRIGGER']); + $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_USER_ID']); $this->cleanupFunction($functionId); From 3b4fea655a09571423d4f46b2de481e007cbc210 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 9 Oct 2024 14:27:38 +0100 Subject: [PATCH 052/101] chore: revert logs --- .github/workflows/tests.yml | 8 -------- .../e2e/Services/Functions/FunctionsCustomClientTest.php | 1 - 2 files changed, 9 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9ddba90595..7c53b03b52 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -147,17 +147,9 @@ jobs: - name: Run ${{matrix.service}} Tests run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug - - name: Log docker-compose logs - run: docker compose logs appwrite - if: failure() - - 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: Log docker-compose logs - run: docker compose logs appwrite - if: failure() - benchmarking: name: Benchmark runs-on: ubuntu-latest diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index c809d3a15a..914a255663 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -6,7 +6,6 @@ use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; -use Utopia\CLI\Console; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; From e0ebeb837a9e1ebf2ed3519985b533d845fe1b54 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 9 Oct 2024 16:04:36 +0000 Subject: [PATCH 053/101] chore: update php runtimes --- app/config/function-templates.php | 13 ++++++--- composer.lock | 48 +++++++++++++++---------------- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/app/config/function-templates.php b/app/config/function-templates.php index db26ff2c19..d34290fde3 100644 --- a/app/config/function-templates.php +++ b/app/config/function-templates.php @@ -3,7 +3,7 @@ const TEMPLATE_RUNTIMES = [ 'NODE' => [ 'name' => 'node', - 'versions' => ['21.0', '20.0', '19.0', '18.0', '16.0', '14.5'] + 'versions' => ['22', '21.0', '20.0', '19.0', '18.0', '16.0', '14.5'] ], 'PYTHON' => [ 'name' => 'python', @@ -11,7 +11,7 @@ const TEMPLATE_RUNTIMES = [ ], 'DART' => [ 'name' => 'dart', - 'versions' => ['3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16', '2.16'] + 'versions' => ['3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16', '2.16'] ], 'GO' => [ 'name' => 'go', @@ -23,12 +23,16 @@ const TEMPLATE_RUNTIMES = [ ], 'BUN' => [ 'name' => 'bun', - 'versions' => ['1.0'] + 'versions' => ['1.1', '1.0'] ], 'RUBY' => [ 'name' => 'ruby', 'versions' => ['3.3', '3.2', '3.1', '3.0'] ], + 'DENO' => [ + 'name' => 'deno', + 'versions' => ['2.0', '1.46', '1.40', '1.35', '1.24', '1.21'] + ], ]; function getRuntimes($runtime, $commands, $entrypoint, $providerRootDirectory, $versionsDenyList = []) @@ -75,6 +79,7 @@ return [ ), ...getRuntimes(TEMPLATE_RUNTIMES['BUN'], 'bun install', 'src/main.ts', 'bun/starter'), ...getRuntimes(TEMPLATE_RUNTIMES['RUBY'], 'bundle install', 'lib/main.rb', 'ruby/starter'), + ...getRuntimes(TEMPLATE_RUNTIMES['DENO'], 'deno cache src/main.ts', 'src/main.ts', 'deno/starter'), ], 'instructions' => 'For documentation and instructions check out file.', 'vcsProvider' => 'github', @@ -2065,4 +2070,4 @@ return [ ], 'scopes' => ['users.read', 'users.write'] ] -]; +]; \ No newline at end of file diff --git a/composer.lock b/composer.lock index 93672e670e..c6fba44502 100644 --- a/composer.lock +++ b/composer.lock @@ -1623,16 +1623,16 @@ }, { "name": "utopia-php/cli", - "version": "0.15.0", + "version": "0.15.1", "source": { "type": "git", "url": "https://github.com/utopia-php/cli.git", - "reference": "ccb7c8125ffe0254fef8f25744bfa376eb7bd0ea" + "reference": "d69bbe51a6a94dc4e5bcdd542b5938038b985a65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cli/zipball/ccb7c8125ffe0254fef8f25744bfa376eb7bd0ea", - "reference": "ccb7c8125ffe0254fef8f25744bfa376eb7bd0ea", + "url": "https://api.github.com/repos/utopia-php/cli/zipball/d69bbe51a6a94dc4e5bcdd542b5938038b985a65", + "reference": "d69bbe51a6a94dc4e5bcdd542b5938038b985a65", "shasum": "" }, "require": { @@ -1666,9 +1666,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cli/issues", - "source": "https://github.com/utopia-php/cli/tree/0.15.0" + "source": "https://github.com/utopia-php/cli/tree/0.15.1" }, - "time": "2023-03-01T05:55:14+00:00" + "time": "2024-10-04T13:55:36+00:00" }, { "name": "utopia-php/config", @@ -2123,16 +2123,16 @@ }, { "name": "utopia-php/messaging", - "version": "0.12.0", + "version": "0.12.1", "source": { "type": "git", "url": "https://github.com/utopia-php/messaging.git", - "reference": "6e466d3511981291843c6ebf9ce3f44fc75e37b0" + "reference": "b9dfafb5efc1d12cbee01d03dc98853ef026e35b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/messaging/zipball/6e466d3511981291843c6ebf9ce3f44fc75e37b0", - "reference": "6e466d3511981291843c6ebf9ce3f44fc75e37b0", + "url": "https://api.github.com/repos/utopia-php/messaging/zipball/b9dfafb5efc1d12cbee01d03dc98853ef026e35b", + "reference": "b9dfafb5efc1d12cbee01d03dc98853ef026e35b", "shasum": "" }, "require": { @@ -2168,9 +2168,9 @@ ], "support": { "issues": "https://github.com/utopia-php/messaging/issues", - "source": "https://github.com/utopia-php/messaging/tree/0.12.0" + "source": "https://github.com/utopia-php/messaging/tree/0.12.1" }, - "time": "2024-05-30T14:58:25+00:00" + "time": "2024-10-09T08:17:07+00:00" }, { "name": "utopia-php/migration", @@ -2993,16 +2993,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.39.22", + "version": "0.39.23", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "bdbb1607527550e67283ff0533522d1410c2c0df" + "reference": "0acceabb7593c9c07c5db85a84a5ebac60896763" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/bdbb1607527550e67283ff0533522d1410c2c0df", - "reference": "bdbb1607527550e67283ff0533522d1410c2c0df", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/0acceabb7593c9c07c5db85a84a5ebac60896763", + "reference": "0acceabb7593c9c07c5db85a84a5ebac60896763", "shasum": "" }, "require": { @@ -3038,9 +3038,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/0.39.22" + "source": "https://github.com/appwrite/sdk-generator/tree/0.39.23" }, - "time": "2024-10-01T16:16:26+00:00" + "time": "2024-10-08T00:38:57+00:00" }, { "name": "doctrine/annotations", @@ -3564,16 +3564,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.3.0", + "version": "v5.3.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a" + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3abf7425cd284141dc5d8d14a9ee444de3345d1a", - "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", "shasum": "" }, "require": { @@ -3616,9 +3616,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" }, - "time": "2024-09-29T13:56:26+00:00" + "time": "2024-10-08T18:51:32+00:00" }, { "name": "phar-io/manifest", From d33ecfa7f563aac7508d0637bf6e7e075dcfae22 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 9 Oct 2024 16:06:34 +0000 Subject: [PATCH 054/101] chore: update php runtimes --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 575973e54d..a581bd2fc1 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,7 @@ "ext-openssl": "*", "ext-zlib": "*", "ext-sockets": "*", - "appwrite/php-runtimes": "0.15.*", + "appwrite/php-runtimes": "0.16.*", "appwrite/php-clamav": "2.0.*", "utopia-php/abuse": "0.43.0", "utopia-php/analytics": "0.10.*", diff --git a/composer.lock b/composer.lock index c6fba44502..575e398452 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "66e31af1f7d0d1617694a1c5a975f887", + "content-hash": "f05357728316c79037d22ee26afd2d3e", "packages": [ { "name": "adhocore/jwt", @@ -156,16 +156,16 @@ }, { "name": "appwrite/php-runtimes", - "version": "0.15.0", + "version": "0.16.1", "source": { "type": "git", "url": "https://github.com/appwrite/runtimes.git", - "reference": "68ea5bcc24c513a6d641ddf9412bbab13e5dfb94" + "reference": "b36da25768c2dc66908ae226f6eed1b073a6f642" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/runtimes/zipball/68ea5bcc24c513a6d641ddf9412bbab13e5dfb94", - "reference": "68ea5bcc24c513a6d641ddf9412bbab13e5dfb94", + "url": "https://api.github.com/repos/appwrite/runtimes/zipball/b36da25768c2dc66908ae226f6eed1b073a6f642", + "reference": "b36da25768c2dc66908ae226f6eed1b073a6f642", "shasum": "" }, "require": { @@ -205,9 +205,9 @@ ], "support": { "issues": "https://github.com/appwrite/runtimes/issues", - "source": "https://github.com/appwrite/runtimes/tree/0.15.0" + "source": "https://github.com/appwrite/runtimes/tree/0.16.1" }, - "time": "2024-08-21T10:23:45+00:00" + "time": "2024-10-09T10:27:14+00:00" }, { "name": "beberlei/assert", From 119834fc96800ba050405bfe0f6d7f25687cc8a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 9 Oct 2024 18:21:35 +0200 Subject: [PATCH 055/101] Fix deno order --- app/config/function-templates.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/config/function-templates.php b/app/config/function-templates.php index d34290fde3..8926f0624c 100644 --- a/app/config/function-templates.php +++ b/app/config/function-templates.php @@ -21,6 +21,10 @@ const TEMPLATE_RUNTIMES = [ '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'] @@ -29,10 +33,6 @@ const TEMPLATE_RUNTIMES = [ 'name' => 'ruby', 'versions' => ['3.3', '3.2', '3.1', '3.0'] ], - 'DENO' => [ - 'name' => 'deno', - 'versions' => ['2.0', '1.46', '1.40', '1.35', '1.24', '1.21'] - ], ]; function getRuntimes($runtime, $commands, $entrypoint, $providerRootDirectory, $versionsDenyList = []) From 4c1d539e0433e7e0545b93e3e3207ce7b3deb594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 9 Oct 2024 19:18:40 +0200 Subject: [PATCH 056/101] Change order --- app/config/function-templates.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/function-templates.php b/app/config/function-templates.php index 8926f0624c..e0d35e57b5 100644 --- a/app/config/function-templates.php +++ b/app/config/function-templates.php @@ -77,9 +77,9 @@ return [ 'src/index.php', 'php/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'), - ...getRuntimes(TEMPLATE_RUNTIMES['DENO'], 'deno cache src/main.ts', 'src/main.ts', 'deno/starter'), ], 'instructions' => 'For documentation and instructions check out file.', 'vcsProvider' => 'github', From ecfe962d43a1693ccb130008424426a67ed81234 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 10 Oct 2024 15:53:54 +1300 Subject: [PATCH 057/101] Fix execution model --- src/Appwrite/Utopia/Response/Model/Execution.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Utopia/Response/Model/Execution.php b/src/Appwrite/Utopia/Response/Model/Execution.php index dc5d41c02c..80b65af696 100644 --- a/src/Appwrite/Utopia/Response/Model/Execution.php +++ b/src/Appwrite/Utopia/Response/Model/Execution.php @@ -81,7 +81,7 @@ class Execution extends Model 'example' => 200, ]) ->addRule('responseBody', [ - 'type' => self::TYPE_PAYLOAD, + 'type' => self::TYPE_STRING, 'description' => 'HTTP response body. This will return empty unless execution is created as synchronous.', 'default' => '', ]) From 244942dd4e7c4ad4fb9c59527f327b28c81277e9 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 10 Oct 2024 15:55:35 +1300 Subject: [PATCH 058/101] Generate specs --- app/config/specs/open-api3-1.6.x-client.json | 190 ++--- app/config/specs/open-api3-1.6.x-console.json | 767 ++++++++--------- app/config/specs/open-api3-1.6.x-server.json | 546 +++++++------ app/config/specs/swagger2-1.6.x-client.json | 192 ++--- app/config/specs/swagger2-1.6.x-console.json | 769 +++++++++--------- app/config/specs/swagger2-1.6.x-server.json | 548 +++++++------ 6 files changed, 1551 insertions(+), 1461 deletions(-) diff --git a/app/config/specs/open-api3-1.6.x-client.json b/app/config/specs/open-api3-1.6.x-client.json index c67793eafd..c00d4e2d07 100644 --- a/app/config/specs/open-api3-1.6.x-client.json +++ b/app/config/specs/open-api3-1.6.x-client.json @@ -43,7 +43,7 @@ }, "x-appwrite": { "method": "get", - "weight": 8, + "weight": 9, "cookies": false, "type": "", "deprecated": false, @@ -94,7 +94,7 @@ }, "x-appwrite": { "method": "create", - "weight": 7, + "weight": 8, "cookies": false, "type": "", "deprecated": false, @@ -181,7 +181,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 33, + "weight": 34, "cookies": false, "type": "", "deprecated": false, @@ -259,7 +259,7 @@ }, "x-appwrite": { "method": "listIdentities", - "weight": 56, + "weight": 57, "cookies": false, "type": "", "deprecated": false, @@ -320,7 +320,7 @@ }, "x-appwrite": { "method": "deleteIdentity", - "weight": 57, + "weight": 58, "cookies": false, "type": "", "deprecated": false, @@ -385,7 +385,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 28, + "weight": 29, "cookies": false, "type": "", "deprecated": false, @@ -436,7 +436,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 30, + "weight": 31, "cookies": false, "type": "", "deprecated": false, @@ -504,7 +504,7 @@ }, "x-appwrite": { "method": "updateMFA", - "weight": 43, + "weight": 44, "cookies": false, "type": "", "deprecated": false, @@ -576,7 +576,7 @@ }, "x-appwrite": { "method": "createMfaAuthenticator", - "weight": 45, + "weight": 46, "cookies": false, "type": "", "deprecated": false, @@ -644,7 +644,7 @@ }, "x-appwrite": { "method": "updateMfaAuthenticator", - "weight": 46, + "weight": 47, "cookies": false, "type": "", "deprecated": false, @@ -724,7 +724,7 @@ }, "x-appwrite": { "method": "deleteMfaAuthenticator", - "weight": 50, + "weight": 51, "cookies": false, "type": "", "deprecated": false, @@ -794,7 +794,7 @@ }, "x-appwrite": { "method": "createMfaChallenge", - "weight": 51, + "weight": 52, "cookies": false, "type": "", "deprecated": false, @@ -870,7 +870,7 @@ }, "x-appwrite": { "method": "updateMfaChallenge", - "weight": 52, + "weight": 53, "cookies": false, "type": "", "deprecated": false, @@ -948,7 +948,7 @@ }, "x-appwrite": { "method": "listMfaFactors", - "weight": 44, + "weight": 45, "cookies": false, "type": "", "deprecated": false, @@ -1001,7 +1001,7 @@ }, "x-appwrite": { "method": "getMfaRecoveryCodes", - "weight": 49, + "weight": 50, "cookies": false, "type": "", "deprecated": false, @@ -1052,7 +1052,7 @@ }, "x-appwrite": { "method": "createMfaRecoveryCodes", - "weight": 47, + "weight": 48, "cookies": false, "type": "", "deprecated": false, @@ -1103,7 +1103,7 @@ }, "x-appwrite": { "method": "updateMfaRecoveryCodes", - "weight": 48, + "weight": 49, "cookies": false, "type": "", "deprecated": false, @@ -1156,7 +1156,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 31, + "weight": 32, "cookies": false, "type": "", "deprecated": false, @@ -1228,7 +1228,7 @@ }, "x-appwrite": { "method": "updatePassword", - "weight": 32, + "weight": 33, "cookies": false, "type": "", "deprecated": false, @@ -1305,7 +1305,7 @@ }, "x-appwrite": { "method": "updatePhone", - "weight": 34, + "weight": 35, "cookies": false, "type": "", "deprecated": false, @@ -1383,7 +1383,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 29, + "weight": 30, "cookies": false, "type": "", "deprecated": false, @@ -1434,7 +1434,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 35, + "weight": 36, "cookies": false, "type": "", "deprecated": false, @@ -1506,7 +1506,7 @@ }, "x-appwrite": { "method": "createRecovery", - "weight": 37, + "weight": 38, "cookies": false, "type": "", "deprecated": false, @@ -1585,7 +1585,7 @@ }, "x-appwrite": { "method": "updateRecovery", - "weight": 38, + "weight": 39, "cookies": false, "type": "", "deprecated": false, @@ -1669,7 +1669,7 @@ }, "x-appwrite": { "method": "listSessions", - "weight": 10, + "weight": 11, "cookies": false, "type": "", "deprecated": false, @@ -1713,7 +1713,7 @@ }, "x-appwrite": { "method": "deleteSessions", - "weight": 11, + "weight": 12, "cookies": false, "type": "", "deprecated": false, @@ -1766,7 +1766,7 @@ }, "x-appwrite": { "method": "createAnonymousSession", - "weight": 16, + "weight": 17, "cookies": false, "type": "", "deprecated": false, @@ -1817,7 +1817,7 @@ }, "x-appwrite": { "method": "createEmailPasswordSession", - "weight": 15, + "weight": 16, "cookies": false, "type": "", "deprecated": false, @@ -1893,7 +1893,7 @@ }, "x-appwrite": { "method": "updateMagicURLSession", - "weight": 25, + "weight": 26, "cookies": false, "type": "", "deprecated": true, @@ -1962,7 +1962,7 @@ }, "x-appwrite": { "method": "createOAuth2Session", - "weight": 18, + "weight": 19, "cookies": false, "type": "webAuth", "deprecated": false, @@ -2105,7 +2105,7 @@ }, "x-appwrite": { "method": "updatePhoneSession", - "weight": 26, + "weight": 27, "cookies": false, "type": "", "deprecated": true, @@ -2181,7 +2181,7 @@ }, "x-appwrite": { "method": "createSession", - "weight": 17, + "weight": 18, "cookies": false, "type": "", "deprecated": false, @@ -2257,7 +2257,7 @@ }, "x-appwrite": { "method": "getSession", - "weight": 12, + "weight": 13, "cookies": false, "type": "", "deprecated": false, @@ -2320,7 +2320,7 @@ }, "x-appwrite": { "method": "updateSession", - "weight": 14, + "weight": 15, "cookies": false, "type": "", "deprecated": false, @@ -2376,7 +2376,7 @@ }, "x-appwrite": { "method": "deleteSession", - "weight": 13, + "weight": 14, "cookies": false, "type": "", "deprecated": false, @@ -2441,7 +2441,7 @@ }, "x-appwrite": { "method": "updateStatus", - "weight": 36, + "weight": 37, "cookies": false, "type": "", "deprecated": false, @@ -2494,7 +2494,7 @@ }, "x-appwrite": { "method": "createPushTarget", - "weight": 53, + "weight": 54, "cookies": false, "type": "", "deprecated": false, @@ -2575,7 +2575,7 @@ }, "x-appwrite": { "method": "updatePushTarget", - "weight": 54, + "weight": 55, "cookies": false, "type": "", "deprecated": false, @@ -2655,7 +2655,7 @@ }, "x-appwrite": { "method": "deletePushTarget", - "weight": 55, + "weight": 56, "cookies": false, "type": "", "deprecated": false, @@ -2718,7 +2718,7 @@ }, "x-appwrite": { "method": "createEmailToken", - "weight": 24, + "weight": 25, "cookies": false, "type": "", "deprecated": false, @@ -2799,7 +2799,7 @@ }, "x-appwrite": { "method": "createMagicURLToken", - "weight": 23, + "weight": 24, "cookies": false, "type": "", "deprecated": false, @@ -2881,7 +2881,7 @@ }, "x-appwrite": { "method": "createOAuth2Token", - "weight": 22, + "weight": 23, "cookies": false, "type": "webAuth", "deprecated": false, @@ -3024,7 +3024,7 @@ }, "x-appwrite": { "method": "createPhoneToken", - "weight": 27, + "weight": 28, "cookies": false, "type": "", "deprecated": false, @@ -3103,7 +3103,7 @@ }, "x-appwrite": { "method": "createVerification", - "weight": 39, + "weight": 40, "cookies": false, "type": "", "deprecated": false, @@ -3173,7 +3173,7 @@ }, "x-appwrite": { "method": "updateVerification", - "weight": 40, + "weight": 41, "cookies": false, "type": "", "deprecated": false, @@ -3251,7 +3251,7 @@ }, "x-appwrite": { "method": "createPhoneVerification", - "weight": 41, + "weight": 42, "cookies": false, "type": "", "deprecated": false, @@ -3305,7 +3305,7 @@ }, "x-appwrite": { "method": "updatePhoneVerification", - "weight": 42, + "weight": 43, "cookies": false, "type": "", "deprecated": false, @@ -3376,7 +3376,7 @@ }, "x-appwrite": { "method": "getBrowser", - "weight": 59, + "weight": 60, "cookies": false, "type": "location", "deprecated": false, @@ -3504,7 +3504,7 @@ }, "x-appwrite": { "method": "getCreditCard", - "weight": 58, + "weight": 59, "cookies": false, "type": "location", "deprecated": false, @@ -3636,7 +3636,7 @@ }, "x-appwrite": { "method": "getFavicon", - "weight": 62, + "weight": 63, "cookies": false, "type": "location", "deprecated": false, @@ -3696,7 +3696,7 @@ }, "x-appwrite": { "method": "getFlag", - "weight": 60, + "weight": 61, "cookies": false, "type": "location", "deprecated": false, @@ -4186,7 +4186,7 @@ }, "x-appwrite": { "method": "getImage", - "weight": 61, + "weight": 62, "cookies": false, "type": "location", "deprecated": false, @@ -4270,7 +4270,7 @@ }, "x-appwrite": { "method": "getInitials", - "weight": 64, + "weight": 65, "cookies": false, "type": "location", "deprecated": false, @@ -4364,7 +4364,7 @@ }, "x-appwrite": { "method": "getQR", - "weight": 63, + "weight": 64, "cookies": false, "type": "location", "deprecated": false, @@ -4465,7 +4465,7 @@ }, "x-appwrite": { "method": "listDocuments", - "weight": 108, + "weight": 109, "cookies": false, "type": "", "deprecated": false, @@ -4552,7 +4552,7 @@ }, "x-appwrite": { "method": "createDocument", - "weight": 107, + "weight": 108, "cookies": false, "type": "", "deprecated": false, @@ -4661,7 +4661,7 @@ }, "x-appwrite": { "method": "getDocument", - "weight": 109, + "weight": 110, "cookies": false, "type": "", "deprecated": false, @@ -4758,7 +4758,7 @@ }, "x-appwrite": { "method": "updateDocument", - "weight": 111, + "weight": 112, "cookies": false, "type": "", "deprecated": false, @@ -4859,7 +4859,7 @@ }, "x-appwrite": { "method": "deleteDocument", - "weight": 112, + "weight": 113, "cookies": false, "type": "", "deprecated": false, @@ -4945,7 +4945,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 304, + "weight": 305, "cookies": false, "type": "", "deprecated": false, @@ -5033,7 +5033,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 303, + "weight": 304, "cookies": false, "type": "", "deprecated": false, @@ -5150,7 +5150,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 305, + "weight": 306, "cookies": false, "type": "", "deprecated": false, @@ -5226,7 +5226,7 @@ }, "x-appwrite": { "method": "query", - "weight": 329, + "weight": 330, "cookies": false, "type": "graphql", "deprecated": false, @@ -5280,7 +5280,7 @@ }, "x-appwrite": { "method": "mutation", - "weight": 328, + "weight": 329, "cookies": false, "type": "graphql", "deprecated": false, @@ -5334,7 +5334,7 @@ }, "x-appwrite": { "method": "get", - "weight": 116, + "weight": 117, "cookies": false, "type": "", "deprecated": false, @@ -5388,7 +5388,7 @@ }, "x-appwrite": { "method": "listCodes", - "weight": 117, + "weight": 118, "cookies": false, "type": "", "deprecated": false, @@ -5442,7 +5442,7 @@ }, "x-appwrite": { "method": "listContinents", - "weight": 121, + "weight": 122, "cookies": false, "type": "", "deprecated": false, @@ -5496,7 +5496,7 @@ }, "x-appwrite": { "method": "listCountries", - "weight": 118, + "weight": 119, "cookies": false, "type": "", "deprecated": false, @@ -5550,7 +5550,7 @@ }, "x-appwrite": { "method": "listCountriesEU", - "weight": 119, + "weight": 120, "cookies": false, "type": "", "deprecated": false, @@ -5604,7 +5604,7 @@ }, "x-appwrite": { "method": "listCountriesPhones", - "weight": 120, + "weight": 121, "cookies": false, "type": "", "deprecated": false, @@ -5658,7 +5658,7 @@ }, "x-appwrite": { "method": "listCurrencies", - "weight": 122, + "weight": 123, "cookies": false, "type": "", "deprecated": false, @@ -5712,7 +5712,7 @@ }, "x-appwrite": { "method": "listLanguages", - "weight": 123, + "weight": 124, "cookies": false, "type": "", "deprecated": false, @@ -5766,7 +5766,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 380, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -5851,7 +5851,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 384, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -5928,7 +5928,7 @@ }, "x-appwrite": { "method": "listFiles", - "weight": 206, + "weight": 207, "cookies": false, "type": "", "deprecated": false, @@ -6016,7 +6016,7 @@ }, "x-appwrite": { "method": "createFile", - "weight": 205, + "weight": 206, "cookies": false, "type": "upload", "deprecated": false, @@ -6116,7 +6116,7 @@ }, "x-appwrite": { "method": "getFile", - "weight": 207, + "weight": 208, "cookies": false, "type": "", "deprecated": false, @@ -6190,7 +6190,7 @@ }, "x-appwrite": { "method": "updateFile", - "weight": 212, + "weight": 213, "cookies": false, "type": "", "deprecated": false, @@ -6281,7 +6281,7 @@ }, "x-appwrite": { "method": "deleteFile", - "weight": 213, + "weight": 214, "cookies": false, "type": "", "deprecated": false, @@ -6350,7 +6350,7 @@ }, "x-appwrite": { "method": "getFileDownload", - "weight": 209, + "weight": 210, "cookies": false, "type": "location", "deprecated": false, @@ -6419,7 +6419,7 @@ }, "x-appwrite": { "method": "getFilePreview", - "weight": 208, + "weight": 209, "cookies": false, "type": "location", "deprecated": false, @@ -6637,7 +6637,7 @@ }, "x-appwrite": { "method": "getFileView", - "weight": 210, + "weight": 211, "cookies": false, "type": "location", "deprecated": false, @@ -6713,7 +6713,7 @@ }, "x-appwrite": { "method": "list", - "weight": 217, + "weight": 218, "cookies": false, "type": "", "deprecated": false, @@ -6791,7 +6791,7 @@ }, "x-appwrite": { "method": "create", - "weight": 216, + "weight": 217, "cookies": false, "type": "", "deprecated": false, @@ -6878,7 +6878,7 @@ }, "x-appwrite": { "method": "get", - "weight": 218, + "weight": 219, "cookies": false, "type": "", "deprecated": false, @@ -6942,7 +6942,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 220, + "weight": 221, "cookies": false, "type": "", "deprecated": false, @@ -7018,7 +7018,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 222, + "weight": 223, "cookies": false, "type": "", "deprecated": false, @@ -7084,7 +7084,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 224, + "weight": 225, "cookies": false, "type": "", "deprecated": false, @@ -7172,7 +7172,7 @@ }, "x-appwrite": { "method": "createMembership", - "weight": 223, + "weight": 224, "cookies": false, "type": "", "deprecated": false, @@ -7285,7 +7285,7 @@ }, "x-appwrite": { "method": "getMembership", - "weight": 225, + "weight": 226, "cookies": false, "type": "", "deprecated": false, @@ -7359,7 +7359,7 @@ }, "x-appwrite": { "method": "updateMembership", - "weight": 226, + "weight": 227, "cookies": false, "type": "", "deprecated": false, @@ -7448,7 +7448,7 @@ }, "x-appwrite": { "method": "deleteMembership", - "weight": 228, + "weight": 229, "cookies": false, "type": "", "deprecated": false, @@ -7524,7 +7524,7 @@ }, "x-appwrite": { "method": "updateMembershipStatus", - "weight": 227, + "weight": 228, "cookies": false, "type": "", "deprecated": false, @@ -7624,7 +7624,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 219, + "weight": 220, "cookies": false, "type": "", "deprecated": false, @@ -7687,7 +7687,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 221, + "weight": 222, "cookies": false, "type": "", "deprecated": false, diff --git a/app/config/specs/open-api3-1.6.x-console.json b/app/config/specs/open-api3-1.6.x-console.json index 32df1d5c94..87c61ada7b 100644 --- a/app/config/specs/open-api3-1.6.x-console.json +++ b/app/config/specs/open-api3-1.6.x-console.json @@ -43,7 +43,7 @@ }, "x-appwrite": { "method": "get", - "weight": 8, + "weight": 9, "cookies": false, "type": "", "deprecated": false, @@ -93,7 +93,7 @@ }, "x-appwrite": { "method": "create", - "weight": 7, + "weight": 8, "cookies": false, "type": "", "deprecated": false, @@ -171,7 +171,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 9, + "weight": 10, "cookies": false, "type": "", "deprecated": false, @@ -221,7 +221,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 33, + "weight": 34, "cookies": false, "type": "", "deprecated": false, @@ -298,7 +298,7 @@ }, "x-appwrite": { "method": "listIdentities", - "weight": 56, + "weight": 57, "cookies": false, "type": "", "deprecated": false, @@ -358,7 +358,7 @@ }, "x-appwrite": { "method": "deleteIdentity", - "weight": 57, + "weight": 58, "cookies": false, "type": "", "deprecated": false, @@ -422,7 +422,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 28, + "weight": 29, "cookies": false, "type": "", "deprecated": false, @@ -473,7 +473,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 30, + "weight": 31, "cookies": false, "type": "", "deprecated": false, @@ -540,7 +540,7 @@ }, "x-appwrite": { "method": "updateMFA", - "weight": 43, + "weight": 44, "cookies": false, "type": "", "deprecated": false, @@ -611,7 +611,7 @@ }, "x-appwrite": { "method": "createMfaAuthenticator", - "weight": 45, + "weight": 46, "cookies": false, "type": "", "deprecated": false, @@ -678,7 +678,7 @@ }, "x-appwrite": { "method": "updateMfaAuthenticator", - "weight": 46, + "weight": 47, "cookies": false, "type": "", "deprecated": false, @@ -757,7 +757,7 @@ }, "x-appwrite": { "method": "deleteMfaAuthenticator", - "weight": 50, + "weight": 51, "cookies": false, "type": "", "deprecated": false, @@ -826,7 +826,7 @@ }, "x-appwrite": { "method": "createMfaChallenge", - "weight": 51, + "weight": 52, "cookies": false, "type": "", "deprecated": false, @@ -902,7 +902,7 @@ }, "x-appwrite": { "method": "updateMfaChallenge", - "weight": 52, + "weight": 53, "cookies": false, "type": "", "deprecated": false, @@ -979,7 +979,7 @@ }, "x-appwrite": { "method": "listMfaFactors", - "weight": 44, + "weight": 45, "cookies": false, "type": "", "deprecated": false, @@ -1031,7 +1031,7 @@ }, "x-appwrite": { "method": "getMfaRecoveryCodes", - "weight": 49, + "weight": 50, "cookies": false, "type": "", "deprecated": false, @@ -1081,7 +1081,7 @@ }, "x-appwrite": { "method": "createMfaRecoveryCodes", - "weight": 47, + "weight": 48, "cookies": false, "type": "", "deprecated": false, @@ -1131,7 +1131,7 @@ }, "x-appwrite": { "method": "updateMfaRecoveryCodes", - "weight": 48, + "weight": 49, "cookies": false, "type": "", "deprecated": false, @@ -1183,7 +1183,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 31, + "weight": 32, "cookies": false, "type": "", "deprecated": false, @@ -1254,7 +1254,7 @@ }, "x-appwrite": { "method": "updatePassword", - "weight": 32, + "weight": 33, "cookies": false, "type": "", "deprecated": false, @@ -1330,7 +1330,7 @@ }, "x-appwrite": { "method": "updatePhone", - "weight": 34, + "weight": 35, "cookies": false, "type": "", "deprecated": false, @@ -1407,7 +1407,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 29, + "weight": 30, "cookies": false, "type": "", "deprecated": false, @@ -1457,7 +1457,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 35, + "weight": 36, "cookies": false, "type": "", "deprecated": false, @@ -1528,7 +1528,7 @@ }, "x-appwrite": { "method": "createRecovery", - "weight": 37, + "weight": 38, "cookies": false, "type": "", "deprecated": false, @@ -1606,7 +1606,7 @@ }, "x-appwrite": { "method": "updateRecovery", - "weight": 38, + "weight": 39, "cookies": false, "type": "", "deprecated": false, @@ -1689,7 +1689,7 @@ }, "x-appwrite": { "method": "listSessions", - "weight": 10, + "weight": 11, "cookies": false, "type": "", "deprecated": false, @@ -1732,7 +1732,7 @@ }, "x-appwrite": { "method": "deleteSessions", - "weight": 11, + "weight": 12, "cookies": false, "type": "", "deprecated": false, @@ -1784,7 +1784,7 @@ }, "x-appwrite": { "method": "createAnonymousSession", - "weight": 16, + "weight": 17, "cookies": false, "type": "", "deprecated": false, @@ -1835,7 +1835,7 @@ }, "x-appwrite": { "method": "createEmailPasswordSession", - "weight": 15, + "weight": 16, "cookies": false, "type": "", "deprecated": false, @@ -1911,7 +1911,7 @@ }, "x-appwrite": { "method": "updateMagicURLSession", - "weight": 25, + "weight": 26, "cookies": false, "type": "", "deprecated": true, @@ -1980,7 +1980,7 @@ }, "x-appwrite": { "method": "createOAuth2Session", - "weight": 18, + "weight": 19, "cookies": false, "type": "webAuth", "deprecated": false, @@ -2123,7 +2123,7 @@ }, "x-appwrite": { "method": "updatePhoneSession", - "weight": 26, + "weight": 27, "cookies": false, "type": "", "deprecated": true, @@ -2199,7 +2199,7 @@ }, "x-appwrite": { "method": "createSession", - "weight": 17, + "weight": 18, "cookies": false, "type": "", "deprecated": false, @@ -2275,7 +2275,7 @@ }, "x-appwrite": { "method": "getSession", - "weight": 12, + "weight": 13, "cookies": false, "type": "", "deprecated": false, @@ -2337,7 +2337,7 @@ }, "x-appwrite": { "method": "updateSession", - "weight": 14, + "weight": 15, "cookies": false, "type": "", "deprecated": false, @@ -2392,7 +2392,7 @@ }, "x-appwrite": { "method": "deleteSession", - "weight": 13, + "weight": 14, "cookies": false, "type": "", "deprecated": false, @@ -2456,7 +2456,7 @@ }, "x-appwrite": { "method": "updateStatus", - "weight": 36, + "weight": 37, "cookies": false, "type": "", "deprecated": false, @@ -2508,7 +2508,7 @@ }, "x-appwrite": { "method": "createPushTarget", - "weight": 53, + "weight": 54, "cookies": false, "type": "", "deprecated": false, @@ -2588,7 +2588,7 @@ }, "x-appwrite": { "method": "updatePushTarget", - "weight": 54, + "weight": 55, "cookies": false, "type": "", "deprecated": false, @@ -2667,7 +2667,7 @@ }, "x-appwrite": { "method": "deletePushTarget", - "weight": 55, + "weight": 56, "cookies": false, "type": "", "deprecated": false, @@ -2729,7 +2729,7 @@ }, "x-appwrite": { "method": "createEmailToken", - "weight": 24, + "weight": 25, "cookies": false, "type": "", "deprecated": false, @@ -2810,7 +2810,7 @@ }, "x-appwrite": { "method": "createMagicURLToken", - "weight": 23, + "weight": 24, "cookies": false, "type": "", "deprecated": false, @@ -2892,7 +2892,7 @@ }, "x-appwrite": { "method": "createOAuth2Token", - "weight": 22, + "weight": 23, "cookies": false, "type": "webAuth", "deprecated": false, @@ -3035,7 +3035,7 @@ }, "x-appwrite": { "method": "createPhoneToken", - "weight": 27, + "weight": 28, "cookies": false, "type": "", "deprecated": false, @@ -3114,7 +3114,7 @@ }, "x-appwrite": { "method": "createVerification", - "weight": 39, + "weight": 40, "cookies": false, "type": "", "deprecated": false, @@ -3183,7 +3183,7 @@ }, "x-appwrite": { "method": "updateVerification", - "weight": 40, + "weight": 41, "cookies": false, "type": "", "deprecated": false, @@ -3260,7 +3260,7 @@ }, "x-appwrite": { "method": "createPhoneVerification", - "weight": 41, + "weight": 42, "cookies": false, "type": "", "deprecated": false, @@ -3313,7 +3313,7 @@ }, "x-appwrite": { "method": "updatePhoneVerification", - "weight": 42, + "weight": 43, "cookies": false, "type": "", "deprecated": false, @@ -3383,7 +3383,7 @@ }, "x-appwrite": { "method": "getBrowser", - "weight": 59, + "weight": 60, "cookies": false, "type": "location", "deprecated": false, @@ -3511,7 +3511,7 @@ }, "x-appwrite": { "method": "getCreditCard", - "weight": 58, + "weight": 59, "cookies": false, "type": "location", "deprecated": false, @@ -3643,7 +3643,7 @@ }, "x-appwrite": { "method": "getFavicon", - "weight": 62, + "weight": 63, "cookies": false, "type": "location", "deprecated": false, @@ -3703,7 +3703,7 @@ }, "x-appwrite": { "method": "getFlag", - "weight": 60, + "weight": 61, "cookies": false, "type": "location", "deprecated": false, @@ -4193,7 +4193,7 @@ }, "x-appwrite": { "method": "getImage", - "weight": 61, + "weight": 62, "cookies": false, "type": "location", "deprecated": false, @@ -4277,7 +4277,7 @@ }, "x-appwrite": { "method": "getInitials", - "weight": 64, + "weight": 65, "cookies": false, "type": "location", "deprecated": false, @@ -4371,7 +4371,7 @@ }, "x-appwrite": { "method": "getQR", - "weight": 63, + "weight": 64, "cookies": false, "type": "location", "deprecated": false, @@ -4465,7 +4465,7 @@ }, "x-appwrite": { "method": "chat", - "weight": 331, + "weight": 332, "cookies": false, "type": "", "deprecated": false, @@ -4534,7 +4534,7 @@ }, "x-appwrite": { "method": "variables", - "weight": 330, + "weight": 331, "cookies": false, "type": "", "deprecated": false, @@ -4584,7 +4584,7 @@ }, "x-appwrite": { "method": "list", - "weight": 69, + "weight": 70, "cookies": false, "type": "", "deprecated": false, @@ -4659,7 +4659,7 @@ }, "x-appwrite": { "method": "create", - "weight": 68, + "weight": 69, "cookies": false, "type": "", "deprecated": false, @@ -4740,7 +4740,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 113, + "weight": 114, "cookies": false, "type": "", "deprecated": false, @@ -4814,7 +4814,7 @@ }, "x-appwrite": { "method": "get", - "weight": 70, + "weight": 71, "cookies": false, "type": "", "deprecated": false, @@ -4875,7 +4875,7 @@ }, "x-appwrite": { "method": "update", - "weight": 72, + "weight": 73, "cookies": false, "type": "", "deprecated": false, @@ -4953,7 +4953,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 73, + "weight": 74, "cookies": false, "type": "", "deprecated": false, @@ -5016,7 +5016,7 @@ }, "x-appwrite": { "method": "listCollections", - "weight": 75, + "weight": 76, "cookies": false, "type": "", "deprecated": false, @@ -5101,7 +5101,7 @@ }, "x-appwrite": { "method": "createCollection", - "weight": 74, + "weight": 75, "cookies": false, "type": "", "deprecated": false, @@ -5207,7 +5207,7 @@ }, "x-appwrite": { "method": "getCollection", - "weight": 76, + "weight": 77, "cookies": false, "type": "", "deprecated": false, @@ -5278,7 +5278,7 @@ }, "x-appwrite": { "method": "updateCollection", - "weight": 78, + "weight": 79, "cookies": false, "type": "", "deprecated": false, @@ -5379,7 +5379,7 @@ }, "x-appwrite": { "method": "deleteCollection", - "weight": 79, + "weight": 80, "cookies": false, "type": "", "deprecated": false, @@ -5452,7 +5452,7 @@ }, "x-appwrite": { "method": "listAttributes", - "weight": 90, + "weight": 91, "cookies": false, "type": "", "deprecated": false, @@ -5538,7 +5538,7 @@ }, "x-appwrite": { "method": "createBooleanAttribute", - "weight": 87, + "weight": 88, "cookies": false, "type": "", "deprecated": false, @@ -5646,7 +5646,7 @@ }, "x-appwrite": { "method": "updateBooleanAttribute", - "weight": 99, + "weight": 100, "cookies": false, "type": "", "deprecated": false, @@ -5759,7 +5759,7 @@ }, "x-appwrite": { "method": "createDatetimeAttribute", - "weight": 88, + "weight": 89, "cookies": false, "type": "", "deprecated": false, @@ -5867,7 +5867,7 @@ }, "x-appwrite": { "method": "updateDatetimeAttribute", - "weight": 100, + "weight": 101, "cookies": false, "type": "", "deprecated": false, @@ -5980,7 +5980,7 @@ }, "x-appwrite": { "method": "createEmailAttribute", - "weight": 81, + "weight": 82, "cookies": false, "type": "", "deprecated": false, @@ -6088,7 +6088,7 @@ }, "x-appwrite": { "method": "updateEmailAttribute", - "weight": 93, + "weight": 94, "cookies": false, "type": "", "deprecated": false, @@ -6201,7 +6201,7 @@ }, "x-appwrite": { "method": "createEnumAttribute", - "weight": 82, + "weight": 83, "cookies": false, "type": "", "deprecated": false, @@ -6318,7 +6318,7 @@ }, "x-appwrite": { "method": "updateEnumAttribute", - "weight": 94, + "weight": 95, "cookies": false, "type": "", "deprecated": false, @@ -6440,7 +6440,7 @@ }, "x-appwrite": { "method": "createFloatAttribute", - "weight": 86, + "weight": 87, "cookies": false, "type": "", "deprecated": false, @@ -6558,7 +6558,7 @@ }, "x-appwrite": { "method": "updateFloatAttribute", - "weight": 98, + "weight": 99, "cookies": false, "type": "", "deprecated": false, @@ -6683,7 +6683,7 @@ }, "x-appwrite": { "method": "createIntegerAttribute", - "weight": 85, + "weight": 86, "cookies": false, "type": "", "deprecated": false, @@ -6801,7 +6801,7 @@ }, "x-appwrite": { "method": "updateIntegerAttribute", - "weight": 97, + "weight": 98, "cookies": false, "type": "", "deprecated": false, @@ -6926,7 +6926,7 @@ }, "x-appwrite": { "method": "createIpAttribute", - "weight": 83, + "weight": 84, "cookies": false, "type": "", "deprecated": false, @@ -7034,7 +7034,7 @@ }, "x-appwrite": { "method": "updateIpAttribute", - "weight": 95, + "weight": 96, "cookies": false, "type": "", "deprecated": false, @@ -7147,7 +7147,7 @@ }, "x-appwrite": { "method": "createRelationshipAttribute", - "weight": 89, + "weight": 90, "cookies": false, "type": "", "deprecated": false, @@ -7280,7 +7280,7 @@ }, "x-appwrite": { "method": "createStringAttribute", - "weight": 80, + "weight": 81, "cookies": false, "type": "", "deprecated": false, @@ -7399,7 +7399,7 @@ }, "x-appwrite": { "method": "updateStringAttribute", - "weight": 92, + "weight": 93, "cookies": false, "type": "", "deprecated": false, @@ -7517,7 +7517,7 @@ }, "x-appwrite": { "method": "createUrlAttribute", - "weight": 84, + "weight": 85, "cookies": false, "type": "", "deprecated": false, @@ -7625,7 +7625,7 @@ }, "x-appwrite": { "method": "updateUrlAttribute", - "weight": 96, + "weight": 97, "cookies": false, "type": "", "deprecated": false, @@ -7769,7 +7769,7 @@ }, "x-appwrite": { "method": "getAttribute", - "weight": 91, + "weight": 92, "cookies": false, "type": "", "deprecated": false, @@ -7842,7 +7842,7 @@ }, "x-appwrite": { "method": "deleteAttribute", - "weight": 102, + "weight": 103, "cookies": false, "type": "", "deprecated": false, @@ -7924,7 +7924,7 @@ }, "x-appwrite": { "method": "updateRelationshipAttribute", - "weight": 101, + "weight": 102, "cookies": false, "type": "", "deprecated": false, @@ -8034,7 +8034,7 @@ }, "x-appwrite": { "method": "listDocuments", - "weight": 108, + "weight": 109, "cookies": false, "type": "", "deprecated": false, @@ -8121,7 +8121,7 @@ }, "x-appwrite": { "method": "createDocument", - "weight": 107, + "weight": 108, "cookies": false, "type": "", "deprecated": false, @@ -8230,7 +8230,7 @@ }, "x-appwrite": { "method": "getDocument", - "weight": 109, + "weight": 110, "cookies": false, "type": "", "deprecated": false, @@ -8327,7 +8327,7 @@ }, "x-appwrite": { "method": "updateDocument", - "weight": 111, + "weight": 112, "cookies": false, "type": "", "deprecated": false, @@ -8428,7 +8428,7 @@ }, "x-appwrite": { "method": "deleteDocument", - "weight": 112, + "weight": 113, "cookies": false, "type": "", "deprecated": false, @@ -8514,7 +8514,7 @@ }, "x-appwrite": { "method": "listDocumentLogs", - "weight": 110, + "weight": 111, "cookies": false, "type": "", "deprecated": false, @@ -8609,7 +8609,7 @@ }, "x-appwrite": { "method": "listIndexes", - "weight": 104, + "weight": 105, "cookies": false, "type": "", "deprecated": false, @@ -8693,7 +8693,7 @@ }, "x-appwrite": { "method": "createIndex", - "weight": 103, + "weight": 104, "cookies": false, "type": "", "deprecated": false, @@ -8815,7 +8815,7 @@ }, "x-appwrite": { "method": "getIndex", - "weight": 105, + "weight": 106, "cookies": false, "type": "", "deprecated": false, @@ -8888,7 +8888,7 @@ }, "x-appwrite": { "method": "deleteIndex", - "weight": 106, + "weight": 107, "cookies": false, "type": "", "deprecated": false, @@ -8970,7 +8970,7 @@ }, "x-appwrite": { "method": "listCollectionLogs", - "weight": 77, + "weight": 78, "cookies": false, "type": "", "deprecated": false, @@ -9055,7 +9055,7 @@ }, "x-appwrite": { "method": "getCollectionUsage", - "weight": 115, + "weight": 116, "cookies": false, "type": "", "deprecated": false, @@ -9149,7 +9149,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 71, + "weight": 72, "cookies": false, "type": "", "deprecated": false, @@ -9224,7 +9224,7 @@ }, "x-appwrite": { "method": "getDatabaseUsage", - "weight": 114, + "weight": 115, "cookies": false, "type": "", "deprecated": false, @@ -9308,7 +9308,7 @@ }, "x-appwrite": { "method": "list", - "weight": 287, + "weight": 288, "cookies": false, "type": "", "deprecated": false, @@ -9383,7 +9383,7 @@ }, "x-appwrite": { "method": "create", - "weight": 286, + "weight": 287, "cookies": false, "type": "", "deprecated": false, @@ -9437,6 +9437,7 @@ "node-19.0", "node-20.0", "node-21.0", + "node-22", "php-8.0", "php-8.1", "php-8.2", @@ -9455,6 +9456,8 @@ "deno-1.24", "deno-1.35", "deno-1.40", + "deno-1.46", + "deno-2.0", "dart-2.15", "dart-2.16", "dart-2.17", @@ -9462,23 +9465,28 @@ "dart-3.0", "dart-3.1", "dart-3.3", - "dotnet-3.1", + "dart-3.5", "dotnet-6.0", "dotnet-7.0", + "dotnet-8.0", "java-8.0", "java-11.0", "java-17.0", "java-18.0", "java-21.0", + "java-22", "swift-5.5", "swift-5.8", "swift-5.9", + "swift-5.10", "kotlin-1.6", "kotlin-1.8", "kotlin-1.9", + "kotlin-2.0", "cpp-17", "cpp-20", "bun-1.0", + "bun-1.1", "go-1.23" ], "x-enum-name": null, @@ -9622,7 +9630,7 @@ }, "x-appwrite": { "method": "listRuntimes", - "weight": 288, + "weight": 289, "cookies": false, "type": "", "deprecated": false, @@ -9673,7 +9681,7 @@ }, "x-appwrite": { "method": "listSpecifications", - "weight": 289, + "weight": 290, "cookies": false, "type": "", "deprecated": false, @@ -9725,7 +9733,7 @@ }, "x-appwrite": { "method": "listTemplates", - "weight": 312, + "weight": 313, "cookies": false, "type": "", "deprecated": false, @@ -9827,7 +9835,7 @@ }, "x-appwrite": { "method": "getTemplate", - "weight": 313, + "weight": 314, "cookies": false, "type": "", "deprecated": false, @@ -9889,7 +9897,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 292, + "weight": 293, "cookies": false, "type": "", "deprecated": false, @@ -9963,7 +9971,7 @@ }, "x-appwrite": { "method": "get", - "weight": 290, + "weight": 291, "cookies": false, "type": "", "deprecated": false, @@ -10024,7 +10032,7 @@ }, "x-appwrite": { "method": "update", - "weight": 293, + "weight": 294, "cookies": false, "type": "", "deprecated": false, @@ -10085,6 +10093,7 @@ "node-19.0", "node-20.0", "node-21.0", + "node-22", "php-8.0", "php-8.1", "php-8.2", @@ -10103,6 +10112,8 @@ "deno-1.24", "deno-1.35", "deno-1.40", + "deno-1.46", + "deno-2.0", "dart-2.15", "dart-2.16", "dart-2.17", @@ -10110,23 +10121,28 @@ "dart-3.0", "dart-3.1", "dart-3.3", - "dotnet-3.1", + "dart-3.5", "dotnet-6.0", "dotnet-7.0", + "dotnet-8.0", "java-8.0", "java-11.0", "java-17.0", "java-18.0", "java-21.0", + "java-22", "swift-5.5", "swift-5.8", "swift-5.9", + "swift-5.10", "kotlin-1.6", "kotlin-1.8", "kotlin-1.9", + "kotlin-2.0", "cpp-17", "cpp-20", "bun-1.0", + "bun-1.1", "go-1.23" ], "x-enum-name": null, @@ -10240,7 +10256,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 296, + "weight": 297, "cookies": false, "type": "", "deprecated": false, @@ -10303,7 +10319,7 @@ }, "x-appwrite": { "method": "listDeployments", - "weight": 298, + "weight": 299, "cookies": false, "type": "", "deprecated": false, @@ -10388,7 +10404,7 @@ }, "x-appwrite": { "method": "createDeployment", - "weight": 297, + "weight": 298, "cookies": false, "type": "upload", "deprecated": false, @@ -10486,7 +10502,7 @@ }, "x-appwrite": { "method": "getDeployment", - "weight": 299, + "weight": 300, "cookies": false, "type": "", "deprecated": false, @@ -10557,7 +10573,7 @@ }, "x-appwrite": { "method": "updateDeployment", - "weight": 295, + "weight": 296, "cookies": false, "type": "", "deprecated": false, @@ -10621,7 +10637,7 @@ }, "x-appwrite": { "method": "deleteDeployment", - "weight": 300, + "weight": 301, "cookies": false, "type": "", "deprecated": false, @@ -10687,7 +10703,7 @@ }, "x-appwrite": { "method": "createBuild", - "weight": 301, + "weight": 302, "cookies": false, "type": "", "deprecated": false, @@ -10774,7 +10790,7 @@ }, "x-appwrite": { "method": "updateDeploymentBuild", - "weight": 302, + "weight": 303, "cookies": false, "type": "", "deprecated": false, @@ -10840,7 +10856,7 @@ }, "x-appwrite": { "method": "getDeploymentDownload", - "weight": 294, + "weight": 295, "cookies": false, "type": "location", "deprecated": false, @@ -10915,7 +10931,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 304, + "weight": 305, "cookies": false, "type": "", "deprecated": false, @@ -11003,7 +11019,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 303, + "weight": 304, "cookies": false, "type": "", "deprecated": false, @@ -11120,7 +11136,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 305, + "weight": 306, "cookies": false, "type": "", "deprecated": false, @@ -11187,7 +11203,7 @@ }, "x-appwrite": { "method": "deleteExecution", - "weight": 306, + "weight": 307, "cookies": false, "type": "", "deprecated": false, @@ -11260,7 +11276,7 @@ }, "x-appwrite": { "method": "getFunctionUsage", - "weight": 291, + "weight": 292, "cookies": false, "type": "", "deprecated": false, @@ -11344,7 +11360,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 308, + "weight": 309, "cookies": false, "type": "", "deprecated": false, @@ -11405,7 +11421,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 307, + "weight": 308, "cookies": false, "type": "", "deprecated": false, @@ -11493,7 +11509,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 309, + "weight": 310, "cookies": false, "type": "", "deprecated": false, @@ -11564,7 +11580,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 310, + "weight": 311, "cookies": false, "type": "", "deprecated": false, @@ -11652,7 +11668,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 311, + "weight": 312, "cookies": false, "type": "", "deprecated": false, @@ -11725,7 +11741,7 @@ }, "x-appwrite": { "method": "query", - "weight": 329, + "weight": 330, "cookies": false, "type": "graphql", "deprecated": false, @@ -11779,7 +11795,7 @@ }, "x-appwrite": { "method": "mutation", - "weight": 328, + "weight": 329, "cookies": false, "type": "graphql", "deprecated": false, @@ -11833,7 +11849,7 @@ }, "x-appwrite": { "method": "get", - "weight": 124, + "weight": 125, "cookies": false, "type": "", "deprecated": false, @@ -11884,7 +11900,7 @@ }, "x-appwrite": { "method": "getAntivirus", - "weight": 146, + "weight": 147, "cookies": false, "type": "", "deprecated": false, @@ -11935,7 +11951,7 @@ }, "x-appwrite": { "method": "getCache", - "weight": 127, + "weight": 128, "cookies": false, "type": "", "deprecated": false, @@ -11986,7 +12002,7 @@ }, "x-appwrite": { "method": "getCertificate", - "weight": 133, + "weight": 134, "cookies": false, "type": "", "deprecated": false, @@ -12048,7 +12064,7 @@ }, "x-appwrite": { "method": "getDB", - "weight": 126, + "weight": 127, "cookies": false, "type": "", "deprecated": false, @@ -12099,7 +12115,7 @@ }, "x-appwrite": { "method": "getPubSub", - "weight": 129, + "weight": 130, "cookies": false, "type": "", "deprecated": false, @@ -12150,7 +12166,7 @@ }, "x-appwrite": { "method": "getQueue", - "weight": 128, + "weight": 129, "cookies": false, "type": "", "deprecated": false, @@ -12201,7 +12217,7 @@ }, "x-appwrite": { "method": "getQueueBuilds", - "weight": 135, + "weight": 136, "cookies": false, "type": "", "deprecated": false, @@ -12265,7 +12281,7 @@ }, "x-appwrite": { "method": "getQueueCertificates", - "weight": 134, + "weight": 135, "cookies": false, "type": "", "deprecated": false, @@ -12329,7 +12345,7 @@ }, "x-appwrite": { "method": "getQueueDatabases", - "weight": 136, + "weight": 137, "cookies": false, "type": "", "deprecated": false, @@ -12404,7 +12420,7 @@ }, "x-appwrite": { "method": "getQueueDeletes", - "weight": 137, + "weight": 138, "cookies": false, "type": "", "deprecated": false, @@ -12468,7 +12484,7 @@ }, "x-appwrite": { "method": "getFailedJobs", - "weight": 147, + "weight": 148, "cookies": false, "type": "", "deprecated": false, @@ -12558,7 +12574,7 @@ }, "x-appwrite": { "method": "getQueueFunctions", - "weight": 141, + "weight": 142, "cookies": false, "type": "", "deprecated": false, @@ -12622,7 +12638,7 @@ }, "x-appwrite": { "method": "getQueueLogs", - "weight": 132, + "weight": 133, "cookies": false, "type": "", "deprecated": false, @@ -12686,7 +12702,7 @@ }, "x-appwrite": { "method": "getQueueMails", - "weight": 138, + "weight": 139, "cookies": false, "type": "", "deprecated": false, @@ -12750,7 +12766,7 @@ }, "x-appwrite": { "method": "getQueueMessaging", - "weight": 139, + "weight": 140, "cookies": false, "type": "", "deprecated": false, @@ -12814,7 +12830,7 @@ }, "x-appwrite": { "method": "getQueueMigrations", - "weight": 140, + "weight": 141, "cookies": false, "type": "", "deprecated": false, @@ -12878,7 +12894,7 @@ }, "x-appwrite": { "method": "getQueueUsage", - "weight": 142, + "weight": 143, "cookies": false, "type": "", "deprecated": false, @@ -12942,7 +12958,7 @@ }, "x-appwrite": { "method": "getQueueUsageDump", - "weight": 143, + "weight": 144, "cookies": false, "type": "", "deprecated": false, @@ -13006,7 +13022,7 @@ }, "x-appwrite": { "method": "getQueueWebhooks", - "weight": 131, + "weight": 132, "cookies": false, "type": "", "deprecated": false, @@ -13070,7 +13086,7 @@ }, "x-appwrite": { "method": "getStorage", - "weight": 145, + "weight": 146, "cookies": false, "type": "", "deprecated": false, @@ -13121,7 +13137,7 @@ }, "x-appwrite": { "method": "getStorageLocal", - "weight": 144, + "weight": 145, "cookies": false, "type": "", "deprecated": false, @@ -13172,7 +13188,7 @@ }, "x-appwrite": { "method": "getTime", - "weight": 130, + "weight": 131, "cookies": false, "type": "", "deprecated": false, @@ -13223,7 +13239,7 @@ }, "x-appwrite": { "method": "get", - "weight": 116, + "weight": 117, "cookies": false, "type": "", "deprecated": false, @@ -13277,7 +13293,7 @@ }, "x-appwrite": { "method": "listCodes", - "weight": 117, + "weight": 118, "cookies": false, "type": "", "deprecated": false, @@ -13331,7 +13347,7 @@ }, "x-appwrite": { "method": "listContinents", - "weight": 121, + "weight": 122, "cookies": false, "type": "", "deprecated": false, @@ -13385,7 +13401,7 @@ }, "x-appwrite": { "method": "listCountries", - "weight": 118, + "weight": 119, "cookies": false, "type": "", "deprecated": false, @@ -13439,7 +13455,7 @@ }, "x-appwrite": { "method": "listCountriesEU", - "weight": 119, + "weight": 120, "cookies": false, "type": "", "deprecated": false, @@ -13493,7 +13509,7 @@ }, "x-appwrite": { "method": "listCountriesPhones", - "weight": 120, + "weight": 121, "cookies": false, "type": "", "deprecated": false, @@ -13547,7 +13563,7 @@ }, "x-appwrite": { "method": "listCurrencies", - "weight": 122, + "weight": 123, "cookies": false, "type": "", "deprecated": false, @@ -13601,7 +13617,7 @@ }, "x-appwrite": { "method": "listLanguages", - "weight": 123, + "weight": 124, "cookies": false, "type": "", "deprecated": false, @@ -13655,7 +13671,7 @@ }, "x-appwrite": { "method": "listMessages", - "weight": 388, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -13733,7 +13749,7 @@ }, "x-appwrite": { "method": "createEmail", - "weight": 385, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -13879,7 +13895,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 392, + "weight": 393, "cookies": false, "type": "", "deprecated": false, @@ -14027,7 +14043,7 @@ }, "x-appwrite": { "method": "createPush", - "weight": 387, + "weight": 388, "cookies": false, "type": "", "deprecated": false, @@ -14184,7 +14200,7 @@ }, "x-appwrite": { "method": "updatePush", - "weight": 394, + "weight": 395, "cookies": false, "type": "", "deprecated": false, @@ -14343,7 +14359,7 @@ }, "x-appwrite": { "method": "createSms", - "weight": 386, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -14454,7 +14470,7 @@ }, "x-appwrite": { "method": "updateSms", - "weight": 393, + "weight": 394, "cookies": false, "type": "", "deprecated": false, @@ -14568,7 +14584,7 @@ }, "x-appwrite": { "method": "getMessage", - "weight": 391, + "weight": 392, "cookies": false, "type": "", "deprecated": false, @@ -14623,7 +14639,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 395, + "weight": 396, "cookies": false, "type": "", "deprecated": false, @@ -14687,7 +14703,7 @@ }, "x-appwrite": { "method": "listMessageLogs", - "weight": 389, + "weight": 390, "cookies": false, "type": "", "deprecated": false, @@ -14764,7 +14780,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 390, + "weight": 391, "cookies": false, "type": "", "deprecated": false, @@ -14841,7 +14857,7 @@ }, "x-appwrite": { "method": "listProviders", - "weight": 360, + "weight": 361, "cookies": false, "type": "", "deprecated": false, @@ -14919,7 +14935,7 @@ }, "x-appwrite": { "method": "createApnsProvider", - "weight": 359, + "weight": 360, "cookies": false, "type": "", "deprecated": false, @@ -15026,7 +15042,7 @@ }, "x-appwrite": { "method": "updateApnsProvider", - "weight": 372, + "weight": 373, "cookies": false, "type": "", "deprecated": false, @@ -15136,7 +15152,7 @@ }, "x-appwrite": { "method": "createFcmProvider", - "weight": 358, + "weight": 359, "cookies": false, "type": "", "deprecated": false, @@ -15223,7 +15239,7 @@ }, "x-appwrite": { "method": "updateFcmProvider", - "weight": 371, + "weight": 372, "cookies": false, "type": "", "deprecated": false, @@ -15313,7 +15329,7 @@ }, "x-appwrite": { "method": "createMailgunProvider", - "weight": 350, + "weight": 351, "cookies": false, "type": "", "deprecated": false, @@ -15430,7 +15446,7 @@ }, "x-appwrite": { "method": "updateMailgunProvider", - "weight": 363, + "weight": 364, "cookies": false, "type": "", "deprecated": false, @@ -15550,7 +15566,7 @@ }, "x-appwrite": { "method": "createMsg91Provider", - "weight": 353, + "weight": 354, "cookies": false, "type": "", "deprecated": false, @@ -15647,7 +15663,7 @@ }, "x-appwrite": { "method": "updateMsg91Provider", - "weight": 366, + "weight": 367, "cookies": false, "type": "", "deprecated": false, @@ -15747,7 +15763,7 @@ }, "x-appwrite": { "method": "createSendgridProvider", - "weight": 351, + "weight": 352, "cookies": false, "type": "", "deprecated": false, @@ -15854,7 +15870,7 @@ }, "x-appwrite": { "method": "updateSendgridProvider", - "weight": 364, + "weight": 365, "cookies": false, "type": "", "deprecated": false, @@ -15964,7 +15980,7 @@ }, "x-appwrite": { "method": "createSmtpProvider", - "weight": 352, + "weight": 353, "cookies": false, "type": "", "deprecated": false, @@ -16109,7 +16125,7 @@ }, "x-appwrite": { "method": "updateSmtpProvider", - "weight": 365, + "weight": 366, "cookies": false, "type": "", "deprecated": false, @@ -16256,7 +16272,7 @@ }, "x-appwrite": { "method": "createTelesignProvider", - "weight": 354, + "weight": 355, "cookies": false, "type": "", "deprecated": false, @@ -16353,7 +16369,7 @@ }, "x-appwrite": { "method": "updateTelesignProvider", - "weight": 367, + "weight": 368, "cookies": false, "type": "", "deprecated": false, @@ -16453,7 +16469,7 @@ }, "x-appwrite": { "method": "createTextmagicProvider", - "weight": 355, + "weight": 356, "cookies": false, "type": "", "deprecated": false, @@ -16550,7 +16566,7 @@ }, "x-appwrite": { "method": "updateTextmagicProvider", - "weight": 368, + "weight": 369, "cookies": false, "type": "", "deprecated": false, @@ -16650,7 +16666,7 @@ }, "x-appwrite": { "method": "createTwilioProvider", - "weight": 356, + "weight": 357, "cookies": false, "type": "", "deprecated": false, @@ -16747,7 +16763,7 @@ }, "x-appwrite": { "method": "updateTwilioProvider", - "weight": 369, + "weight": 370, "cookies": false, "type": "", "deprecated": false, @@ -16847,7 +16863,7 @@ }, "x-appwrite": { "method": "createVonageProvider", - "weight": 357, + "weight": 358, "cookies": false, "type": "", "deprecated": false, @@ -16944,7 +16960,7 @@ }, "x-appwrite": { "method": "updateVonageProvider", - "weight": 370, + "weight": 371, "cookies": false, "type": "", "deprecated": false, @@ -17044,7 +17060,7 @@ }, "x-appwrite": { "method": "getProvider", - "weight": 362, + "weight": 363, "cookies": false, "type": "", "deprecated": false, @@ -17099,7 +17115,7 @@ }, "x-appwrite": { "method": "deleteProvider", - "weight": 373, + "weight": 374, "cookies": false, "type": "", "deprecated": false, @@ -17163,7 +17179,7 @@ }, "x-appwrite": { "method": "listProviderLogs", - "weight": 361, + "weight": 362, "cookies": false, "type": "", "deprecated": false, @@ -17240,7 +17256,7 @@ }, "x-appwrite": { "method": "listSubscriberLogs", - "weight": 382, + "weight": 383, "cookies": false, "type": "", "deprecated": false, @@ -17317,7 +17333,7 @@ }, "x-appwrite": { "method": "listTopics", - "weight": 375, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -17393,7 +17409,7 @@ }, "x-appwrite": { "method": "createTopic", - "weight": 374, + "weight": 375, "cookies": false, "type": "", "deprecated": false, @@ -17478,7 +17494,7 @@ }, "x-appwrite": { "method": "getTopic", - "weight": 377, + "weight": 378, "cookies": false, "type": "", "deprecated": false, @@ -17540,7 +17556,7 @@ }, "x-appwrite": { "method": "updateTopic", - "weight": 378, + "weight": 379, "cookies": false, "type": "", "deprecated": false, @@ -17619,7 +17635,7 @@ }, "x-appwrite": { "method": "deleteTopic", - "weight": 379, + "weight": 380, "cookies": false, "type": "", "deprecated": false, @@ -17683,7 +17699,7 @@ }, "x-appwrite": { "method": "listTopicLogs", - "weight": 376, + "weight": 377, "cookies": false, "type": "", "deprecated": false, @@ -17760,7 +17776,7 @@ }, "x-appwrite": { "method": "listSubscribers", - "weight": 381, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -17846,7 +17862,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 380, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -17938,7 +17954,7 @@ }, "x-appwrite": { "method": "getSubscriber", - "weight": 383, + "weight": 384, "cookies": false, "type": "", "deprecated": false, @@ -18003,7 +18019,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 384, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -18080,7 +18096,7 @@ }, "x-appwrite": { "method": "list", - "weight": 337, + "weight": 338, "cookies": false, "type": "", "deprecated": false, @@ -18156,7 +18172,7 @@ }, "x-appwrite": { "method": "createAppwriteMigration", - "weight": 332, + "weight": 333, "cookies": false, "type": "", "deprecated": false, @@ -18246,7 +18262,7 @@ }, "x-appwrite": { "method": "getAppwriteReport", - "weight": 339, + "weight": 340, "cookies": false, "type": "", "deprecated": false, @@ -18341,7 +18357,7 @@ }, "x-appwrite": { "method": "createFirebaseMigration", - "weight": 334, + "weight": 335, "cookies": false, "type": "", "deprecated": false, @@ -18412,7 +18428,7 @@ }, "x-appwrite": { "method": "deleteFirebaseAuth", - "weight": 345, + "weight": 346, "cookies": false, "type": "", "deprecated": false, @@ -18462,7 +18478,7 @@ }, "x-appwrite": { "method": "createFirebaseOAuthMigration", - "weight": 333, + "weight": 334, "cookies": false, "type": "", "deprecated": false, @@ -18540,7 +18556,7 @@ }, "x-appwrite": { "method": "listFirebaseProjects", - "weight": 344, + "weight": 345, "cookies": false, "type": "", "deprecated": false, @@ -18590,7 +18606,7 @@ }, "x-appwrite": { "method": "getFirebaseReport", - "weight": 340, + "weight": 341, "cookies": false, "type": "", "deprecated": false, @@ -18664,7 +18680,7 @@ }, "x-appwrite": { "method": "getFirebaseReportOAuth", - "weight": 341, + "weight": 342, "cookies": false, "type": "", "deprecated": false, @@ -18738,7 +18754,7 @@ }, "x-appwrite": { "method": "createNHostMigration", - "weight": 336, + "weight": 337, "cookies": false, "type": "", "deprecated": false, @@ -18851,7 +18867,7 @@ }, "x-appwrite": { "method": "getNHostReport", - "weight": 347, + "weight": 348, "cookies": false, "type": "", "deprecated": false, @@ -18986,7 +19002,7 @@ }, "x-appwrite": { "method": "createSupabaseMigration", - "weight": 335, + "weight": 336, "cookies": false, "type": "", "deprecated": false, @@ -19093,7 +19109,7 @@ }, "x-appwrite": { "method": "getSupabaseReport", - "weight": 346, + "weight": 347, "cookies": false, "type": "", "deprecated": false, @@ -19219,7 +19235,7 @@ }, "x-appwrite": { "method": "get", - "weight": 338, + "weight": 339, "cookies": false, "type": "", "deprecated": false, @@ -19279,7 +19295,7 @@ }, "x-appwrite": { "method": "retry", - "weight": 348, + "weight": 349, "cookies": false, "type": "", "deprecated": false, @@ -19332,7 +19348,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 349, + "weight": 350, "cookies": false, "type": "", "deprecated": false, @@ -19394,7 +19410,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 194, + "weight": 195, "cookies": false, "type": "", "deprecated": false, @@ -19484,7 +19500,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 196, + "weight": 197, "cookies": false, "type": "", "deprecated": false, @@ -19532,7 +19548,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 195, + "weight": 196, "cookies": false, "type": "", "deprecated": false, @@ -19607,7 +19623,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 197, + "weight": 198, "cookies": false, "type": "", "deprecated": false, @@ -19667,7 +19683,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 198, + "weight": 199, "cookies": false, "type": "", "deprecated": false, @@ -19744,7 +19760,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 199, + "weight": 200, "cookies": false, "type": "", "deprecated": false, @@ -19806,7 +19822,7 @@ }, "x-appwrite": { "method": "list", - "weight": 150, + "weight": 151, "cookies": false, "type": "", "deprecated": false, @@ -19880,7 +19896,7 @@ }, "x-appwrite": { "method": "create", - "weight": 149, + "weight": 150, "cookies": false, "type": "", "deprecated": false, @@ -20017,7 +20033,7 @@ }, "x-appwrite": { "method": "get", - "weight": 151, + "weight": 152, "cookies": false, "type": "", "deprecated": false, @@ -20077,7 +20093,7 @@ }, "x-appwrite": { "method": "update", - "weight": 152, + "weight": 153, "cookies": false, "type": "", "deprecated": false, @@ -20194,7 +20210,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 168, + "weight": 169, "cookies": false, "type": "", "deprecated": false, @@ -20256,7 +20272,7 @@ }, "x-appwrite": { "method": "updateApiStatus", - "weight": 156, + "weight": 157, "cookies": false, "type": "", "deprecated": false, @@ -20350,7 +20366,7 @@ }, "x-appwrite": { "method": "updateApiStatusAll", - "weight": 157, + "weight": 158, "cookies": false, "type": "", "deprecated": false, @@ -20431,7 +20447,7 @@ }, "x-appwrite": { "method": "updateAuthDuration", - "weight": 161, + "weight": 162, "cookies": false, "type": "", "deprecated": false, @@ -20512,7 +20528,7 @@ }, "x-appwrite": { "method": "updateAuthLimit", - "weight": 160, + "weight": 161, "cookies": false, "type": "", "deprecated": false, @@ -20593,7 +20609,7 @@ }, "x-appwrite": { "method": "updateAuthSessionsLimit", - "weight": 166, + "weight": 167, "cookies": false, "type": "", "deprecated": false, @@ -20674,7 +20690,7 @@ }, "x-appwrite": { "method": "updateMockNumbers", - "weight": 167, + "weight": 168, "cookies": false, "type": "", "deprecated": false, @@ -20758,7 +20774,7 @@ }, "x-appwrite": { "method": "updateAuthPasswordDictionary", - "weight": 164, + "weight": 165, "cookies": false, "type": "", "deprecated": false, @@ -20839,7 +20855,7 @@ }, "x-appwrite": { "method": "updateAuthPasswordHistory", - "weight": 163, + "weight": 164, "cookies": false, "type": "", "deprecated": false, @@ -20920,7 +20936,7 @@ }, "x-appwrite": { "method": "updatePersonalDataCheck", - "weight": 165, + "weight": 166, "cookies": false, "type": "", "deprecated": false, @@ -21001,7 +21017,7 @@ }, "x-appwrite": { "method": "updateSessionAlerts", - "weight": 159, + "weight": 160, "cookies": false, "type": "", "deprecated": false, @@ -21082,7 +21098,7 @@ }, "x-appwrite": { "method": "updateAuthStatus", - "weight": 162, + "weight": 163, "cookies": false, "type": "", "deprecated": false, @@ -21184,7 +21200,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 180, + "weight": 181, "cookies": false, "type": "", "deprecated": false, @@ -21273,7 +21289,7 @@ }, "x-appwrite": { "method": "listKeys", - "weight": 176, + "weight": 177, "cookies": false, "type": "", "deprecated": false, @@ -21333,7 +21349,7 @@ }, "x-appwrite": { "method": "createKey", - "weight": 175, + "weight": 176, "cookies": false, "type": "", "deprecated": false, @@ -21428,7 +21444,7 @@ }, "x-appwrite": { "method": "getKey", - "weight": 177, + "weight": 178, "cookies": false, "type": "", "deprecated": false, @@ -21498,7 +21514,7 @@ }, "x-appwrite": { "method": "updateKey", - "weight": 178, + "weight": 179, "cookies": false, "type": "", "deprecated": false, @@ -21594,7 +21610,7 @@ }, "x-appwrite": { "method": "deleteKey", - "weight": 179, + "weight": 180, "cookies": false, "type": "", "deprecated": false, @@ -21666,7 +21682,7 @@ }, "x-appwrite": { "method": "updateOAuth2", - "weight": 158, + "weight": 159, "cookies": false, "type": "", "deprecated": false, @@ -21805,7 +21821,7 @@ }, "x-appwrite": { "method": "listPlatforms", - "weight": 182, + "weight": 183, "cookies": false, "type": "", "deprecated": false, @@ -21865,7 +21881,7 @@ }, "x-appwrite": { "method": "createPlatform", - "weight": 181, + "weight": 182, "cookies": false, "type": "", "deprecated": false, @@ -21986,7 +22002,7 @@ }, "x-appwrite": { "method": "getPlatform", - "weight": 183, + "weight": 184, "cookies": false, "type": "", "deprecated": false, @@ -22056,7 +22072,7 @@ }, "x-appwrite": { "method": "updatePlatform", - "weight": 184, + "weight": 185, "cookies": false, "type": "", "deprecated": false, @@ -22153,7 +22169,7 @@ }, "x-appwrite": { "method": "deletePlatform", - "weight": 185, + "weight": 186, "cookies": false, "type": "", "deprecated": false, @@ -22225,7 +22241,7 @@ }, "x-appwrite": { "method": "updateServiceStatus", - "weight": 154, + "weight": 155, "cookies": false, "type": "", "deprecated": false, @@ -22327,7 +22343,7 @@ }, "x-appwrite": { "method": "updateServiceStatusAll", - "weight": 155, + "weight": 156, "cookies": false, "type": "", "deprecated": false, @@ -22408,7 +22424,7 @@ }, "x-appwrite": { "method": "updateSmtp", - "weight": 186, + "weight": 187, "cookies": false, "type": "", "deprecated": false, @@ -22528,7 +22544,7 @@ }, "x-appwrite": { "method": "createSmtpTest", - "weight": 187, + "weight": 188, "cookies": false, "type": "", "deprecated": false, @@ -22661,7 +22677,7 @@ }, "x-appwrite": { "method": "updateTeam", - "weight": 153, + "weight": 154, "cookies": false, "type": "", "deprecated": false, @@ -22742,7 +22758,7 @@ }, "x-appwrite": { "method": "getEmailTemplate", - "weight": 189, + "weight": 190, "cookies": false, "type": "", "deprecated": false, @@ -22968,7 +22984,7 @@ }, "x-appwrite": { "method": "updateEmailTemplate", - "weight": 191, + "weight": 192, "cookies": false, "type": "", "deprecated": false, @@ -23234,7 +23250,7 @@ }, "x-appwrite": { "method": "deleteEmailTemplate", - "weight": 193, + "weight": 194, "cookies": false, "type": "", "deprecated": false, @@ -23462,7 +23478,7 @@ }, "x-appwrite": { "method": "getSmsTemplate", - "weight": 188, + "weight": 189, "cookies": false, "type": "", "deprecated": false, @@ -23685,7 +23701,7 @@ }, "x-appwrite": { "method": "updateSmsTemplate", - "weight": 190, + "weight": 191, "cookies": false, "type": "", "deprecated": false, @@ -23927,7 +23943,7 @@ }, "x-appwrite": { "method": "deleteSmsTemplate", - "weight": 192, + "weight": 193, "cookies": false, "type": "", "deprecated": false, @@ -24152,7 +24168,7 @@ }, "x-appwrite": { "method": "listWebhooks", - "weight": 170, + "weight": 171, "cookies": false, "type": "", "deprecated": false, @@ -24212,7 +24228,7 @@ }, "x-appwrite": { "method": "createWebhook", - "weight": 169, + "weight": 170, "cookies": false, "type": "", "deprecated": false, @@ -24329,7 +24345,7 @@ }, "x-appwrite": { "method": "getWebhook", - "weight": 171, + "weight": 172, "cookies": false, "type": "", "deprecated": false, @@ -24399,7 +24415,7 @@ }, "x-appwrite": { "method": "updateWebhook", - "weight": 172, + "weight": 173, "cookies": false, "type": "", "deprecated": false, @@ -24517,7 +24533,7 @@ }, "x-appwrite": { "method": "deleteWebhook", - "weight": 174, + "weight": 175, "cookies": false, "type": "", "deprecated": false, @@ -24589,7 +24605,7 @@ }, "x-appwrite": { "method": "updateWebhookSignature", - "weight": 173, + "weight": 174, "cookies": false, "type": "", "deprecated": false, @@ -24661,7 +24677,7 @@ }, "x-appwrite": { "method": "listRules", - "weight": 315, + "weight": 316, "cookies": false, "type": "", "deprecated": false, @@ -24735,7 +24751,7 @@ }, "x-appwrite": { "method": "createRule", - "weight": 314, + "weight": 315, "cookies": false, "type": "", "deprecated": false, @@ -24821,7 +24837,7 @@ }, "x-appwrite": { "method": "getRule", - "weight": 316, + "weight": 317, "cookies": false, "type": "", "deprecated": false, @@ -24874,7 +24890,7 @@ }, "x-appwrite": { "method": "deleteRule", - "weight": 317, + "weight": 318, "cookies": false, "type": "", "deprecated": false, @@ -24936,7 +24952,7 @@ }, "x-appwrite": { "method": "updateRuleVerification", - "weight": 318, + "weight": 319, "cookies": false, "type": "", "deprecated": false, @@ -24998,7 +25014,7 @@ }, "x-appwrite": { "method": "listBuckets", - "weight": 201, + "weight": 202, "cookies": false, "type": "", "deprecated": false, @@ -25073,7 +25089,7 @@ }, "x-appwrite": { "method": "createBucket", - "weight": 200, + "weight": 201, "cookies": false, "type": "", "deprecated": false, @@ -25202,7 +25218,7 @@ }, "x-appwrite": { "method": "getBucket", - "weight": 202, + "weight": 203, "cookies": false, "type": "", "deprecated": false, @@ -25263,7 +25279,7 @@ }, "x-appwrite": { "method": "updateBucket", - "weight": 203, + "weight": 204, "cookies": false, "type": "", "deprecated": false, @@ -25389,7 +25405,7 @@ }, "x-appwrite": { "method": "deleteBucket", - "weight": 204, + "weight": 205, "cookies": false, "type": "", "deprecated": false, @@ -25452,7 +25468,7 @@ }, "x-appwrite": { "method": "listFiles", - "weight": 206, + "weight": 207, "cookies": false, "type": "", "deprecated": false, @@ -25540,7 +25556,7 @@ }, "x-appwrite": { "method": "createFile", - "weight": 205, + "weight": 206, "cookies": false, "type": "upload", "deprecated": false, @@ -25640,7 +25656,7 @@ }, "x-appwrite": { "method": "getFile", - "weight": 207, + "weight": 208, "cookies": false, "type": "", "deprecated": false, @@ -25714,7 +25730,7 @@ }, "x-appwrite": { "method": "updateFile", - "weight": 212, + "weight": 213, "cookies": false, "type": "", "deprecated": false, @@ -25805,7 +25821,7 @@ }, "x-appwrite": { "method": "deleteFile", - "weight": 213, + "weight": 214, "cookies": false, "type": "", "deprecated": false, @@ -25874,7 +25890,7 @@ }, "x-appwrite": { "method": "getFileDownload", - "weight": 209, + "weight": 210, "cookies": false, "type": "location", "deprecated": false, @@ -25943,7 +25959,7 @@ }, "x-appwrite": { "method": "getFilePreview", - "weight": 208, + "weight": 209, "cookies": false, "type": "location", "deprecated": false, @@ -26161,7 +26177,7 @@ }, "x-appwrite": { "method": "getFileView", - "weight": 210, + "weight": 211, "cookies": false, "type": "location", "deprecated": false, @@ -26237,7 +26253,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 214, + "weight": 215, "cookies": false, "type": "", "deprecated": false, @@ -26311,7 +26327,7 @@ }, "x-appwrite": { "method": "getBucketUsage", - "weight": 215, + "weight": 216, "cookies": false, "type": "", "deprecated": false, @@ -26395,7 +26411,7 @@ }, "x-appwrite": { "method": "list", - "weight": 217, + "weight": 218, "cookies": false, "type": "", "deprecated": false, @@ -26473,7 +26489,7 @@ }, "x-appwrite": { "method": "create", - "weight": 216, + "weight": 217, "cookies": false, "type": "", "deprecated": false, @@ -26560,7 +26576,7 @@ }, "x-appwrite": { "method": "get", - "weight": 218, + "weight": 219, "cookies": false, "type": "", "deprecated": false, @@ -26624,7 +26640,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 220, + "weight": 221, "cookies": false, "type": "", "deprecated": false, @@ -26700,7 +26716,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 222, + "weight": 223, "cookies": false, "type": "", "deprecated": false, @@ -26766,7 +26782,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 229, + "weight": 230, "cookies": false, "type": "", "deprecated": false, @@ -26841,7 +26857,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 224, + "weight": 225, "cookies": false, "type": "", "deprecated": false, @@ -26929,7 +26945,7 @@ }, "x-appwrite": { "method": "createMembership", - "weight": 223, + "weight": 224, "cookies": false, "type": "", "deprecated": false, @@ -27042,7 +27058,7 @@ }, "x-appwrite": { "method": "getMembership", - "weight": 225, + "weight": 226, "cookies": false, "type": "", "deprecated": false, @@ -27116,7 +27132,7 @@ }, "x-appwrite": { "method": "updateMembership", - "weight": 226, + "weight": 227, "cookies": false, "type": "", "deprecated": false, @@ -27205,7 +27221,7 @@ }, "x-appwrite": { "method": "deleteMembership", - "weight": 228, + "weight": 229, "cookies": false, "type": "", "deprecated": false, @@ -27281,7 +27297,7 @@ }, "x-appwrite": { "method": "updateMembershipStatus", - "weight": 227, + "weight": 228, "cookies": false, "type": "", "deprecated": false, @@ -27380,7 +27396,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 219, + "weight": 220, "cookies": false, "type": "", "deprecated": false, @@ -27442,7 +27458,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 221, + "weight": 222, "cookies": false, "type": "", "deprecated": false, @@ -27525,7 +27541,7 @@ }, "x-appwrite": { "method": "list", - "weight": 239, + "weight": 240, "cookies": false, "type": "", "deprecated": false, @@ -27600,7 +27616,7 @@ }, "x-appwrite": { "method": "create", - "weight": 230, + "weight": 231, "cookies": false, "type": "", "deprecated": false, @@ -27690,7 +27706,7 @@ }, "x-appwrite": { "method": "createArgon2User", - "weight": 233, + "weight": 234, "cookies": false, "type": "", "deprecated": false, @@ -27777,7 +27793,7 @@ }, "x-appwrite": { "method": "createBcryptUser", - "weight": 231, + "weight": 232, "cookies": false, "type": "", "deprecated": false, @@ -27864,7 +27880,7 @@ }, "x-appwrite": { "method": "listIdentities", - "weight": 247, + "weight": 248, "cookies": false, "type": "", "deprecated": false, @@ -27934,7 +27950,7 @@ }, "x-appwrite": { "method": "deleteIdentity", - "weight": 270, + "weight": 271, "cookies": false, "type": "", "deprecated": false, @@ -27997,7 +28013,7 @@ }, "x-appwrite": { "method": "createMD5User", - "weight": 232, + "weight": 233, "cookies": false, "type": "", "deprecated": false, @@ -28084,7 +28100,7 @@ }, "x-appwrite": { "method": "createPHPassUser", - "weight": 235, + "weight": 236, "cookies": false, "type": "", "deprecated": false, @@ -28171,7 +28187,7 @@ }, "x-appwrite": { "method": "createScryptUser", - "weight": 236, + "weight": 237, "cookies": false, "type": "", "deprecated": false, @@ -28288,7 +28304,7 @@ }, "x-appwrite": { "method": "createScryptModifiedUser", - "weight": 237, + "weight": 238, "cookies": false, "type": "", "deprecated": false, @@ -28393,7 +28409,7 @@ }, "x-appwrite": { "method": "createSHAUser", - "weight": 234, + "weight": 235, "cookies": false, "type": "", "deprecated": false, @@ -28500,7 +28516,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 272, + "weight": 273, "cookies": false, "type": "", "deprecated": false, @@ -28574,7 +28590,7 @@ }, "x-appwrite": { "method": "get", - "weight": 240, + "weight": 241, "cookies": false, "type": "", "deprecated": false, @@ -28628,7 +28644,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 268, + "weight": 269, "cookies": false, "type": "", "deprecated": false, @@ -28691,7 +28707,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 253, + "weight": 254, "cookies": false, "type": "", "deprecated": false, @@ -28773,7 +28789,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 271, + "weight": 272, "cookies": false, "type": "", "deprecated": false, @@ -28857,7 +28873,7 @@ }, "x-appwrite": { "method": "updateLabels", - "weight": 249, + "weight": 250, "cookies": false, "type": "", "deprecated": false, @@ -28942,7 +28958,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 245, + "weight": 246, "cookies": false, "type": "", "deprecated": false, @@ -29018,7 +29034,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 244, + "weight": 245, "cookies": false, "type": "", "deprecated": false, @@ -29081,7 +29097,7 @@ }, "x-appwrite": { "method": "updateMfa", - "weight": 258, + "weight": 259, "cookies": false, "type": "", "deprecated": false, @@ -29163,7 +29179,7 @@ }, "x-appwrite": { "method": "deleteMfaAuthenticator", - "weight": 263, + "weight": 264, "cookies": false, "type": "", "deprecated": false, @@ -29241,7 +29257,7 @@ }, "x-appwrite": { "method": "listMfaFactors", - "weight": 259, + "weight": 260, "cookies": false, "type": "", "deprecated": false, @@ -29304,7 +29320,7 @@ }, "x-appwrite": { "method": "getMfaRecoveryCodes", - "weight": 260, + "weight": 261, "cookies": false, "type": "", "deprecated": false, @@ -29365,7 +29381,7 @@ }, "x-appwrite": { "method": "updateMfaRecoveryCodes", - "weight": 262, + "weight": 263, "cookies": false, "type": "", "deprecated": false, @@ -29426,7 +29442,7 @@ }, "x-appwrite": { "method": "createMfaRecoveryCodes", - "weight": 261, + "weight": 262, "cookies": false, "type": "", "deprecated": false, @@ -29489,7 +29505,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 251, + "weight": 252, "cookies": false, "type": "", "deprecated": false, @@ -29571,7 +29587,7 @@ }, "x-appwrite": { "method": "updatePassword", - "weight": 252, + "weight": 253, "cookies": false, "type": "", "deprecated": false, @@ -29653,7 +29669,7 @@ }, "x-appwrite": { "method": "updatePhone", - "weight": 254, + "weight": 255, "cookies": false, "type": "", "deprecated": false, @@ -29735,7 +29751,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 241, + "weight": 242, "cookies": false, "type": "", "deprecated": false, @@ -29796,7 +29812,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 256, + "weight": 257, "cookies": false, "type": "", "deprecated": false, @@ -29878,7 +29894,7 @@ }, "x-appwrite": { "method": "listSessions", - "weight": 243, + "weight": 244, "cookies": false, "type": "", "deprecated": false, @@ -29939,7 +29955,7 @@ }, "x-appwrite": { "method": "createSession", - "weight": 264, + "weight": 265, "cookies": false, "type": "", "deprecated": false, @@ -29993,7 +30009,7 @@ }, "x-appwrite": { "method": "deleteSessions", - "weight": 267, + "weight": 268, "cookies": false, "type": "", "deprecated": false, @@ -30049,7 +30065,7 @@ }, "x-appwrite": { "method": "deleteSession", - "weight": 266, + "weight": 267, "cookies": false, "type": "", "deprecated": false, @@ -30122,7 +30138,7 @@ }, "x-appwrite": { "method": "updateStatus", - "weight": 248, + "weight": 249, "cookies": false, "type": "", "deprecated": false, @@ -30204,7 +30220,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 246, + "weight": 247, "cookies": false, "type": "", "deprecated": false, @@ -30279,7 +30295,7 @@ }, "x-appwrite": { "method": "createTarget", - "weight": 238, + "weight": 239, "cookies": false, "type": "", "deprecated": false, @@ -30391,7 +30407,7 @@ }, "x-appwrite": { "method": "getTarget", - "weight": 242, + "weight": 243, "cookies": false, "type": "", "deprecated": false, @@ -30463,7 +30479,7 @@ }, "x-appwrite": { "method": "updateTarget", - "weight": 257, + "weight": 258, "cookies": false, "type": "", "deprecated": false, @@ -30554,7 +30570,7 @@ }, "x-appwrite": { "method": "deleteTarget", - "weight": 269, + "weight": 270, "cookies": false, "type": "", "deprecated": false, @@ -30628,7 +30644,7 @@ }, "x-appwrite": { "method": "createToken", - "weight": 265, + "weight": 266, "cookies": false, "type": "", "deprecated": false, @@ -30712,7 +30728,7 @@ }, "x-appwrite": { "method": "updateEmailVerification", - "weight": 255, + "weight": 256, "cookies": false, "type": "", "deprecated": false, @@ -30794,7 +30810,7 @@ }, "x-appwrite": { "method": "updatePhoneVerification", - "weight": 250, + "weight": 251, "cookies": false, "type": "", "deprecated": false, @@ -30876,7 +30892,7 @@ }, "x-appwrite": { "method": "listRepositories", - "weight": 277, + "weight": 278, "cookies": false, "type": "", "deprecated": false, @@ -30947,7 +30963,7 @@ }, "x-appwrite": { "method": "createRepository", - "weight": 278, + "weight": 279, "cookies": false, "type": "", "deprecated": false, @@ -31034,7 +31050,7 @@ }, "x-appwrite": { "method": "getRepository", - "weight": 279, + "weight": 280, "cookies": false, "type": "", "deprecated": false, @@ -31106,7 +31122,7 @@ }, "x-appwrite": { "method": "listRepositoryBranches", - "weight": 280, + "weight": 281, "cookies": false, "type": "", "deprecated": false, @@ -31178,7 +31194,7 @@ }, "x-appwrite": { "method": "getRepositoryContents", - "weight": 275, + "weight": 276, "cookies": false, "type": "", "deprecated": false, @@ -31261,7 +31277,7 @@ }, "x-appwrite": { "method": "createRepositoryDetection", - "weight": 276, + "weight": 277, "cookies": false, "type": "", "deprecated": false, @@ -31342,7 +31358,7 @@ }, "x-appwrite": { "method": "updateExternalDeployments", - "weight": 285, + "weight": 286, "cookies": false, "type": "", "deprecated": false, @@ -31433,7 +31449,7 @@ }, "x-appwrite": { "method": "listInstallations", - "weight": 282, + "weight": 283, "cookies": false, "type": "", "deprecated": false, @@ -31509,7 +31525,7 @@ }, "x-appwrite": { "method": "getInstallation", - "weight": 283, + "weight": 284, "cookies": false, "type": "", "deprecated": false, @@ -31562,7 +31578,7 @@ }, "x-appwrite": { "method": "deleteInstallation", - "weight": 284, + "weight": 285, "cookies": false, "type": "", "deprecated": false, @@ -36046,6 +36062,17 @@ "description": "SMTP server secure protocol", "x-example": "tls" }, + "pingCount": { + "type": "integer", + "description": "Number of times the ping was received for this project.", + "x-example": 1, + "format": "int32" + }, + "pingedAt": { + "type": "string", + "description": "Last ping datetime in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, "authEmailPassword": { "type": "boolean", "description": "Email\/Password auth method status", @@ -36173,6 +36200,8 @@ "smtpUsername", "smtpPassword", "smtpSecure", + "pingCount", + "pingedAt", "authEmailPassword", "authUsersAuthMagicURL", "authEmailOtp", @@ -38419,7 +38448,7 @@ }, "$createdAt": { "type": "string", - "description": "Variable creation date in ISO 8601 format.", + "description": "Migration creation date in ISO 8601 format.", "x-example": "2020-10-15T06:38:00.000+00:00" }, "$updatedAt": { diff --git a/app/config/specs/open-api3-1.6.x-server.json b/app/config/specs/open-api3-1.6.x-server.json index 32ba6b3388..44270f16af 100644 --- a/app/config/specs/open-api3-1.6.x-server.json +++ b/app/config/specs/open-api3-1.6.x-server.json @@ -43,7 +43,7 @@ }, "x-appwrite": { "method": "get", - "weight": 8, + "weight": 9, "cookies": false, "type": "", "deprecated": false, @@ -95,7 +95,7 @@ }, "x-appwrite": { "method": "create", - "weight": 7, + "weight": 8, "cookies": false, "type": "", "deprecated": false, @@ -182,7 +182,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 33, + "weight": 34, "cookies": false, "type": "", "deprecated": false, @@ -261,7 +261,7 @@ }, "x-appwrite": { "method": "listIdentities", - "weight": 56, + "weight": 57, "cookies": false, "type": "", "deprecated": false, @@ -323,7 +323,7 @@ }, "x-appwrite": { "method": "deleteIdentity", - "weight": 57, + "weight": 58, "cookies": false, "type": "", "deprecated": false, @@ -389,7 +389,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 28, + "weight": 29, "cookies": false, "type": "", "deprecated": false, @@ -440,7 +440,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 30, + "weight": 31, "cookies": false, "type": "", "deprecated": false, @@ -509,7 +509,7 @@ }, "x-appwrite": { "method": "updateMFA", - "weight": 43, + "weight": 44, "cookies": false, "type": "", "deprecated": false, @@ -582,7 +582,7 @@ }, "x-appwrite": { "method": "createMfaAuthenticator", - "weight": 45, + "weight": 46, "cookies": false, "type": "", "deprecated": false, @@ -651,7 +651,7 @@ }, "x-appwrite": { "method": "updateMfaAuthenticator", - "weight": 46, + "weight": 47, "cookies": false, "type": "", "deprecated": false, @@ -732,7 +732,7 @@ }, "x-appwrite": { "method": "deleteMfaAuthenticator", - "weight": 50, + "weight": 51, "cookies": false, "type": "", "deprecated": false, @@ -803,7 +803,7 @@ }, "x-appwrite": { "method": "createMfaChallenge", - "weight": 51, + "weight": 52, "cookies": false, "type": "", "deprecated": false, @@ -879,7 +879,7 @@ }, "x-appwrite": { "method": "updateMfaChallenge", - "weight": 52, + "weight": 53, "cookies": false, "type": "", "deprecated": false, @@ -958,7 +958,7 @@ }, "x-appwrite": { "method": "listMfaFactors", - "weight": 44, + "weight": 45, "cookies": false, "type": "", "deprecated": false, @@ -1012,7 +1012,7 @@ }, "x-appwrite": { "method": "getMfaRecoveryCodes", - "weight": 49, + "weight": 50, "cookies": false, "type": "", "deprecated": false, @@ -1064,7 +1064,7 @@ }, "x-appwrite": { "method": "createMfaRecoveryCodes", - "weight": 47, + "weight": 48, "cookies": false, "type": "", "deprecated": false, @@ -1116,7 +1116,7 @@ }, "x-appwrite": { "method": "updateMfaRecoveryCodes", - "weight": 48, + "weight": 49, "cookies": false, "type": "", "deprecated": false, @@ -1170,7 +1170,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 31, + "weight": 32, "cookies": false, "type": "", "deprecated": false, @@ -1243,7 +1243,7 @@ }, "x-appwrite": { "method": "updatePassword", - "weight": 32, + "weight": 33, "cookies": false, "type": "", "deprecated": false, @@ -1321,7 +1321,7 @@ }, "x-appwrite": { "method": "updatePhone", - "weight": 34, + "weight": 35, "cookies": false, "type": "", "deprecated": false, @@ -1400,7 +1400,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 29, + "weight": 30, "cookies": false, "type": "", "deprecated": false, @@ -1452,7 +1452,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 35, + "weight": 36, "cookies": false, "type": "", "deprecated": false, @@ -1525,7 +1525,7 @@ }, "x-appwrite": { "method": "createRecovery", - "weight": 37, + "weight": 38, "cookies": false, "type": "", "deprecated": false, @@ -1605,7 +1605,7 @@ }, "x-appwrite": { "method": "updateRecovery", - "weight": 38, + "weight": 39, "cookies": false, "type": "", "deprecated": false, @@ -1690,7 +1690,7 @@ }, "x-appwrite": { "method": "listSessions", - "weight": 10, + "weight": 11, "cookies": false, "type": "", "deprecated": false, @@ -1735,7 +1735,7 @@ }, "x-appwrite": { "method": "deleteSessions", - "weight": 11, + "weight": 12, "cookies": false, "type": "", "deprecated": false, @@ -1789,7 +1789,7 @@ }, "x-appwrite": { "method": "createAnonymousSession", - "weight": 16, + "weight": 17, "cookies": false, "type": "", "deprecated": false, @@ -1840,7 +1840,7 @@ }, "x-appwrite": { "method": "createEmailPasswordSession", - "weight": 15, + "weight": 16, "cookies": false, "type": "", "deprecated": false, @@ -1916,7 +1916,7 @@ }, "x-appwrite": { "method": "updateMagicURLSession", - "weight": 25, + "weight": 26, "cookies": false, "type": "", "deprecated": true, @@ -1992,7 +1992,7 @@ }, "x-appwrite": { "method": "updatePhoneSession", - "weight": 26, + "weight": 27, "cookies": false, "type": "", "deprecated": true, @@ -2068,7 +2068,7 @@ }, "x-appwrite": { "method": "createSession", - "weight": 17, + "weight": 18, "cookies": false, "type": "", "deprecated": false, @@ -2144,7 +2144,7 @@ }, "x-appwrite": { "method": "getSession", - "weight": 12, + "weight": 13, "cookies": false, "type": "", "deprecated": false, @@ -2208,7 +2208,7 @@ }, "x-appwrite": { "method": "updateSession", - "weight": 14, + "weight": 15, "cookies": false, "type": "", "deprecated": false, @@ -2265,7 +2265,7 @@ }, "x-appwrite": { "method": "deleteSession", - "weight": 13, + "weight": 14, "cookies": false, "type": "", "deprecated": false, @@ -2331,7 +2331,7 @@ }, "x-appwrite": { "method": "updateStatus", - "weight": 36, + "weight": 37, "cookies": false, "type": "", "deprecated": false, @@ -2385,7 +2385,7 @@ }, "x-appwrite": { "method": "createEmailToken", - "weight": 24, + "weight": 25, "cookies": false, "type": "", "deprecated": false, @@ -2466,7 +2466,7 @@ }, "x-appwrite": { "method": "createMagicURLToken", - "weight": 23, + "weight": 24, "cookies": false, "type": "", "deprecated": false, @@ -2548,7 +2548,7 @@ }, "x-appwrite": { "method": "createOAuth2Token", - "weight": 22, + "weight": 23, "cookies": false, "type": "webAuth", "deprecated": false, @@ -2691,7 +2691,7 @@ }, "x-appwrite": { "method": "createPhoneToken", - "weight": 27, + "weight": 28, "cookies": false, "type": "", "deprecated": false, @@ -2770,7 +2770,7 @@ }, "x-appwrite": { "method": "createVerification", - "weight": 39, + "weight": 40, "cookies": false, "type": "", "deprecated": false, @@ -2841,7 +2841,7 @@ }, "x-appwrite": { "method": "updateVerification", - "weight": 40, + "weight": 41, "cookies": false, "type": "", "deprecated": false, @@ -2920,7 +2920,7 @@ }, "x-appwrite": { "method": "createPhoneVerification", - "weight": 41, + "weight": 42, "cookies": false, "type": "", "deprecated": false, @@ -2975,7 +2975,7 @@ }, "x-appwrite": { "method": "updatePhoneVerification", - "weight": 42, + "weight": 43, "cookies": false, "type": "", "deprecated": false, @@ -3047,7 +3047,7 @@ }, "x-appwrite": { "method": "getBrowser", - "weight": 59, + "weight": 60, "cookies": false, "type": "location", "deprecated": false, @@ -3177,7 +3177,7 @@ }, "x-appwrite": { "method": "getCreditCard", - "weight": 58, + "weight": 59, "cookies": false, "type": "location", "deprecated": false, @@ -3311,7 +3311,7 @@ }, "x-appwrite": { "method": "getFavicon", - "weight": 62, + "weight": 63, "cookies": false, "type": "location", "deprecated": false, @@ -3373,7 +3373,7 @@ }, "x-appwrite": { "method": "getFlag", - "weight": 60, + "weight": 61, "cookies": false, "type": "location", "deprecated": false, @@ -3865,7 +3865,7 @@ }, "x-appwrite": { "method": "getImage", - "weight": 61, + "weight": 62, "cookies": false, "type": "location", "deprecated": false, @@ -3951,7 +3951,7 @@ }, "x-appwrite": { "method": "getInitials", - "weight": 64, + "weight": 65, "cookies": false, "type": "location", "deprecated": false, @@ -4047,7 +4047,7 @@ }, "x-appwrite": { "method": "getQR", - "weight": 63, + "weight": 64, "cookies": false, "type": "location", "deprecated": false, @@ -4150,7 +4150,7 @@ }, "x-appwrite": { "method": "list", - "weight": 69, + "weight": 70, "cookies": false, "type": "", "deprecated": false, @@ -4226,7 +4226,7 @@ }, "x-appwrite": { "method": "create", - "weight": 68, + "weight": 69, "cookies": false, "type": "", "deprecated": false, @@ -4308,7 +4308,7 @@ }, "x-appwrite": { "method": "get", - "weight": 70, + "weight": 71, "cookies": false, "type": "", "deprecated": false, @@ -4370,7 +4370,7 @@ }, "x-appwrite": { "method": "update", - "weight": 72, + "weight": 73, "cookies": false, "type": "", "deprecated": false, @@ -4449,7 +4449,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 73, + "weight": 74, "cookies": false, "type": "", "deprecated": false, @@ -4513,7 +4513,7 @@ }, "x-appwrite": { "method": "listCollections", - "weight": 75, + "weight": 76, "cookies": false, "type": "", "deprecated": false, @@ -4599,7 +4599,7 @@ }, "x-appwrite": { "method": "createCollection", - "weight": 74, + "weight": 75, "cookies": false, "type": "", "deprecated": false, @@ -4706,7 +4706,7 @@ }, "x-appwrite": { "method": "getCollection", - "weight": 76, + "weight": 77, "cookies": false, "type": "", "deprecated": false, @@ -4778,7 +4778,7 @@ }, "x-appwrite": { "method": "updateCollection", - "weight": 78, + "weight": 79, "cookies": false, "type": "", "deprecated": false, @@ -4880,7 +4880,7 @@ }, "x-appwrite": { "method": "deleteCollection", - "weight": 79, + "weight": 80, "cookies": false, "type": "", "deprecated": false, @@ -4954,7 +4954,7 @@ }, "x-appwrite": { "method": "listAttributes", - "weight": 90, + "weight": 91, "cookies": false, "type": "", "deprecated": false, @@ -5041,7 +5041,7 @@ }, "x-appwrite": { "method": "createBooleanAttribute", - "weight": 87, + "weight": 88, "cookies": false, "type": "", "deprecated": false, @@ -5150,7 +5150,7 @@ }, "x-appwrite": { "method": "updateBooleanAttribute", - "weight": 99, + "weight": 100, "cookies": false, "type": "", "deprecated": false, @@ -5264,7 +5264,7 @@ }, "x-appwrite": { "method": "createDatetimeAttribute", - "weight": 88, + "weight": 89, "cookies": false, "type": "", "deprecated": false, @@ -5373,7 +5373,7 @@ }, "x-appwrite": { "method": "updateDatetimeAttribute", - "weight": 100, + "weight": 101, "cookies": false, "type": "", "deprecated": false, @@ -5487,7 +5487,7 @@ }, "x-appwrite": { "method": "createEmailAttribute", - "weight": 81, + "weight": 82, "cookies": false, "type": "", "deprecated": false, @@ -5596,7 +5596,7 @@ }, "x-appwrite": { "method": "updateEmailAttribute", - "weight": 93, + "weight": 94, "cookies": false, "type": "", "deprecated": false, @@ -5710,7 +5710,7 @@ }, "x-appwrite": { "method": "createEnumAttribute", - "weight": 82, + "weight": 83, "cookies": false, "type": "", "deprecated": false, @@ -5828,7 +5828,7 @@ }, "x-appwrite": { "method": "updateEnumAttribute", - "weight": 94, + "weight": 95, "cookies": false, "type": "", "deprecated": false, @@ -5951,7 +5951,7 @@ }, "x-appwrite": { "method": "createFloatAttribute", - "weight": 86, + "weight": 87, "cookies": false, "type": "", "deprecated": false, @@ -6070,7 +6070,7 @@ }, "x-appwrite": { "method": "updateFloatAttribute", - "weight": 98, + "weight": 99, "cookies": false, "type": "", "deprecated": false, @@ -6196,7 +6196,7 @@ }, "x-appwrite": { "method": "createIntegerAttribute", - "weight": 85, + "weight": 86, "cookies": false, "type": "", "deprecated": false, @@ -6315,7 +6315,7 @@ }, "x-appwrite": { "method": "updateIntegerAttribute", - "weight": 97, + "weight": 98, "cookies": false, "type": "", "deprecated": false, @@ -6441,7 +6441,7 @@ }, "x-appwrite": { "method": "createIpAttribute", - "weight": 83, + "weight": 84, "cookies": false, "type": "", "deprecated": false, @@ -6550,7 +6550,7 @@ }, "x-appwrite": { "method": "updateIpAttribute", - "weight": 95, + "weight": 96, "cookies": false, "type": "", "deprecated": false, @@ -6664,7 +6664,7 @@ }, "x-appwrite": { "method": "createRelationshipAttribute", - "weight": 89, + "weight": 90, "cookies": false, "type": "", "deprecated": false, @@ -6798,7 +6798,7 @@ }, "x-appwrite": { "method": "createStringAttribute", - "weight": 80, + "weight": 81, "cookies": false, "type": "", "deprecated": false, @@ -6918,7 +6918,7 @@ }, "x-appwrite": { "method": "updateStringAttribute", - "weight": 92, + "weight": 93, "cookies": false, "type": "", "deprecated": false, @@ -7037,7 +7037,7 @@ }, "x-appwrite": { "method": "createUrlAttribute", - "weight": 84, + "weight": 85, "cookies": false, "type": "", "deprecated": false, @@ -7146,7 +7146,7 @@ }, "x-appwrite": { "method": "updateUrlAttribute", - "weight": 96, + "weight": 97, "cookies": false, "type": "", "deprecated": false, @@ -7291,7 +7291,7 @@ }, "x-appwrite": { "method": "getAttribute", - "weight": 91, + "weight": 92, "cookies": false, "type": "", "deprecated": false, @@ -7365,7 +7365,7 @@ }, "x-appwrite": { "method": "deleteAttribute", - "weight": 102, + "weight": 103, "cookies": false, "type": "", "deprecated": false, @@ -7448,7 +7448,7 @@ }, "x-appwrite": { "method": "updateRelationshipAttribute", - "weight": 101, + "weight": 102, "cookies": false, "type": "", "deprecated": false, @@ -7559,7 +7559,7 @@ }, "x-appwrite": { "method": "listDocuments", - "weight": 108, + "weight": 109, "cookies": false, "type": "", "deprecated": false, @@ -7648,7 +7648,7 @@ }, "x-appwrite": { "method": "createDocument", - "weight": 107, + "weight": 108, "cookies": false, "type": "", "deprecated": false, @@ -7759,7 +7759,7 @@ }, "x-appwrite": { "method": "getDocument", - "weight": 109, + "weight": 110, "cookies": false, "type": "", "deprecated": false, @@ -7858,7 +7858,7 @@ }, "x-appwrite": { "method": "updateDocument", - "weight": 111, + "weight": 112, "cookies": false, "type": "", "deprecated": false, @@ -7961,7 +7961,7 @@ }, "x-appwrite": { "method": "deleteDocument", - "weight": 112, + "weight": 113, "cookies": false, "type": "", "deprecated": false, @@ -8049,7 +8049,7 @@ }, "x-appwrite": { "method": "listIndexes", - "weight": 104, + "weight": 105, "cookies": false, "type": "", "deprecated": false, @@ -8134,7 +8134,7 @@ }, "x-appwrite": { "method": "createIndex", - "weight": 103, + "weight": 104, "cookies": false, "type": "", "deprecated": false, @@ -8257,7 +8257,7 @@ }, "x-appwrite": { "method": "getIndex", - "weight": 105, + "weight": 106, "cookies": false, "type": "", "deprecated": false, @@ -8331,7 +8331,7 @@ }, "x-appwrite": { "method": "deleteIndex", - "weight": 106, + "weight": 107, "cookies": false, "type": "", "deprecated": false, @@ -8414,7 +8414,7 @@ }, "x-appwrite": { "method": "list", - "weight": 287, + "weight": 288, "cookies": false, "type": "", "deprecated": false, @@ -8490,7 +8490,7 @@ }, "x-appwrite": { "method": "create", - "weight": 286, + "weight": 287, "cookies": false, "type": "", "deprecated": false, @@ -8545,6 +8545,7 @@ "node-19.0", "node-20.0", "node-21.0", + "node-22", "php-8.0", "php-8.1", "php-8.2", @@ -8563,6 +8564,8 @@ "deno-1.24", "deno-1.35", "deno-1.40", + "deno-1.46", + "deno-2.0", "dart-2.15", "dart-2.16", "dart-2.17", @@ -8570,23 +8573,28 @@ "dart-3.0", "dart-3.1", "dart-3.3", - "dotnet-3.1", + "dart-3.5", "dotnet-6.0", "dotnet-7.0", + "dotnet-8.0", "java-8.0", "java-11.0", "java-17.0", "java-18.0", "java-21.0", + "java-22", "swift-5.5", "swift-5.8", "swift-5.9", + "swift-5.10", "kotlin-1.6", "kotlin-1.8", "kotlin-1.9", + "kotlin-2.0", "cpp-17", "cpp-20", "bun-1.0", + "bun-1.1", "go-1.23" ], "x-enum-name": null, @@ -8730,7 +8738,7 @@ }, "x-appwrite": { "method": "listRuntimes", - "weight": 288, + "weight": 289, "cookies": false, "type": "", "deprecated": false, @@ -8782,7 +8790,7 @@ }, "x-appwrite": { "method": "listSpecifications", - "weight": 289, + "weight": 290, "cookies": false, "type": "", "deprecated": false, @@ -8835,7 +8843,7 @@ }, "x-appwrite": { "method": "get", - "weight": 290, + "weight": 291, "cookies": false, "type": "", "deprecated": false, @@ -8897,7 +8905,7 @@ }, "x-appwrite": { "method": "update", - "weight": 293, + "weight": 294, "cookies": false, "type": "", "deprecated": false, @@ -8959,6 +8967,7 @@ "node-19.0", "node-20.0", "node-21.0", + "node-22", "php-8.0", "php-8.1", "php-8.2", @@ -8977,6 +8986,8 @@ "deno-1.24", "deno-1.35", "deno-1.40", + "deno-1.46", + "deno-2.0", "dart-2.15", "dart-2.16", "dart-2.17", @@ -8984,23 +8995,28 @@ "dart-3.0", "dart-3.1", "dart-3.3", - "dotnet-3.1", + "dart-3.5", "dotnet-6.0", "dotnet-7.0", + "dotnet-8.0", "java-8.0", "java-11.0", "java-17.0", "java-18.0", "java-21.0", + "java-22", "swift-5.5", "swift-5.8", "swift-5.9", + "swift-5.10", "kotlin-1.6", "kotlin-1.8", "kotlin-1.9", + "kotlin-2.0", "cpp-17", "cpp-20", "bun-1.0", + "bun-1.1", "go-1.23" ], "x-enum-name": null, @@ -9114,7 +9130,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 296, + "weight": 297, "cookies": false, "type": "", "deprecated": false, @@ -9178,7 +9194,7 @@ }, "x-appwrite": { "method": "listDeployments", - "weight": 298, + "weight": 299, "cookies": false, "type": "", "deprecated": false, @@ -9264,7 +9280,7 @@ }, "x-appwrite": { "method": "createDeployment", - "weight": 297, + "weight": 298, "cookies": false, "type": "upload", "deprecated": false, @@ -9363,7 +9379,7 @@ }, "x-appwrite": { "method": "getDeployment", - "weight": 299, + "weight": 300, "cookies": false, "type": "", "deprecated": false, @@ -9435,7 +9451,7 @@ }, "x-appwrite": { "method": "updateDeployment", - "weight": 295, + "weight": 296, "cookies": false, "type": "", "deprecated": false, @@ -9500,7 +9516,7 @@ }, "x-appwrite": { "method": "deleteDeployment", - "weight": 300, + "weight": 301, "cookies": false, "type": "", "deprecated": false, @@ -9567,7 +9583,7 @@ }, "x-appwrite": { "method": "createBuild", - "weight": 301, + "weight": 302, "cookies": false, "type": "", "deprecated": false, @@ -9655,7 +9671,7 @@ }, "x-appwrite": { "method": "updateDeploymentBuild", - "weight": 302, + "weight": 303, "cookies": false, "type": "", "deprecated": false, @@ -9722,7 +9738,7 @@ }, "x-appwrite": { "method": "getDeploymentDownload", - "weight": 294, + "weight": 295, "cookies": false, "type": "location", "deprecated": false, @@ -9798,7 +9814,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 304, + "weight": 305, "cookies": false, "type": "", "deprecated": false, @@ -9888,7 +9904,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 303, + "weight": 304, "cookies": false, "type": "", "deprecated": false, @@ -10007,7 +10023,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 305, + "weight": 306, "cookies": false, "type": "", "deprecated": false, @@ -10076,7 +10092,7 @@ }, "x-appwrite": { "method": "deleteExecution", - "weight": 306, + "weight": 307, "cookies": false, "type": "", "deprecated": false, @@ -10150,7 +10166,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 308, + "weight": 309, "cookies": false, "type": "", "deprecated": false, @@ -10212,7 +10228,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 307, + "weight": 308, "cookies": false, "type": "", "deprecated": false, @@ -10301,7 +10317,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 309, + "weight": 310, "cookies": false, "type": "", "deprecated": false, @@ -10373,7 +10389,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 310, + "weight": 311, "cookies": false, "type": "", "deprecated": false, @@ -10462,7 +10478,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 311, + "weight": 312, "cookies": false, "type": "", "deprecated": false, @@ -10536,7 +10552,7 @@ }, "x-appwrite": { "method": "query", - "weight": 329, + "weight": 330, "cookies": false, "type": "graphql", "deprecated": false, @@ -10592,7 +10608,7 @@ }, "x-appwrite": { "method": "mutation", - "weight": 328, + "weight": 329, "cookies": false, "type": "graphql", "deprecated": false, @@ -10648,7 +10664,7 @@ }, "x-appwrite": { "method": "get", - "weight": 124, + "weight": 125, "cookies": false, "type": "", "deprecated": false, @@ -10700,7 +10716,7 @@ }, "x-appwrite": { "method": "getAntivirus", - "weight": 146, + "weight": 147, "cookies": false, "type": "", "deprecated": false, @@ -10752,7 +10768,7 @@ }, "x-appwrite": { "method": "getCache", - "weight": 127, + "weight": 128, "cookies": false, "type": "", "deprecated": false, @@ -10804,7 +10820,7 @@ }, "x-appwrite": { "method": "getCertificate", - "weight": 133, + "weight": 134, "cookies": false, "type": "", "deprecated": false, @@ -10867,7 +10883,7 @@ }, "x-appwrite": { "method": "getDB", - "weight": 126, + "weight": 127, "cookies": false, "type": "", "deprecated": false, @@ -10919,7 +10935,7 @@ }, "x-appwrite": { "method": "getPubSub", - "weight": 129, + "weight": 130, "cookies": false, "type": "", "deprecated": false, @@ -10971,7 +10987,7 @@ }, "x-appwrite": { "method": "getQueue", - "weight": 128, + "weight": 129, "cookies": false, "type": "", "deprecated": false, @@ -11023,7 +11039,7 @@ }, "x-appwrite": { "method": "getQueueBuilds", - "weight": 135, + "weight": 136, "cookies": false, "type": "", "deprecated": false, @@ -11088,7 +11104,7 @@ }, "x-appwrite": { "method": "getQueueCertificates", - "weight": 134, + "weight": 135, "cookies": false, "type": "", "deprecated": false, @@ -11153,7 +11169,7 @@ }, "x-appwrite": { "method": "getQueueDatabases", - "weight": 136, + "weight": 137, "cookies": false, "type": "", "deprecated": false, @@ -11229,7 +11245,7 @@ }, "x-appwrite": { "method": "getQueueDeletes", - "weight": 137, + "weight": 138, "cookies": false, "type": "", "deprecated": false, @@ -11294,7 +11310,7 @@ }, "x-appwrite": { "method": "getFailedJobs", - "weight": 147, + "weight": 148, "cookies": false, "type": "", "deprecated": false, @@ -11385,7 +11401,7 @@ }, "x-appwrite": { "method": "getQueueFunctions", - "weight": 141, + "weight": 142, "cookies": false, "type": "", "deprecated": false, @@ -11450,7 +11466,7 @@ }, "x-appwrite": { "method": "getQueueLogs", - "weight": 132, + "weight": 133, "cookies": false, "type": "", "deprecated": false, @@ -11515,7 +11531,7 @@ }, "x-appwrite": { "method": "getQueueMails", - "weight": 138, + "weight": 139, "cookies": false, "type": "", "deprecated": false, @@ -11580,7 +11596,7 @@ }, "x-appwrite": { "method": "getQueueMessaging", - "weight": 139, + "weight": 140, "cookies": false, "type": "", "deprecated": false, @@ -11645,7 +11661,7 @@ }, "x-appwrite": { "method": "getQueueMigrations", - "weight": 140, + "weight": 141, "cookies": false, "type": "", "deprecated": false, @@ -11710,7 +11726,7 @@ }, "x-appwrite": { "method": "getQueueUsage", - "weight": 142, + "weight": 143, "cookies": false, "type": "", "deprecated": false, @@ -11775,7 +11791,7 @@ }, "x-appwrite": { "method": "getQueueUsageDump", - "weight": 143, + "weight": 144, "cookies": false, "type": "", "deprecated": false, @@ -11840,7 +11856,7 @@ }, "x-appwrite": { "method": "getQueueWebhooks", - "weight": 131, + "weight": 132, "cookies": false, "type": "", "deprecated": false, @@ -11905,7 +11921,7 @@ }, "x-appwrite": { "method": "getStorage", - "weight": 145, + "weight": 146, "cookies": false, "type": "", "deprecated": false, @@ -11957,7 +11973,7 @@ }, "x-appwrite": { "method": "getStorageLocal", - "weight": 144, + "weight": 145, "cookies": false, "type": "", "deprecated": false, @@ -12009,7 +12025,7 @@ }, "x-appwrite": { "method": "getTime", - "weight": 130, + "weight": 131, "cookies": false, "type": "", "deprecated": false, @@ -12061,7 +12077,7 @@ }, "x-appwrite": { "method": "get", - "weight": 116, + "weight": 117, "cookies": false, "type": "", "deprecated": false, @@ -12117,7 +12133,7 @@ }, "x-appwrite": { "method": "listCodes", - "weight": 117, + "weight": 118, "cookies": false, "type": "", "deprecated": false, @@ -12173,7 +12189,7 @@ }, "x-appwrite": { "method": "listContinents", - "weight": 121, + "weight": 122, "cookies": false, "type": "", "deprecated": false, @@ -12229,7 +12245,7 @@ }, "x-appwrite": { "method": "listCountries", - "weight": 118, + "weight": 119, "cookies": false, "type": "", "deprecated": false, @@ -12285,7 +12301,7 @@ }, "x-appwrite": { "method": "listCountriesEU", - "weight": 119, + "weight": 120, "cookies": false, "type": "", "deprecated": false, @@ -12341,7 +12357,7 @@ }, "x-appwrite": { "method": "listCountriesPhones", - "weight": 120, + "weight": 121, "cookies": false, "type": "", "deprecated": false, @@ -12397,7 +12413,7 @@ }, "x-appwrite": { "method": "listCurrencies", - "weight": 122, + "weight": 123, "cookies": false, "type": "", "deprecated": false, @@ -12453,7 +12469,7 @@ }, "x-appwrite": { "method": "listLanguages", - "weight": 123, + "weight": 124, "cookies": false, "type": "", "deprecated": false, @@ -12509,7 +12525,7 @@ }, "x-appwrite": { "method": "listMessages", - "weight": 388, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -12588,7 +12604,7 @@ }, "x-appwrite": { "method": "createEmail", - "weight": 385, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -12735,7 +12751,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 392, + "weight": 393, "cookies": false, "type": "", "deprecated": false, @@ -12884,7 +12900,7 @@ }, "x-appwrite": { "method": "createPush", - "weight": 387, + "weight": 388, "cookies": false, "type": "", "deprecated": false, @@ -13042,7 +13058,7 @@ }, "x-appwrite": { "method": "updatePush", - "weight": 394, + "weight": 395, "cookies": false, "type": "", "deprecated": false, @@ -13202,7 +13218,7 @@ }, "x-appwrite": { "method": "createSms", - "weight": 386, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -13314,7 +13330,7 @@ }, "x-appwrite": { "method": "updateSms", - "weight": 393, + "weight": 394, "cookies": false, "type": "", "deprecated": false, @@ -13429,7 +13445,7 @@ }, "x-appwrite": { "method": "getMessage", - "weight": 391, + "weight": 392, "cookies": false, "type": "", "deprecated": false, @@ -13485,7 +13501,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 395, + "weight": 396, "cookies": false, "type": "", "deprecated": false, @@ -13550,7 +13566,7 @@ }, "x-appwrite": { "method": "listMessageLogs", - "weight": 389, + "weight": 390, "cookies": false, "type": "", "deprecated": false, @@ -13628,7 +13644,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 390, + "weight": 391, "cookies": false, "type": "", "deprecated": false, @@ -13706,7 +13722,7 @@ }, "x-appwrite": { "method": "listProviders", - "weight": 360, + "weight": 361, "cookies": false, "type": "", "deprecated": false, @@ -13785,7 +13801,7 @@ }, "x-appwrite": { "method": "createApnsProvider", - "weight": 359, + "weight": 360, "cookies": false, "type": "", "deprecated": false, @@ -13893,7 +13909,7 @@ }, "x-appwrite": { "method": "updateApnsProvider", - "weight": 372, + "weight": 373, "cookies": false, "type": "", "deprecated": false, @@ -14004,7 +14020,7 @@ }, "x-appwrite": { "method": "createFcmProvider", - "weight": 358, + "weight": 359, "cookies": false, "type": "", "deprecated": false, @@ -14092,7 +14108,7 @@ }, "x-appwrite": { "method": "updateFcmProvider", - "weight": 371, + "weight": 372, "cookies": false, "type": "", "deprecated": false, @@ -14183,7 +14199,7 @@ }, "x-appwrite": { "method": "createMailgunProvider", - "weight": 350, + "weight": 351, "cookies": false, "type": "", "deprecated": false, @@ -14301,7 +14317,7 @@ }, "x-appwrite": { "method": "updateMailgunProvider", - "weight": 363, + "weight": 364, "cookies": false, "type": "", "deprecated": false, @@ -14422,7 +14438,7 @@ }, "x-appwrite": { "method": "createMsg91Provider", - "weight": 353, + "weight": 354, "cookies": false, "type": "", "deprecated": false, @@ -14520,7 +14536,7 @@ }, "x-appwrite": { "method": "updateMsg91Provider", - "weight": 366, + "weight": 367, "cookies": false, "type": "", "deprecated": false, @@ -14621,7 +14637,7 @@ }, "x-appwrite": { "method": "createSendgridProvider", - "weight": 351, + "weight": 352, "cookies": false, "type": "", "deprecated": false, @@ -14729,7 +14745,7 @@ }, "x-appwrite": { "method": "updateSendgridProvider", - "weight": 364, + "weight": 365, "cookies": false, "type": "", "deprecated": false, @@ -14840,7 +14856,7 @@ }, "x-appwrite": { "method": "createSmtpProvider", - "weight": 352, + "weight": 353, "cookies": false, "type": "", "deprecated": false, @@ -14986,7 +15002,7 @@ }, "x-appwrite": { "method": "updateSmtpProvider", - "weight": 365, + "weight": 366, "cookies": false, "type": "", "deprecated": false, @@ -15134,7 +15150,7 @@ }, "x-appwrite": { "method": "createTelesignProvider", - "weight": 354, + "weight": 355, "cookies": false, "type": "", "deprecated": false, @@ -15232,7 +15248,7 @@ }, "x-appwrite": { "method": "updateTelesignProvider", - "weight": 367, + "weight": 368, "cookies": false, "type": "", "deprecated": false, @@ -15333,7 +15349,7 @@ }, "x-appwrite": { "method": "createTextmagicProvider", - "weight": 355, + "weight": 356, "cookies": false, "type": "", "deprecated": false, @@ -15431,7 +15447,7 @@ }, "x-appwrite": { "method": "updateTextmagicProvider", - "weight": 368, + "weight": 369, "cookies": false, "type": "", "deprecated": false, @@ -15532,7 +15548,7 @@ }, "x-appwrite": { "method": "createTwilioProvider", - "weight": 356, + "weight": 357, "cookies": false, "type": "", "deprecated": false, @@ -15630,7 +15646,7 @@ }, "x-appwrite": { "method": "updateTwilioProvider", - "weight": 369, + "weight": 370, "cookies": false, "type": "", "deprecated": false, @@ -15731,7 +15747,7 @@ }, "x-appwrite": { "method": "createVonageProvider", - "weight": 357, + "weight": 358, "cookies": false, "type": "", "deprecated": false, @@ -15829,7 +15845,7 @@ }, "x-appwrite": { "method": "updateVonageProvider", - "weight": 370, + "weight": 371, "cookies": false, "type": "", "deprecated": false, @@ -15930,7 +15946,7 @@ }, "x-appwrite": { "method": "getProvider", - "weight": 362, + "weight": 363, "cookies": false, "type": "", "deprecated": false, @@ -15986,7 +16002,7 @@ }, "x-appwrite": { "method": "deleteProvider", - "weight": 373, + "weight": 374, "cookies": false, "type": "", "deprecated": false, @@ -16051,7 +16067,7 @@ }, "x-appwrite": { "method": "listProviderLogs", - "weight": 361, + "weight": 362, "cookies": false, "type": "", "deprecated": false, @@ -16129,7 +16145,7 @@ }, "x-appwrite": { "method": "listSubscriberLogs", - "weight": 382, + "weight": 383, "cookies": false, "type": "", "deprecated": false, @@ -16207,7 +16223,7 @@ }, "x-appwrite": { "method": "listTopics", - "weight": 375, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -16284,7 +16300,7 @@ }, "x-appwrite": { "method": "createTopic", - "weight": 374, + "weight": 375, "cookies": false, "type": "", "deprecated": false, @@ -16370,7 +16386,7 @@ }, "x-appwrite": { "method": "getTopic", - "weight": 377, + "weight": 378, "cookies": false, "type": "", "deprecated": false, @@ -16433,7 +16449,7 @@ }, "x-appwrite": { "method": "updateTopic", - "weight": 378, + "weight": 379, "cookies": false, "type": "", "deprecated": false, @@ -16513,7 +16529,7 @@ }, "x-appwrite": { "method": "deleteTopic", - "weight": 379, + "weight": 380, "cookies": false, "type": "", "deprecated": false, @@ -16578,7 +16594,7 @@ }, "x-appwrite": { "method": "listTopicLogs", - "weight": 376, + "weight": 377, "cookies": false, "type": "", "deprecated": false, @@ -16656,7 +16672,7 @@ }, "x-appwrite": { "method": "listSubscribers", - "weight": 381, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -16743,7 +16759,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 380, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -16837,7 +16853,7 @@ }, "x-appwrite": { "method": "getSubscriber", - "weight": 383, + "weight": 384, "cookies": false, "type": "", "deprecated": false, @@ -16903,7 +16919,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 384, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -16982,7 +16998,7 @@ }, "x-appwrite": { "method": "listBuckets", - "weight": 201, + "weight": 202, "cookies": false, "type": "", "deprecated": false, @@ -17058,7 +17074,7 @@ }, "x-appwrite": { "method": "createBucket", - "weight": 200, + "weight": 201, "cookies": false, "type": "", "deprecated": false, @@ -17188,7 +17204,7 @@ }, "x-appwrite": { "method": "getBucket", - "weight": 202, + "weight": 203, "cookies": false, "type": "", "deprecated": false, @@ -17250,7 +17266,7 @@ }, "x-appwrite": { "method": "updateBucket", - "weight": 203, + "weight": 204, "cookies": false, "type": "", "deprecated": false, @@ -17377,7 +17393,7 @@ }, "x-appwrite": { "method": "deleteBucket", - "weight": 204, + "weight": 205, "cookies": false, "type": "", "deprecated": false, @@ -17441,7 +17457,7 @@ }, "x-appwrite": { "method": "listFiles", - "weight": 206, + "weight": 207, "cookies": false, "type": "", "deprecated": false, @@ -17531,7 +17547,7 @@ }, "x-appwrite": { "method": "createFile", - "weight": 205, + "weight": 206, "cookies": false, "type": "upload", "deprecated": false, @@ -17633,7 +17649,7 @@ }, "x-appwrite": { "method": "getFile", - "weight": 207, + "weight": 208, "cookies": false, "type": "", "deprecated": false, @@ -17709,7 +17725,7 @@ }, "x-appwrite": { "method": "updateFile", - "weight": 212, + "weight": 213, "cookies": false, "type": "", "deprecated": false, @@ -17802,7 +17818,7 @@ }, "x-appwrite": { "method": "deleteFile", - "weight": 213, + "weight": 214, "cookies": false, "type": "", "deprecated": false, @@ -17873,7 +17889,7 @@ }, "x-appwrite": { "method": "getFileDownload", - "weight": 209, + "weight": 210, "cookies": false, "type": "location", "deprecated": false, @@ -17944,7 +17960,7 @@ }, "x-appwrite": { "method": "getFilePreview", - "weight": 208, + "weight": 209, "cookies": false, "type": "location", "deprecated": false, @@ -18164,7 +18180,7 @@ }, "x-appwrite": { "method": "getFileView", - "weight": 210, + "weight": 211, "cookies": false, "type": "location", "deprecated": false, @@ -18242,7 +18258,7 @@ }, "x-appwrite": { "method": "list", - "weight": 217, + "weight": 218, "cookies": false, "type": "", "deprecated": false, @@ -18322,7 +18338,7 @@ }, "x-appwrite": { "method": "create", - "weight": 216, + "weight": 217, "cookies": false, "type": "", "deprecated": false, @@ -18411,7 +18427,7 @@ }, "x-appwrite": { "method": "get", - "weight": 218, + "weight": 219, "cookies": false, "type": "", "deprecated": false, @@ -18477,7 +18493,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 220, + "weight": 221, "cookies": false, "type": "", "deprecated": false, @@ -18555,7 +18571,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 222, + "weight": 223, "cookies": false, "type": "", "deprecated": false, @@ -18623,7 +18639,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 224, + "weight": 225, "cookies": false, "type": "", "deprecated": false, @@ -18713,7 +18729,7 @@ }, "x-appwrite": { "method": "createMembership", - "weight": 223, + "weight": 224, "cookies": false, "type": "", "deprecated": false, @@ -18828,7 +18844,7 @@ }, "x-appwrite": { "method": "getMembership", - "weight": 225, + "weight": 226, "cookies": false, "type": "", "deprecated": false, @@ -18904,7 +18920,7 @@ }, "x-appwrite": { "method": "updateMembership", - "weight": 226, + "weight": 227, "cookies": false, "type": "", "deprecated": false, @@ -18995,7 +19011,7 @@ }, "x-appwrite": { "method": "deleteMembership", - "weight": 228, + "weight": 229, "cookies": false, "type": "", "deprecated": false, @@ -19073,7 +19089,7 @@ }, "x-appwrite": { "method": "updateMembershipStatus", - "weight": 227, + "weight": 228, "cookies": false, "type": "", "deprecated": false, @@ -19174,7 +19190,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 219, + "weight": 220, "cookies": false, "type": "", "deprecated": false, @@ -19238,7 +19254,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 221, + "weight": 222, "cookies": false, "type": "", "deprecated": false, @@ -19323,7 +19339,7 @@ }, "x-appwrite": { "method": "list", - "weight": 239, + "weight": 240, "cookies": false, "type": "", "deprecated": false, @@ -19399,7 +19415,7 @@ }, "x-appwrite": { "method": "create", - "weight": 230, + "weight": 231, "cookies": false, "type": "", "deprecated": false, @@ -19490,7 +19506,7 @@ }, "x-appwrite": { "method": "createArgon2User", - "weight": 233, + "weight": 234, "cookies": false, "type": "", "deprecated": false, @@ -19578,7 +19594,7 @@ }, "x-appwrite": { "method": "createBcryptUser", - "weight": 231, + "weight": 232, "cookies": false, "type": "", "deprecated": false, @@ -19666,7 +19682,7 @@ }, "x-appwrite": { "method": "listIdentities", - "weight": 247, + "weight": 248, "cookies": false, "type": "", "deprecated": false, @@ -19737,7 +19753,7 @@ }, "x-appwrite": { "method": "deleteIdentity", - "weight": 270, + "weight": 271, "cookies": false, "type": "", "deprecated": false, @@ -19801,7 +19817,7 @@ }, "x-appwrite": { "method": "createMD5User", - "weight": 232, + "weight": 233, "cookies": false, "type": "", "deprecated": false, @@ -19889,7 +19905,7 @@ }, "x-appwrite": { "method": "createPHPassUser", - "weight": 235, + "weight": 236, "cookies": false, "type": "", "deprecated": false, @@ -19977,7 +19993,7 @@ }, "x-appwrite": { "method": "createScryptUser", - "weight": 236, + "weight": 237, "cookies": false, "type": "", "deprecated": false, @@ -20095,7 +20111,7 @@ }, "x-appwrite": { "method": "createScryptModifiedUser", - "weight": 237, + "weight": 238, "cookies": false, "type": "", "deprecated": false, @@ -20201,7 +20217,7 @@ }, "x-appwrite": { "method": "createSHAUser", - "weight": 234, + "weight": 235, "cookies": false, "type": "", "deprecated": false, @@ -20309,7 +20325,7 @@ }, "x-appwrite": { "method": "get", - "weight": 240, + "weight": 241, "cookies": false, "type": "", "deprecated": false, @@ -20364,7 +20380,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 268, + "weight": 269, "cookies": false, "type": "", "deprecated": false, @@ -20428,7 +20444,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 253, + "weight": 254, "cookies": false, "type": "", "deprecated": false, @@ -20511,7 +20527,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 271, + "weight": 272, "cookies": false, "type": "", "deprecated": false, @@ -20596,7 +20612,7 @@ }, "x-appwrite": { "method": "updateLabels", - "weight": 249, + "weight": 250, "cookies": false, "type": "", "deprecated": false, @@ -20682,7 +20698,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 245, + "weight": 246, "cookies": false, "type": "", "deprecated": false, @@ -20759,7 +20775,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 244, + "weight": 245, "cookies": false, "type": "", "deprecated": false, @@ -20823,7 +20839,7 @@ }, "x-appwrite": { "method": "updateMfa", - "weight": 258, + "weight": 259, "cookies": false, "type": "", "deprecated": false, @@ -20906,7 +20922,7 @@ }, "x-appwrite": { "method": "deleteMfaAuthenticator", - "weight": 263, + "weight": 264, "cookies": false, "type": "", "deprecated": false, @@ -20985,7 +21001,7 @@ }, "x-appwrite": { "method": "listMfaFactors", - "weight": 259, + "weight": 260, "cookies": false, "type": "", "deprecated": false, @@ -21049,7 +21065,7 @@ }, "x-appwrite": { "method": "getMfaRecoveryCodes", - "weight": 260, + "weight": 261, "cookies": false, "type": "", "deprecated": false, @@ -21111,7 +21127,7 @@ }, "x-appwrite": { "method": "updateMfaRecoveryCodes", - "weight": 262, + "weight": 263, "cookies": false, "type": "", "deprecated": false, @@ -21173,7 +21189,7 @@ }, "x-appwrite": { "method": "createMfaRecoveryCodes", - "weight": 261, + "weight": 262, "cookies": false, "type": "", "deprecated": false, @@ -21237,7 +21253,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 251, + "weight": 252, "cookies": false, "type": "", "deprecated": false, @@ -21320,7 +21336,7 @@ }, "x-appwrite": { "method": "updatePassword", - "weight": 252, + "weight": 253, "cookies": false, "type": "", "deprecated": false, @@ -21403,7 +21419,7 @@ }, "x-appwrite": { "method": "updatePhone", - "weight": 254, + "weight": 255, "cookies": false, "type": "", "deprecated": false, @@ -21486,7 +21502,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 241, + "weight": 242, "cookies": false, "type": "", "deprecated": false, @@ -21548,7 +21564,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 256, + "weight": 257, "cookies": false, "type": "", "deprecated": false, @@ -21631,7 +21647,7 @@ }, "x-appwrite": { "method": "listSessions", - "weight": 243, + "weight": 244, "cookies": false, "type": "", "deprecated": false, @@ -21693,7 +21709,7 @@ }, "x-appwrite": { "method": "createSession", - "weight": 264, + "weight": 265, "cookies": false, "type": "", "deprecated": false, @@ -21748,7 +21764,7 @@ }, "x-appwrite": { "method": "deleteSessions", - "weight": 267, + "weight": 268, "cookies": false, "type": "", "deprecated": false, @@ -21805,7 +21821,7 @@ }, "x-appwrite": { "method": "deleteSession", - "weight": 266, + "weight": 267, "cookies": false, "type": "", "deprecated": false, @@ -21879,7 +21895,7 @@ }, "x-appwrite": { "method": "updateStatus", - "weight": 248, + "weight": 249, "cookies": false, "type": "", "deprecated": false, @@ -21962,7 +21978,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 246, + "weight": 247, "cookies": false, "type": "", "deprecated": false, @@ -22038,7 +22054,7 @@ }, "x-appwrite": { "method": "createTarget", - "weight": 238, + "weight": 239, "cookies": false, "type": "", "deprecated": false, @@ -22151,7 +22167,7 @@ }, "x-appwrite": { "method": "getTarget", - "weight": 242, + "weight": 243, "cookies": false, "type": "", "deprecated": false, @@ -22224,7 +22240,7 @@ }, "x-appwrite": { "method": "updateTarget", - "weight": 257, + "weight": 258, "cookies": false, "type": "", "deprecated": false, @@ -22316,7 +22332,7 @@ }, "x-appwrite": { "method": "deleteTarget", - "weight": 269, + "weight": 270, "cookies": false, "type": "", "deprecated": false, @@ -22391,7 +22407,7 @@ }, "x-appwrite": { "method": "createToken", - "weight": 265, + "weight": 266, "cookies": false, "type": "", "deprecated": false, @@ -22476,7 +22492,7 @@ }, "x-appwrite": { "method": "updateEmailVerification", - "weight": 255, + "weight": 256, "cookies": false, "type": "", "deprecated": false, @@ -22559,7 +22575,7 @@ }, "x-appwrite": { "method": "updatePhoneVerification", - "weight": 250, + "weight": 251, "cookies": false, "type": "", "deprecated": false, diff --git a/app/config/specs/swagger2-1.6.x-client.json b/app/config/specs/swagger2-1.6.x-client.json index 421987f6a0..f070b1a4b0 100644 --- a/app/config/specs/swagger2-1.6.x-client.json +++ b/app/config/specs/swagger2-1.6.x-client.json @@ -87,7 +87,7 @@ }, "x-appwrite": { "method": "get", - "weight": 8, + "weight": 9, "cookies": false, "type": "", "deprecated": false, @@ -140,7 +140,7 @@ }, "x-appwrite": { "method": "create", - "weight": 7, + "weight": 8, "cookies": false, "type": "", "deprecated": false, @@ -233,7 +233,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 33, + "weight": 34, "cookies": false, "type": "", "deprecated": false, @@ -315,7 +315,7 @@ }, "x-appwrite": { "method": "listIdentities", - "weight": 56, + "weight": 57, "cookies": false, "type": "", "deprecated": false, @@ -379,7 +379,7 @@ }, "x-appwrite": { "method": "deleteIdentity", - "weight": 57, + "weight": 58, "cookies": false, "type": "", "deprecated": false, @@ -444,7 +444,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 28, + "weight": 29, "cookies": false, "type": "", "deprecated": false, @@ -497,7 +497,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 30, + "weight": 31, "cookies": false, "type": "", "deprecated": false, @@ -566,7 +566,7 @@ }, "x-appwrite": { "method": "updateMFA", - "weight": 43, + "weight": 44, "cookies": false, "type": "", "deprecated": false, @@ -641,7 +641,7 @@ }, "x-appwrite": { "method": "createMfaAuthenticator", - "weight": 45, + "weight": 46, "cookies": false, "type": "", "deprecated": false, @@ -709,7 +709,7 @@ }, "x-appwrite": { "method": "updateMfaAuthenticator", - "weight": 46, + "weight": 47, "cookies": false, "type": "", "deprecated": false, @@ -790,7 +790,7 @@ }, "x-appwrite": { "method": "deleteMfaAuthenticator", - "weight": 50, + "weight": 51, "cookies": false, "type": "", "deprecated": false, @@ -860,7 +860,7 @@ }, "x-appwrite": { "method": "createMfaChallenge", - "weight": 51, + "weight": 52, "cookies": false, "type": "", "deprecated": false, @@ -934,7 +934,7 @@ }, "x-appwrite": { "method": "updateMfaChallenge", - "weight": 52, + "weight": 53, "cookies": false, "type": "", "deprecated": false, @@ -1016,7 +1016,7 @@ }, "x-appwrite": { "method": "listMfaFactors", - "weight": 44, + "weight": 45, "cookies": false, "type": "", "deprecated": false, @@ -1071,7 +1071,7 @@ }, "x-appwrite": { "method": "getMfaRecoveryCodes", - "weight": 49, + "weight": 50, "cookies": false, "type": "", "deprecated": false, @@ -1124,7 +1124,7 @@ }, "x-appwrite": { "method": "createMfaRecoveryCodes", - "weight": 47, + "weight": 48, "cookies": false, "type": "", "deprecated": false, @@ -1177,7 +1177,7 @@ }, "x-appwrite": { "method": "updateMfaRecoveryCodes", - "weight": 48, + "weight": 49, "cookies": false, "type": "", "deprecated": false, @@ -1232,7 +1232,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 31, + "weight": 32, "cookies": false, "type": "", "deprecated": false, @@ -1307,7 +1307,7 @@ }, "x-appwrite": { "method": "updatePassword", - "weight": 32, + "weight": 33, "cookies": false, "type": "", "deprecated": false, @@ -1388,7 +1388,7 @@ }, "x-appwrite": { "method": "updatePhone", - "weight": 34, + "weight": 35, "cookies": false, "type": "", "deprecated": false, @@ -1470,7 +1470,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 29, + "weight": 30, "cookies": false, "type": "", "deprecated": false, @@ -1523,7 +1523,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 35, + "weight": 36, "cookies": false, "type": "", "deprecated": false, @@ -1598,7 +1598,7 @@ }, "x-appwrite": { "method": "createRecovery", - "weight": 37, + "weight": 38, "cookies": false, "type": "", "deprecated": false, @@ -1681,7 +1681,7 @@ }, "x-appwrite": { "method": "updateRecovery", - "weight": 38, + "weight": 39, "cookies": false, "type": "", "deprecated": false, @@ -1770,7 +1770,7 @@ }, "x-appwrite": { "method": "listSessions", - "weight": 10, + "weight": 11, "cookies": false, "type": "", "deprecated": false, @@ -1818,7 +1818,7 @@ }, "x-appwrite": { "method": "deleteSessions", - "weight": 11, + "weight": 12, "cookies": false, "type": "", "deprecated": false, @@ -1873,7 +1873,7 @@ }, "x-appwrite": { "method": "createAnonymousSession", - "weight": 16, + "weight": 17, "cookies": false, "type": "", "deprecated": false, @@ -1926,7 +1926,7 @@ }, "x-appwrite": { "method": "createEmailPasswordSession", - "weight": 15, + "weight": 16, "cookies": false, "type": "", "deprecated": false, @@ -2006,7 +2006,7 @@ }, "x-appwrite": { "method": "updateMagicURLSession", - "weight": 25, + "weight": 26, "cookies": false, "type": "", "deprecated": true, @@ -2083,7 +2083,7 @@ }, "x-appwrite": { "method": "createOAuth2Session", - "weight": 18, + "weight": 19, "cookies": false, "type": "webAuth", "deprecated": false, @@ -2221,7 +2221,7 @@ }, "x-appwrite": { "method": "updatePhoneSession", - "weight": 26, + "weight": 27, "cookies": false, "type": "", "deprecated": true, @@ -2301,7 +2301,7 @@ }, "x-appwrite": { "method": "createSession", - "weight": 17, + "weight": 18, "cookies": false, "type": "", "deprecated": false, @@ -2381,7 +2381,7 @@ }, "x-appwrite": { "method": "getSession", - "weight": 12, + "weight": 13, "cookies": false, "type": "", "deprecated": false, @@ -2444,7 +2444,7 @@ }, "x-appwrite": { "method": "updateSession", - "weight": 14, + "weight": 15, "cookies": false, "type": "", "deprecated": false, @@ -2502,7 +2502,7 @@ }, "x-appwrite": { "method": "deleteSession", - "weight": 13, + "weight": 14, "cookies": false, "type": "", "deprecated": false, @@ -2567,7 +2567,7 @@ }, "x-appwrite": { "method": "updateStatus", - "weight": 36, + "weight": 37, "cookies": false, "type": "", "deprecated": false, @@ -2622,7 +2622,7 @@ }, "x-appwrite": { "method": "createPushTarget", - "weight": 53, + "weight": 54, "cookies": false, "type": "", "deprecated": false, @@ -2708,7 +2708,7 @@ }, "x-appwrite": { "method": "updatePushTarget", - "weight": 54, + "weight": 55, "cookies": false, "type": "", "deprecated": false, @@ -2784,7 +2784,7 @@ }, "x-appwrite": { "method": "deletePushTarget", - "weight": 55, + "weight": 56, "cookies": false, "type": "", "deprecated": false, @@ -2847,7 +2847,7 @@ }, "x-appwrite": { "method": "createEmailToken", - "weight": 24, + "weight": 25, "cookies": false, "type": "", "deprecated": false, @@ -2933,7 +2933,7 @@ }, "x-appwrite": { "method": "createMagicURLToken", - "weight": 23, + "weight": 24, "cookies": false, "type": "", "deprecated": false, @@ -3025,7 +3025,7 @@ }, "x-appwrite": { "method": "createOAuth2Token", - "weight": 22, + "weight": 23, "cookies": false, "type": "webAuth", "deprecated": false, @@ -3163,7 +3163,7 @@ }, "x-appwrite": { "method": "createPhoneToken", - "weight": 27, + "weight": 28, "cookies": false, "type": "", "deprecated": false, @@ -3246,7 +3246,7 @@ }, "x-appwrite": { "method": "createVerification", - "weight": 39, + "weight": 40, "cookies": false, "type": "", "deprecated": false, @@ -3319,7 +3319,7 @@ }, "x-appwrite": { "method": "updateVerification", - "weight": 40, + "weight": 41, "cookies": false, "type": "", "deprecated": false, @@ -3401,7 +3401,7 @@ }, "x-appwrite": { "method": "createPhoneVerification", - "weight": 41, + "weight": 42, "cookies": false, "type": "", "deprecated": false, @@ -3457,7 +3457,7 @@ }, "x-appwrite": { "method": "updatePhoneVerification", - "weight": 42, + "weight": 43, "cookies": false, "type": "", "deprecated": false, @@ -3539,7 +3539,7 @@ }, "x-appwrite": { "method": "getBrowser", - "weight": 59, + "weight": 60, "cookies": false, "type": "location", "deprecated": false, @@ -3668,7 +3668,7 @@ }, "x-appwrite": { "method": "getCreditCard", - "weight": 58, + "weight": 59, "cookies": false, "type": "location", "deprecated": false, @@ -3801,7 +3801,7 @@ }, "x-appwrite": { "method": "getFavicon", - "weight": 62, + "weight": 63, "cookies": false, "type": "location", "deprecated": false, @@ -3868,7 +3868,7 @@ }, "x-appwrite": { "method": "getFlag", - "weight": 60, + "weight": 61, "cookies": false, "type": "location", "deprecated": false, @@ -4359,7 +4359,7 @@ }, "x-appwrite": { "method": "getImage", - "weight": 61, + "weight": 62, "cookies": false, "type": "location", "deprecated": false, @@ -4446,7 +4446,7 @@ }, "x-appwrite": { "method": "getInitials", - "weight": 64, + "weight": 65, "cookies": false, "type": "location", "deprecated": false, @@ -4541,7 +4541,7 @@ }, "x-appwrite": { "method": "getQR", - "weight": 63, + "weight": 64, "cookies": false, "type": "location", "deprecated": false, @@ -4636,7 +4636,7 @@ }, "x-appwrite": { "method": "listDocuments", - "weight": 108, + "weight": 109, "cookies": false, "type": "", "deprecated": false, @@ -4720,7 +4720,7 @@ }, "x-appwrite": { "method": "createDocument", - "weight": 107, + "weight": 108, "cookies": false, "type": "", "deprecated": false, @@ -4828,7 +4828,7 @@ }, "x-appwrite": { "method": "getDocument", - "weight": 109, + "weight": 110, "cookies": false, "type": "", "deprecated": false, @@ -4920,7 +4920,7 @@ }, "x-appwrite": { "method": "updateDocument", - "weight": 111, + "weight": 112, "cookies": false, "type": "", "deprecated": false, @@ -5019,7 +5019,7 @@ }, "x-appwrite": { "method": "deleteDocument", - "weight": 112, + "weight": 113, "cookies": false, "type": "", "deprecated": false, @@ -5101,7 +5101,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 304, + "weight": 305, "cookies": false, "type": "", "deprecated": false, @@ -5186,7 +5186,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 303, + "weight": 304, "cookies": false, "type": "", "deprecated": false, @@ -5307,7 +5307,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 305, + "weight": 306, "cookies": false, "type": "", "deprecated": false, @@ -5381,7 +5381,7 @@ }, "x-appwrite": { "method": "query", - "weight": 329, + "weight": 330, "cookies": false, "type": "graphql", "deprecated": false, @@ -5457,7 +5457,7 @@ }, "x-appwrite": { "method": "mutation", - "weight": 328, + "weight": 329, "cookies": false, "type": "graphql", "deprecated": false, @@ -5533,7 +5533,7 @@ }, "x-appwrite": { "method": "get", - "weight": 116, + "weight": 117, "cookies": false, "type": "", "deprecated": false, @@ -5589,7 +5589,7 @@ }, "x-appwrite": { "method": "listCodes", - "weight": 117, + "weight": 118, "cookies": false, "type": "", "deprecated": false, @@ -5645,7 +5645,7 @@ }, "x-appwrite": { "method": "listContinents", - "weight": 121, + "weight": 122, "cookies": false, "type": "", "deprecated": false, @@ -5701,7 +5701,7 @@ }, "x-appwrite": { "method": "listCountries", - "weight": 118, + "weight": 119, "cookies": false, "type": "", "deprecated": false, @@ -5757,7 +5757,7 @@ }, "x-appwrite": { "method": "listCountriesEU", - "weight": 119, + "weight": 120, "cookies": false, "type": "", "deprecated": false, @@ -5813,7 +5813,7 @@ }, "x-appwrite": { "method": "listCountriesPhones", - "weight": 120, + "weight": 121, "cookies": false, "type": "", "deprecated": false, @@ -5869,7 +5869,7 @@ }, "x-appwrite": { "method": "listCurrencies", - "weight": 122, + "weight": 123, "cookies": false, "type": "", "deprecated": false, @@ -5925,7 +5925,7 @@ }, "x-appwrite": { "method": "listLanguages", - "weight": 123, + "weight": 124, "cookies": false, "type": "", "deprecated": false, @@ -5981,7 +5981,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 380, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -6070,7 +6070,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 384, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -6145,7 +6145,7 @@ }, "x-appwrite": { "method": "listFiles", - "weight": 206, + "weight": 207, "cookies": false, "type": "", "deprecated": false, @@ -6230,7 +6230,7 @@ }, "x-appwrite": { "method": "createFile", - "weight": 205, + "weight": 206, "cookies": false, "type": "upload", "deprecated": false, @@ -6324,7 +6324,7 @@ }, "x-appwrite": { "method": "getFile", - "weight": 207, + "weight": 208, "cookies": false, "type": "", "deprecated": false, @@ -6396,7 +6396,7 @@ }, "x-appwrite": { "method": "updateFile", - "weight": 212, + "weight": 213, "cookies": false, "type": "", "deprecated": false, @@ -6487,7 +6487,7 @@ }, "x-appwrite": { "method": "deleteFile", - "weight": 213, + "weight": 214, "cookies": false, "type": "", "deprecated": false, @@ -6561,7 +6561,7 @@ }, "x-appwrite": { "method": "getFileDownload", - "weight": 209, + "weight": 210, "cookies": false, "type": "location", "deprecated": false, @@ -6635,7 +6635,7 @@ }, "x-appwrite": { "method": "getFilePreview", - "weight": 208, + "weight": 209, "cookies": false, "type": "location", "deprecated": false, @@ -6836,7 +6836,7 @@ }, "x-appwrite": { "method": "getFileView", - "weight": 210, + "weight": 211, "cookies": false, "type": "location", "deprecated": false, @@ -6910,7 +6910,7 @@ }, "x-appwrite": { "method": "list", - "weight": 217, + "weight": 218, "cookies": false, "type": "", "deprecated": false, @@ -6987,7 +6987,7 @@ }, "x-appwrite": { "method": "create", - "weight": 216, + "weight": 217, "cookies": false, "type": "", "deprecated": false, @@ -7081,7 +7081,7 @@ }, "x-appwrite": { "method": "get", - "weight": 218, + "weight": 219, "cookies": false, "type": "", "deprecated": false, @@ -7145,7 +7145,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 220, + "weight": 221, "cookies": false, "type": "", "deprecated": false, @@ -7222,7 +7222,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 222, + "weight": 223, "cookies": false, "type": "", "deprecated": false, @@ -7288,7 +7288,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 224, + "weight": 225, "cookies": false, "type": "", "deprecated": false, @@ -7373,7 +7373,7 @@ }, "x-appwrite": { "method": "createMembership", - "weight": 223, + "weight": 224, "cookies": false, "type": "", "deprecated": false, @@ -7490,7 +7490,7 @@ }, "x-appwrite": { "method": "getMembership", - "weight": 225, + "weight": 226, "cookies": false, "type": "", "deprecated": false, @@ -7562,7 +7562,7 @@ }, "x-appwrite": { "method": "updateMembership", - "weight": 226, + "weight": 227, "cookies": false, "type": "", "deprecated": false, @@ -7650,7 +7650,7 @@ }, "x-appwrite": { "method": "deleteMembership", - "weight": 228, + "weight": 229, "cookies": false, "type": "", "deprecated": false, @@ -7724,7 +7724,7 @@ }, "x-appwrite": { "method": "updateMembershipStatus", - "weight": 227, + "weight": 228, "cookies": false, "type": "", "deprecated": false, @@ -7822,7 +7822,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 219, + "weight": 220, "cookies": false, "type": "", "deprecated": false, @@ -7885,7 +7885,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 221, + "weight": 222, "cookies": false, "type": "", "deprecated": false, @@ -9584,7 +9584,7 @@ "format": "int32" }, "responseBody": { - "type": "payload", + "type": "string", "description": "HTTP response body. This will return empty unless execution is created as synchronous.", "x-example": "" }, diff --git a/app/config/specs/swagger2-1.6.x-console.json b/app/config/specs/swagger2-1.6.x-console.json index b7b6816c65..ef651e4723 100644 --- a/app/config/specs/swagger2-1.6.x-console.json +++ b/app/config/specs/swagger2-1.6.x-console.json @@ -99,7 +99,7 @@ }, "x-appwrite": { "method": "get", - "weight": 8, + "weight": 9, "cookies": false, "type": "", "deprecated": false, @@ -151,7 +151,7 @@ }, "x-appwrite": { "method": "create", - "weight": 7, + "weight": 8, "cookies": false, "type": "", "deprecated": false, @@ -237,7 +237,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 9, + "weight": 10, "cookies": false, "type": "", "deprecated": false, @@ -289,7 +289,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 33, + "weight": 34, "cookies": false, "type": "", "deprecated": false, @@ -370,7 +370,7 @@ }, "x-appwrite": { "method": "listIdentities", - "weight": 56, + "weight": 57, "cookies": false, "type": "", "deprecated": false, @@ -433,7 +433,7 @@ }, "x-appwrite": { "method": "deleteIdentity", - "weight": 57, + "weight": 58, "cookies": false, "type": "", "deprecated": false, @@ -497,7 +497,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 28, + "weight": 29, "cookies": false, "type": "", "deprecated": false, @@ -550,7 +550,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 30, + "weight": 31, "cookies": false, "type": "", "deprecated": false, @@ -618,7 +618,7 @@ }, "x-appwrite": { "method": "updateMFA", - "weight": 43, + "weight": 44, "cookies": false, "type": "", "deprecated": false, @@ -692,7 +692,7 @@ }, "x-appwrite": { "method": "createMfaAuthenticator", - "weight": 45, + "weight": 46, "cookies": false, "type": "", "deprecated": false, @@ -759,7 +759,7 @@ }, "x-appwrite": { "method": "updateMfaAuthenticator", - "weight": 46, + "weight": 47, "cookies": false, "type": "", "deprecated": false, @@ -839,7 +839,7 @@ }, "x-appwrite": { "method": "deleteMfaAuthenticator", - "weight": 50, + "weight": 51, "cookies": false, "type": "", "deprecated": false, @@ -908,7 +908,7 @@ }, "x-appwrite": { "method": "createMfaChallenge", - "weight": 51, + "weight": 52, "cookies": false, "type": "", "deprecated": false, @@ -982,7 +982,7 @@ }, "x-appwrite": { "method": "updateMfaChallenge", - "weight": 52, + "weight": 53, "cookies": false, "type": "", "deprecated": false, @@ -1063,7 +1063,7 @@ }, "x-appwrite": { "method": "listMfaFactors", - "weight": 44, + "weight": 45, "cookies": false, "type": "", "deprecated": false, @@ -1117,7 +1117,7 @@ }, "x-appwrite": { "method": "getMfaRecoveryCodes", - "weight": 49, + "weight": 50, "cookies": false, "type": "", "deprecated": false, @@ -1169,7 +1169,7 @@ }, "x-appwrite": { "method": "createMfaRecoveryCodes", - "weight": 47, + "weight": 48, "cookies": false, "type": "", "deprecated": false, @@ -1221,7 +1221,7 @@ }, "x-appwrite": { "method": "updateMfaRecoveryCodes", - "weight": 48, + "weight": 49, "cookies": false, "type": "", "deprecated": false, @@ -1275,7 +1275,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 31, + "weight": 32, "cookies": false, "type": "", "deprecated": false, @@ -1349,7 +1349,7 @@ }, "x-appwrite": { "method": "updatePassword", - "weight": 32, + "weight": 33, "cookies": false, "type": "", "deprecated": false, @@ -1429,7 +1429,7 @@ }, "x-appwrite": { "method": "updatePhone", - "weight": 34, + "weight": 35, "cookies": false, "type": "", "deprecated": false, @@ -1510,7 +1510,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 29, + "weight": 30, "cookies": false, "type": "", "deprecated": false, @@ -1562,7 +1562,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 35, + "weight": 36, "cookies": false, "type": "", "deprecated": false, @@ -1636,7 +1636,7 @@ }, "x-appwrite": { "method": "createRecovery", - "weight": 37, + "weight": 38, "cookies": false, "type": "", "deprecated": false, @@ -1718,7 +1718,7 @@ }, "x-appwrite": { "method": "updateRecovery", - "weight": 38, + "weight": 39, "cookies": false, "type": "", "deprecated": false, @@ -1806,7 +1806,7 @@ }, "x-appwrite": { "method": "listSessions", - "weight": 10, + "weight": 11, "cookies": false, "type": "", "deprecated": false, @@ -1853,7 +1853,7 @@ }, "x-appwrite": { "method": "deleteSessions", - "weight": 11, + "weight": 12, "cookies": false, "type": "", "deprecated": false, @@ -1907,7 +1907,7 @@ }, "x-appwrite": { "method": "createAnonymousSession", - "weight": 16, + "weight": 17, "cookies": false, "type": "", "deprecated": false, @@ -1960,7 +1960,7 @@ }, "x-appwrite": { "method": "createEmailPasswordSession", - "weight": 15, + "weight": 16, "cookies": false, "type": "", "deprecated": false, @@ -2040,7 +2040,7 @@ }, "x-appwrite": { "method": "updateMagicURLSession", - "weight": 25, + "weight": 26, "cookies": false, "type": "", "deprecated": true, @@ -2117,7 +2117,7 @@ }, "x-appwrite": { "method": "createOAuth2Session", - "weight": 18, + "weight": 19, "cookies": false, "type": "webAuth", "deprecated": false, @@ -2255,7 +2255,7 @@ }, "x-appwrite": { "method": "updatePhoneSession", - "weight": 26, + "weight": 27, "cookies": false, "type": "", "deprecated": true, @@ -2335,7 +2335,7 @@ }, "x-appwrite": { "method": "createSession", - "weight": 17, + "weight": 18, "cookies": false, "type": "", "deprecated": false, @@ -2415,7 +2415,7 @@ }, "x-appwrite": { "method": "getSession", - "weight": 12, + "weight": 13, "cookies": false, "type": "", "deprecated": false, @@ -2477,7 +2477,7 @@ }, "x-appwrite": { "method": "updateSession", - "weight": 14, + "weight": 15, "cookies": false, "type": "", "deprecated": false, @@ -2534,7 +2534,7 @@ }, "x-appwrite": { "method": "deleteSession", - "weight": 13, + "weight": 14, "cookies": false, "type": "", "deprecated": false, @@ -2598,7 +2598,7 @@ }, "x-appwrite": { "method": "updateStatus", - "weight": 36, + "weight": 37, "cookies": false, "type": "", "deprecated": false, @@ -2652,7 +2652,7 @@ }, "x-appwrite": { "method": "createPushTarget", - "weight": 53, + "weight": 54, "cookies": false, "type": "", "deprecated": false, @@ -2737,7 +2737,7 @@ }, "x-appwrite": { "method": "updatePushTarget", - "weight": 54, + "weight": 55, "cookies": false, "type": "", "deprecated": false, @@ -2812,7 +2812,7 @@ }, "x-appwrite": { "method": "deletePushTarget", - "weight": 55, + "weight": 56, "cookies": false, "type": "", "deprecated": false, @@ -2874,7 +2874,7 @@ }, "x-appwrite": { "method": "createEmailToken", - "weight": 24, + "weight": 25, "cookies": false, "type": "", "deprecated": false, @@ -2960,7 +2960,7 @@ }, "x-appwrite": { "method": "createMagicURLToken", - "weight": 23, + "weight": 24, "cookies": false, "type": "", "deprecated": false, @@ -3052,7 +3052,7 @@ }, "x-appwrite": { "method": "createOAuth2Token", - "weight": 22, + "weight": 23, "cookies": false, "type": "webAuth", "deprecated": false, @@ -3190,7 +3190,7 @@ }, "x-appwrite": { "method": "createPhoneToken", - "weight": 27, + "weight": 28, "cookies": false, "type": "", "deprecated": false, @@ -3273,7 +3273,7 @@ }, "x-appwrite": { "method": "createVerification", - "weight": 39, + "weight": 40, "cookies": false, "type": "", "deprecated": false, @@ -3345,7 +3345,7 @@ }, "x-appwrite": { "method": "updateVerification", - "weight": 40, + "weight": 41, "cookies": false, "type": "", "deprecated": false, @@ -3426,7 +3426,7 @@ }, "x-appwrite": { "method": "createPhoneVerification", - "weight": 41, + "weight": 42, "cookies": false, "type": "", "deprecated": false, @@ -3481,7 +3481,7 @@ }, "x-appwrite": { "method": "updatePhoneVerification", - "weight": 42, + "weight": 43, "cookies": false, "type": "", "deprecated": false, @@ -3562,7 +3562,7 @@ }, "x-appwrite": { "method": "getBrowser", - "weight": 59, + "weight": 60, "cookies": false, "type": "location", "deprecated": false, @@ -3691,7 +3691,7 @@ }, "x-appwrite": { "method": "getCreditCard", - "weight": 58, + "weight": 59, "cookies": false, "type": "location", "deprecated": false, @@ -3824,7 +3824,7 @@ }, "x-appwrite": { "method": "getFavicon", - "weight": 62, + "weight": 63, "cookies": false, "type": "location", "deprecated": false, @@ -3891,7 +3891,7 @@ }, "x-appwrite": { "method": "getFlag", - "weight": 60, + "weight": 61, "cookies": false, "type": "location", "deprecated": false, @@ -4382,7 +4382,7 @@ }, "x-appwrite": { "method": "getImage", - "weight": 61, + "weight": 62, "cookies": false, "type": "location", "deprecated": false, @@ -4469,7 +4469,7 @@ }, "x-appwrite": { "method": "getInitials", - "weight": 64, + "weight": 65, "cookies": false, "type": "location", "deprecated": false, @@ -4564,7 +4564,7 @@ }, "x-appwrite": { "method": "getQR", - "weight": 63, + "weight": 64, "cookies": false, "type": "location", "deprecated": false, @@ -4659,7 +4659,7 @@ }, "x-appwrite": { "method": "chat", - "weight": 331, + "weight": 332, "cookies": false, "type": "", "deprecated": false, @@ -4731,7 +4731,7 @@ }, "x-appwrite": { "method": "variables", - "weight": 330, + "weight": 331, "cookies": false, "type": "", "deprecated": false, @@ -4783,7 +4783,7 @@ }, "x-appwrite": { "method": "list", - "weight": 69, + "weight": 70, "cookies": false, "type": "", "deprecated": false, @@ -4857,7 +4857,7 @@ }, "x-appwrite": { "method": "create", - "weight": 68, + "weight": 69, "cookies": false, "type": "", "deprecated": false, @@ -4943,7 +4943,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 113, + "weight": 114, "cookies": false, "type": "", "deprecated": false, @@ -5017,7 +5017,7 @@ }, "x-appwrite": { "method": "get", - "weight": 70, + "weight": 71, "cookies": false, "type": "", "deprecated": false, @@ -5078,7 +5078,7 @@ }, "x-appwrite": { "method": "update", - "weight": 72, + "weight": 73, "cookies": false, "type": "", "deprecated": false, @@ -5158,7 +5158,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 73, + "weight": 74, "cookies": false, "type": "", "deprecated": false, @@ -5221,7 +5221,7 @@ }, "x-appwrite": { "method": "listCollections", - "weight": 75, + "weight": 76, "cookies": false, "type": "", "deprecated": false, @@ -5303,7 +5303,7 @@ }, "x-appwrite": { "method": "createCollection", - "weight": 74, + "weight": 75, "cookies": false, "type": "", "deprecated": false, @@ -5412,7 +5412,7 @@ }, "x-appwrite": { "method": "getCollection", - "weight": 76, + "weight": 77, "cookies": false, "type": "", "deprecated": false, @@ -5481,7 +5481,7 @@ }, "x-appwrite": { "method": "updateCollection", - "weight": 78, + "weight": 79, "cookies": false, "type": "", "deprecated": false, @@ -5584,7 +5584,7 @@ }, "x-appwrite": { "method": "deleteCollection", - "weight": 79, + "weight": 80, "cookies": false, "type": "", "deprecated": false, @@ -5655,7 +5655,7 @@ }, "x-appwrite": { "method": "listAttributes", - "weight": 90, + "weight": 91, "cookies": false, "type": "", "deprecated": false, @@ -5738,7 +5738,7 @@ }, "x-appwrite": { "method": "createBooleanAttribute", - "weight": 87, + "weight": 88, "cookies": false, "type": "", "deprecated": false, @@ -5844,7 +5844,7 @@ }, "x-appwrite": { "method": "updateBooleanAttribute", - "weight": 99, + "weight": 100, "cookies": false, "type": "", "deprecated": false, @@ -5954,7 +5954,7 @@ }, "x-appwrite": { "method": "createDatetimeAttribute", - "weight": 88, + "weight": 89, "cookies": false, "type": "", "deprecated": false, @@ -6060,7 +6060,7 @@ }, "x-appwrite": { "method": "updateDatetimeAttribute", - "weight": 100, + "weight": 101, "cookies": false, "type": "", "deprecated": false, @@ -6170,7 +6170,7 @@ }, "x-appwrite": { "method": "createEmailAttribute", - "weight": 81, + "weight": 82, "cookies": false, "type": "", "deprecated": false, @@ -6276,7 +6276,7 @@ }, "x-appwrite": { "method": "updateEmailAttribute", - "weight": 93, + "weight": 94, "cookies": false, "type": "", "deprecated": false, @@ -6386,7 +6386,7 @@ }, "x-appwrite": { "method": "createEnumAttribute", - "weight": 82, + "weight": 83, "cookies": false, "type": "", "deprecated": false, @@ -6502,7 +6502,7 @@ }, "x-appwrite": { "method": "updateEnumAttribute", - "weight": 94, + "weight": 95, "cookies": false, "type": "", "deprecated": false, @@ -6622,7 +6622,7 @@ }, "x-appwrite": { "method": "createFloatAttribute", - "weight": 86, + "weight": 87, "cookies": false, "type": "", "deprecated": false, @@ -6740,7 +6740,7 @@ }, "x-appwrite": { "method": "updateFloatAttribute", - "weight": 98, + "weight": 99, "cookies": false, "type": "", "deprecated": false, @@ -6864,7 +6864,7 @@ }, "x-appwrite": { "method": "createIntegerAttribute", - "weight": 85, + "weight": 86, "cookies": false, "type": "", "deprecated": false, @@ -6982,7 +6982,7 @@ }, "x-appwrite": { "method": "updateIntegerAttribute", - "weight": 97, + "weight": 98, "cookies": false, "type": "", "deprecated": false, @@ -7106,7 +7106,7 @@ }, "x-appwrite": { "method": "createIpAttribute", - "weight": 83, + "weight": 84, "cookies": false, "type": "", "deprecated": false, @@ -7212,7 +7212,7 @@ }, "x-appwrite": { "method": "updateIpAttribute", - "weight": 95, + "weight": 96, "cookies": false, "type": "", "deprecated": false, @@ -7322,7 +7322,7 @@ }, "x-appwrite": { "method": "createRelationshipAttribute", - "weight": 89, + "weight": 90, "cookies": false, "type": "", "deprecated": false, @@ -7457,7 +7457,7 @@ }, "x-appwrite": { "method": "createStringAttribute", - "weight": 80, + "weight": 81, "cookies": false, "type": "", "deprecated": false, @@ -7576,7 +7576,7 @@ }, "x-appwrite": { "method": "updateStringAttribute", - "weight": 92, + "weight": 93, "cookies": false, "type": "", "deprecated": false, @@ -7692,7 +7692,7 @@ }, "x-appwrite": { "method": "createUrlAttribute", - "weight": 84, + "weight": 85, "cookies": false, "type": "", "deprecated": false, @@ -7798,7 +7798,7 @@ }, "x-appwrite": { "method": "updateUrlAttribute", - "weight": 96, + "weight": 97, "cookies": false, "type": "", "deprecated": false, @@ -7939,7 +7939,7 @@ }, "x-appwrite": { "method": "getAttribute", - "weight": 91, + "weight": 92, "cookies": false, "type": "", "deprecated": false, @@ -8010,7 +8010,7 @@ }, "x-appwrite": { "method": "deleteAttribute", - "weight": 102, + "weight": 103, "cookies": false, "type": "", "deprecated": false, @@ -8086,7 +8086,7 @@ }, "x-appwrite": { "method": "updateRelationshipAttribute", - "weight": 101, + "weight": 102, "cookies": false, "type": "", "deprecated": false, @@ -8192,7 +8192,7 @@ }, "x-appwrite": { "method": "listDocuments", - "weight": 108, + "weight": 109, "cookies": false, "type": "", "deprecated": false, @@ -8276,7 +8276,7 @@ }, "x-appwrite": { "method": "createDocument", - "weight": 107, + "weight": 108, "cookies": false, "type": "", "deprecated": false, @@ -8384,7 +8384,7 @@ }, "x-appwrite": { "method": "getDocument", - "weight": 109, + "weight": 110, "cookies": false, "type": "", "deprecated": false, @@ -8476,7 +8476,7 @@ }, "x-appwrite": { "method": "updateDocument", - "weight": 111, + "weight": 112, "cookies": false, "type": "", "deprecated": false, @@ -8575,7 +8575,7 @@ }, "x-appwrite": { "method": "deleteDocument", - "weight": 112, + "weight": 113, "cookies": false, "type": "", "deprecated": false, @@ -8657,7 +8657,7 @@ }, "x-appwrite": { "method": "listDocumentLogs", - "weight": 110, + "weight": 111, "cookies": false, "type": "", "deprecated": false, @@ -8747,7 +8747,7 @@ }, "x-appwrite": { "method": "listIndexes", - "weight": 104, + "weight": 105, "cookies": false, "type": "", "deprecated": false, @@ -8828,7 +8828,7 @@ }, "x-appwrite": { "method": "createIndex", - "weight": 103, + "weight": 104, "cookies": false, "type": "", "deprecated": false, @@ -8950,7 +8950,7 @@ }, "x-appwrite": { "method": "getIndex", - "weight": 105, + "weight": 106, "cookies": false, "type": "", "deprecated": false, @@ -9021,7 +9021,7 @@ }, "x-appwrite": { "method": "deleteIndex", - "weight": 106, + "weight": 107, "cookies": false, "type": "", "deprecated": false, @@ -9099,7 +9099,7 @@ }, "x-appwrite": { "method": "listCollectionLogs", - "weight": 77, + "weight": 78, "cookies": false, "type": "", "deprecated": false, @@ -9181,7 +9181,7 @@ }, "x-appwrite": { "method": "getCollectionUsage", - "weight": 115, + "weight": 116, "cookies": false, "type": "", "deprecated": false, @@ -9271,7 +9271,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 71, + "weight": 72, "cookies": false, "type": "", "deprecated": false, @@ -9345,7 +9345,7 @@ }, "x-appwrite": { "method": "getDatabaseUsage", - "weight": 114, + "weight": 115, "cookies": false, "type": "", "deprecated": false, @@ -9427,7 +9427,7 @@ }, "x-appwrite": { "method": "list", - "weight": 287, + "weight": 288, "cookies": false, "type": "", "deprecated": false, @@ -9501,7 +9501,7 @@ }, "x-appwrite": { "method": "create", - "weight": 286, + "weight": 287, "cookies": false, "type": "", "deprecated": false, @@ -9559,6 +9559,7 @@ "node-19.0", "node-20.0", "node-21.0", + "node-22", "php-8.0", "php-8.1", "php-8.2", @@ -9577,6 +9578,8 @@ "deno-1.24", "deno-1.35", "deno-1.40", + "deno-1.46", + "deno-2.0", "dart-2.15", "dart-2.16", "dart-2.17", @@ -9584,23 +9587,28 @@ "dart-3.0", "dart-3.1", "dart-3.3", - "dotnet-3.1", + "dart-3.5", "dotnet-6.0", "dotnet-7.0", + "dotnet-8.0", "java-8.0", "java-11.0", "java-17.0", "java-18.0", "java-21.0", + "java-22", "swift-5.5", "swift-5.8", "swift-5.9", + "swift-5.10", "kotlin-1.6", "kotlin-1.8", "kotlin-1.9", + "kotlin-2.0", "cpp-17", "cpp-20", "bun-1.0", + "bun-1.1", "go-1.23" ], "x-enum-name": null, @@ -9764,7 +9772,7 @@ }, "x-appwrite": { "method": "listRuntimes", - "weight": 288, + "weight": 289, "cookies": false, "type": "", "deprecated": false, @@ -9817,7 +9825,7 @@ }, "x-appwrite": { "method": "listSpecifications", - "weight": 289, + "weight": 290, "cookies": false, "type": "", "deprecated": false, @@ -9871,7 +9879,7 @@ }, "x-appwrite": { "method": "listTemplates", - "weight": 312, + "weight": 313, "cookies": false, "type": "", "deprecated": false, @@ -9969,7 +9977,7 @@ }, "x-appwrite": { "method": "getTemplate", - "weight": 313, + "weight": 314, "cookies": false, "type": "", "deprecated": false, @@ -10031,7 +10039,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 292, + "weight": 293, "cookies": false, "type": "", "deprecated": false, @@ -10105,7 +10113,7 @@ }, "x-appwrite": { "method": "get", - "weight": 290, + "weight": 291, "cookies": false, "type": "", "deprecated": false, @@ -10166,7 +10174,7 @@ }, "x-appwrite": { "method": "update", - "weight": 293, + "weight": 294, "cookies": false, "type": "", "deprecated": false, @@ -10226,6 +10234,7 @@ "node-19.0", "node-20.0", "node-21.0", + "node-22", "php-8.0", "php-8.1", "php-8.2", @@ -10244,6 +10253,8 @@ "deno-1.24", "deno-1.35", "deno-1.40", + "deno-1.46", + "deno-2.0", "dart-2.15", "dart-2.16", "dart-2.17", @@ -10251,23 +10262,28 @@ "dart-3.0", "dart-3.1", "dart-3.3", - "dotnet-3.1", + "dart-3.5", "dotnet-6.0", "dotnet-7.0", + "dotnet-8.0", "java-8.0", "java-11.0", "java-17.0", "java-18.0", "java-21.0", + "java-22", "swift-5.5", "swift-5.8", "swift-5.9", + "swift-5.10", "kotlin-1.6", "kotlin-1.8", "kotlin-1.9", + "kotlin-2.0", "cpp-17", "cpp-20", "bun-1.0", + "bun-1.1", "go-1.23" ], "x-enum-name": null, @@ -10399,7 +10415,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 296, + "weight": 297, "cookies": false, "type": "", "deprecated": false, @@ -10462,7 +10478,7 @@ }, "x-appwrite": { "method": "listDeployments", - "weight": 298, + "weight": 299, "cookies": false, "type": "", "deprecated": false, @@ -10544,7 +10560,7 @@ }, "x-appwrite": { "method": "createDeployment", - "weight": 297, + "weight": 298, "cookies": false, "type": "upload", "deprecated": false, @@ -10638,7 +10654,7 @@ }, "x-appwrite": { "method": "getDeployment", - "weight": 299, + "weight": 300, "cookies": false, "type": "", "deprecated": false, @@ -10707,7 +10723,7 @@ }, "x-appwrite": { "method": "updateDeployment", - "weight": 295, + "weight": 296, "cookies": false, "type": "", "deprecated": false, @@ -10771,7 +10787,7 @@ }, "x-appwrite": { "method": "deleteDeployment", - "weight": 300, + "weight": 301, "cookies": false, "type": "", "deprecated": false, @@ -10837,7 +10853,7 @@ }, "x-appwrite": { "method": "createBuild", - "weight": 301, + "weight": 302, "cookies": false, "type": "", "deprecated": false, @@ -10921,7 +10937,7 @@ }, "x-appwrite": { "method": "updateDeploymentBuild", - "weight": 302, + "weight": 303, "cookies": false, "type": "", "deprecated": false, @@ -10992,7 +11008,7 @@ }, "x-appwrite": { "method": "getDeploymentDownload", - "weight": 294, + "weight": 295, "cookies": false, "type": "location", "deprecated": false, @@ -11065,7 +11081,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 304, + "weight": 305, "cookies": false, "type": "", "deprecated": false, @@ -11150,7 +11166,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 303, + "weight": 304, "cookies": false, "type": "", "deprecated": false, @@ -11271,7 +11287,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 305, + "weight": 306, "cookies": false, "type": "", "deprecated": false, @@ -11338,7 +11354,7 @@ }, "x-appwrite": { "method": "deleteExecution", - "weight": 306, + "weight": 307, "cookies": false, "type": "", "deprecated": false, @@ -11409,7 +11425,7 @@ }, "x-appwrite": { "method": "getFunctionUsage", - "weight": 291, + "weight": 292, "cookies": false, "type": "", "deprecated": false, @@ -11491,7 +11507,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 308, + "weight": 309, "cookies": false, "type": "", "deprecated": false, @@ -11552,7 +11568,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 307, + "weight": 308, "cookies": false, "type": "", "deprecated": false, @@ -11640,7 +11656,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 309, + "weight": 310, "cookies": false, "type": "", "deprecated": false, @@ -11709,7 +11725,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 310, + "weight": 311, "cookies": false, "type": "", "deprecated": false, @@ -11797,7 +11813,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 311, + "weight": 312, "cookies": false, "type": "", "deprecated": false, @@ -11868,7 +11884,7 @@ }, "x-appwrite": { "method": "query", - "weight": 329, + "weight": 330, "cookies": false, "type": "graphql", "deprecated": false, @@ -11944,7 +11960,7 @@ }, "x-appwrite": { "method": "mutation", - "weight": 328, + "weight": 329, "cookies": false, "type": "graphql", "deprecated": false, @@ -12020,7 +12036,7 @@ }, "x-appwrite": { "method": "get", - "weight": 124, + "weight": 125, "cookies": false, "type": "", "deprecated": false, @@ -12073,7 +12089,7 @@ }, "x-appwrite": { "method": "getAntivirus", - "weight": 146, + "weight": 147, "cookies": false, "type": "", "deprecated": false, @@ -12126,7 +12142,7 @@ }, "x-appwrite": { "method": "getCache", - "weight": 127, + "weight": 128, "cookies": false, "type": "", "deprecated": false, @@ -12179,7 +12195,7 @@ }, "x-appwrite": { "method": "getCertificate", - "weight": 133, + "weight": 134, "cookies": false, "type": "", "deprecated": false, @@ -12241,7 +12257,7 @@ }, "x-appwrite": { "method": "getDB", - "weight": 126, + "weight": 127, "cookies": false, "type": "", "deprecated": false, @@ -12294,7 +12310,7 @@ }, "x-appwrite": { "method": "getPubSub", - "weight": 129, + "weight": 130, "cookies": false, "type": "", "deprecated": false, @@ -12347,7 +12363,7 @@ }, "x-appwrite": { "method": "getQueue", - "weight": 128, + "weight": 129, "cookies": false, "type": "", "deprecated": false, @@ -12400,7 +12416,7 @@ }, "x-appwrite": { "method": "getQueueBuilds", - "weight": 135, + "weight": 136, "cookies": false, "type": "", "deprecated": false, @@ -12464,7 +12480,7 @@ }, "x-appwrite": { "method": "getQueueCertificates", - "weight": 134, + "weight": 135, "cookies": false, "type": "", "deprecated": false, @@ -12528,7 +12544,7 @@ }, "x-appwrite": { "method": "getQueueDatabases", - "weight": 136, + "weight": 137, "cookies": false, "type": "", "deprecated": false, @@ -12601,7 +12617,7 @@ }, "x-appwrite": { "method": "getQueueDeletes", - "weight": 137, + "weight": 138, "cookies": false, "type": "", "deprecated": false, @@ -12665,7 +12681,7 @@ }, "x-appwrite": { "method": "getFailedJobs", - "weight": 147, + "weight": 148, "cookies": false, "type": "", "deprecated": false, @@ -12753,7 +12769,7 @@ }, "x-appwrite": { "method": "getQueueFunctions", - "weight": 141, + "weight": 142, "cookies": false, "type": "", "deprecated": false, @@ -12817,7 +12833,7 @@ }, "x-appwrite": { "method": "getQueueLogs", - "weight": 132, + "weight": 133, "cookies": false, "type": "", "deprecated": false, @@ -12881,7 +12897,7 @@ }, "x-appwrite": { "method": "getQueueMails", - "weight": 138, + "weight": 139, "cookies": false, "type": "", "deprecated": false, @@ -12945,7 +12961,7 @@ }, "x-appwrite": { "method": "getQueueMessaging", - "weight": 139, + "weight": 140, "cookies": false, "type": "", "deprecated": false, @@ -13009,7 +13025,7 @@ }, "x-appwrite": { "method": "getQueueMigrations", - "weight": 140, + "weight": 141, "cookies": false, "type": "", "deprecated": false, @@ -13073,7 +13089,7 @@ }, "x-appwrite": { "method": "getQueueUsage", - "weight": 142, + "weight": 143, "cookies": false, "type": "", "deprecated": false, @@ -13137,7 +13153,7 @@ }, "x-appwrite": { "method": "getQueueUsageDump", - "weight": 143, + "weight": 144, "cookies": false, "type": "", "deprecated": false, @@ -13201,7 +13217,7 @@ }, "x-appwrite": { "method": "getQueueWebhooks", - "weight": 131, + "weight": 132, "cookies": false, "type": "", "deprecated": false, @@ -13265,7 +13281,7 @@ }, "x-appwrite": { "method": "getStorage", - "weight": 145, + "weight": 146, "cookies": false, "type": "", "deprecated": false, @@ -13318,7 +13334,7 @@ }, "x-appwrite": { "method": "getStorageLocal", - "weight": 144, + "weight": 145, "cookies": false, "type": "", "deprecated": false, @@ -13371,7 +13387,7 @@ }, "x-appwrite": { "method": "getTime", - "weight": 130, + "weight": 131, "cookies": false, "type": "", "deprecated": false, @@ -13424,7 +13440,7 @@ }, "x-appwrite": { "method": "get", - "weight": 116, + "weight": 117, "cookies": false, "type": "", "deprecated": false, @@ -13480,7 +13496,7 @@ }, "x-appwrite": { "method": "listCodes", - "weight": 117, + "weight": 118, "cookies": false, "type": "", "deprecated": false, @@ -13536,7 +13552,7 @@ }, "x-appwrite": { "method": "listContinents", - "weight": 121, + "weight": 122, "cookies": false, "type": "", "deprecated": false, @@ -13592,7 +13608,7 @@ }, "x-appwrite": { "method": "listCountries", - "weight": 118, + "weight": 119, "cookies": false, "type": "", "deprecated": false, @@ -13648,7 +13664,7 @@ }, "x-appwrite": { "method": "listCountriesEU", - "weight": 119, + "weight": 120, "cookies": false, "type": "", "deprecated": false, @@ -13704,7 +13720,7 @@ }, "x-appwrite": { "method": "listCountriesPhones", - "weight": 120, + "weight": 121, "cookies": false, "type": "", "deprecated": false, @@ -13760,7 +13776,7 @@ }, "x-appwrite": { "method": "listCurrencies", - "weight": 122, + "weight": 123, "cookies": false, "type": "", "deprecated": false, @@ -13816,7 +13832,7 @@ }, "x-appwrite": { "method": "listLanguages", - "weight": 123, + "weight": 124, "cookies": false, "type": "", "deprecated": false, @@ -13872,7 +13888,7 @@ }, "x-appwrite": { "method": "listMessages", - "weight": 388, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -13949,7 +13965,7 @@ }, "x-appwrite": { "method": "createEmail", - "weight": 385, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -14109,7 +14125,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 392, + "weight": 393, "cookies": false, "type": "", "deprecated": false, @@ -14266,7 +14282,7 @@ }, "x-appwrite": { "method": "createPush", - "weight": 387, + "weight": 388, "cookies": false, "type": "", "deprecated": false, @@ -14441,7 +14457,7 @@ }, "x-appwrite": { "method": "updatePush", - "weight": 394, + "weight": 395, "cookies": false, "type": "", "deprecated": false, @@ -14613,7 +14629,7 @@ }, "x-appwrite": { "method": "createSms", - "weight": 386, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -14733,7 +14749,7 @@ }, "x-appwrite": { "method": "updateSms", - "weight": 393, + "weight": 394, "cookies": false, "type": "", "deprecated": false, @@ -14851,7 +14867,7 @@ }, "x-appwrite": { "method": "getMessage", - "weight": 391, + "weight": 392, "cookies": false, "type": "", "deprecated": false, @@ -14910,7 +14926,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 395, + "weight": 396, "cookies": false, "type": "", "deprecated": false, @@ -14974,7 +14990,7 @@ }, "x-appwrite": { "method": "listMessageLogs", - "weight": 389, + "weight": 390, "cookies": false, "type": "", "deprecated": false, @@ -15050,7 +15066,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 390, + "weight": 391, "cookies": false, "type": "", "deprecated": false, @@ -15126,7 +15142,7 @@ }, "x-appwrite": { "method": "listProviders", - "weight": 360, + "weight": 361, "cookies": false, "type": "", "deprecated": false, @@ -15203,7 +15219,7 @@ }, "x-appwrite": { "method": "createApnsProvider", - "weight": 359, + "weight": 360, "cookies": false, "type": "", "deprecated": false, @@ -15320,7 +15336,7 @@ }, "x-appwrite": { "method": "updateApnsProvider", - "weight": 372, + "weight": 373, "cookies": false, "type": "", "deprecated": false, @@ -15435,7 +15451,7 @@ }, "x-appwrite": { "method": "createFcmProvider", - "weight": 358, + "weight": 359, "cookies": false, "type": "", "deprecated": false, @@ -15528,7 +15544,7 @@ }, "x-appwrite": { "method": "updateFcmProvider", - "weight": 371, + "weight": 372, "cookies": false, "type": "", "deprecated": false, @@ -15619,7 +15635,7 @@ }, "x-appwrite": { "method": "createMailgunProvider", - "weight": 350, + "weight": 351, "cookies": false, "type": "", "deprecated": false, @@ -15748,7 +15764,7 @@ }, "x-appwrite": { "method": "updateMailgunProvider", - "weight": 363, + "weight": 364, "cookies": false, "type": "", "deprecated": false, @@ -15875,7 +15891,7 @@ }, "x-appwrite": { "method": "createMsg91Provider", - "weight": 353, + "weight": 354, "cookies": false, "type": "", "deprecated": false, @@ -15980,7 +15996,7 @@ }, "x-appwrite": { "method": "updateMsg91Provider", - "weight": 366, + "weight": 367, "cookies": false, "type": "", "deprecated": false, @@ -16083,7 +16099,7 @@ }, "x-appwrite": { "method": "createSendgridProvider", - "weight": 351, + "weight": 352, "cookies": false, "type": "", "deprecated": false, @@ -16200,7 +16216,7 @@ }, "x-appwrite": { "method": "updateSendgridProvider", - "weight": 364, + "weight": 365, "cookies": false, "type": "", "deprecated": false, @@ -16315,7 +16331,7 @@ }, "x-appwrite": { "method": "createSmtpProvider", - "weight": 352, + "weight": 353, "cookies": false, "type": "", "deprecated": false, @@ -16476,7 +16492,7 @@ }, "x-appwrite": { "method": "updateSmtpProvider", - "weight": 365, + "weight": 366, "cookies": false, "type": "", "deprecated": false, @@ -16634,7 +16650,7 @@ }, "x-appwrite": { "method": "createTelesignProvider", - "weight": 354, + "weight": 355, "cookies": false, "type": "", "deprecated": false, @@ -16739,7 +16755,7 @@ }, "x-appwrite": { "method": "updateTelesignProvider", - "weight": 367, + "weight": 368, "cookies": false, "type": "", "deprecated": false, @@ -16842,7 +16858,7 @@ }, "x-appwrite": { "method": "createTextmagicProvider", - "weight": 355, + "weight": 356, "cookies": false, "type": "", "deprecated": false, @@ -16947,7 +16963,7 @@ }, "x-appwrite": { "method": "updateTextmagicProvider", - "weight": 368, + "weight": 369, "cookies": false, "type": "", "deprecated": false, @@ -17050,7 +17066,7 @@ }, "x-appwrite": { "method": "createTwilioProvider", - "weight": 356, + "weight": 357, "cookies": false, "type": "", "deprecated": false, @@ -17155,7 +17171,7 @@ }, "x-appwrite": { "method": "updateTwilioProvider", - "weight": 369, + "weight": 370, "cookies": false, "type": "", "deprecated": false, @@ -17258,7 +17274,7 @@ }, "x-appwrite": { "method": "createVonageProvider", - "weight": 357, + "weight": 358, "cookies": false, "type": "", "deprecated": false, @@ -17363,7 +17379,7 @@ }, "x-appwrite": { "method": "updateVonageProvider", - "weight": 370, + "weight": 371, "cookies": false, "type": "", "deprecated": false, @@ -17466,7 +17482,7 @@ }, "x-appwrite": { "method": "getProvider", - "weight": 362, + "weight": 363, "cookies": false, "type": "", "deprecated": false, @@ -17525,7 +17541,7 @@ }, "x-appwrite": { "method": "deleteProvider", - "weight": 373, + "weight": 374, "cookies": false, "type": "", "deprecated": false, @@ -17589,7 +17605,7 @@ }, "x-appwrite": { "method": "listProviderLogs", - "weight": 361, + "weight": 362, "cookies": false, "type": "", "deprecated": false, @@ -17665,7 +17681,7 @@ }, "x-appwrite": { "method": "listSubscriberLogs", - "weight": 382, + "weight": 383, "cookies": false, "type": "", "deprecated": false, @@ -17741,7 +17757,7 @@ }, "x-appwrite": { "method": "listTopics", - "weight": 375, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -17816,7 +17832,7 @@ }, "x-appwrite": { "method": "createTopic", - "weight": 374, + "weight": 375, "cookies": false, "type": "", "deprecated": false, @@ -17908,7 +17924,7 @@ }, "x-appwrite": { "method": "getTopic", - "weight": 377, + "weight": 378, "cookies": false, "type": "", "deprecated": false, @@ -17970,7 +17986,7 @@ }, "x-appwrite": { "method": "updateTopic", - "weight": 378, + "weight": 379, "cookies": false, "type": "", "deprecated": false, @@ -18053,7 +18069,7 @@ }, "x-appwrite": { "method": "deleteTopic", - "weight": 379, + "weight": 380, "cookies": false, "type": "", "deprecated": false, @@ -18117,7 +18133,7 @@ }, "x-appwrite": { "method": "listTopicLogs", - "weight": 376, + "weight": 377, "cookies": false, "type": "", "deprecated": false, @@ -18193,7 +18209,7 @@ }, "x-appwrite": { "method": "listSubscribers", - "weight": 381, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -18276,7 +18292,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 380, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -18368,7 +18384,7 @@ }, "x-appwrite": { "method": "getSubscriber", - "weight": 383, + "weight": 384, "cookies": false, "type": "", "deprecated": false, @@ -18435,7 +18451,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 384, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -18510,7 +18526,7 @@ }, "x-appwrite": { "method": "list", - "weight": 337, + "weight": 338, "cookies": false, "type": "", "deprecated": false, @@ -18585,7 +18601,7 @@ }, "x-appwrite": { "method": "createAppwriteMigration", - "weight": 332, + "weight": 333, "cookies": false, "type": "", "deprecated": false, @@ -18681,7 +18697,7 @@ }, "x-appwrite": { "method": "getAppwriteReport", - "weight": 339, + "weight": 340, "cookies": false, "type": "", "deprecated": false, @@ -18771,7 +18787,7 @@ }, "x-appwrite": { "method": "createFirebaseMigration", - "weight": 334, + "weight": 335, "cookies": false, "type": "", "deprecated": false, @@ -18853,7 +18869,7 @@ }, "x-appwrite": { "method": "deleteFirebaseAuth", - "weight": 345, + "weight": 346, "cookies": false, "type": "", "deprecated": false, @@ -18905,7 +18921,7 @@ }, "x-appwrite": { "method": "createFirebaseOAuthMigration", - "weight": 333, + "weight": 334, "cookies": false, "type": "", "deprecated": false, @@ -18987,7 +19003,7 @@ }, "x-appwrite": { "method": "listFirebaseProjects", - "weight": 344, + "weight": 345, "cookies": false, "type": "", "deprecated": false, @@ -19039,7 +19055,7 @@ }, "x-appwrite": { "method": "getFirebaseReport", - "weight": 340, + "weight": 341, "cookies": false, "type": "", "deprecated": false, @@ -19112,7 +19128,7 @@ }, "x-appwrite": { "method": "getFirebaseReportOAuth", - "weight": 341, + "weight": 342, "cookies": false, "type": "", "deprecated": false, @@ -19185,7 +19201,7 @@ }, "x-appwrite": { "method": "createNHostMigration", - "weight": 336, + "weight": 337, "cookies": false, "type": "", "deprecated": false, @@ -19308,7 +19324,7 @@ }, "x-appwrite": { "method": "getNHostReport", - "weight": 347, + "weight": 348, "cookies": false, "type": "", "deprecated": false, @@ -19430,7 +19446,7 @@ }, "x-appwrite": { "method": "createSupabaseMigration", - "weight": 335, + "weight": 336, "cookies": false, "type": "", "deprecated": false, @@ -19546,7 +19562,7 @@ }, "x-appwrite": { "method": "getSupabaseReport", - "weight": 346, + "weight": 347, "cookies": false, "type": "", "deprecated": false, @@ -19661,7 +19677,7 @@ }, "x-appwrite": { "method": "get", - "weight": 338, + "weight": 339, "cookies": false, "type": "", "deprecated": false, @@ -19721,7 +19737,7 @@ }, "x-appwrite": { "method": "retry", - "weight": 348, + "weight": 349, "cookies": false, "type": "", "deprecated": false, @@ -19776,7 +19792,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 349, + "weight": 350, "cookies": false, "type": "", "deprecated": false, @@ -19838,7 +19854,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 194, + "weight": 195, "cookies": false, "type": "", "deprecated": false, @@ -19924,7 +19940,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 196, + "weight": 197, "cookies": false, "type": "", "deprecated": false, @@ -19974,7 +19990,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 195, + "weight": 196, "cookies": false, "type": "", "deprecated": false, @@ -20053,7 +20069,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 197, + "weight": 198, "cookies": false, "type": "", "deprecated": false, @@ -20113,7 +20129,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 198, + "weight": 199, "cookies": false, "type": "", "deprecated": false, @@ -20192,7 +20208,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 199, + "weight": 200, "cookies": false, "type": "", "deprecated": false, @@ -20254,7 +20270,7 @@ }, "x-appwrite": { "method": "list", - "weight": 150, + "weight": 151, "cookies": false, "type": "", "deprecated": false, @@ -20327,7 +20343,7 @@ }, "x-appwrite": { "method": "create", - "weight": 149, + "weight": 150, "cookies": false, "type": "", "deprecated": false, @@ -20479,7 +20495,7 @@ }, "x-appwrite": { "method": "get", - "weight": 151, + "weight": 152, "cookies": false, "type": "", "deprecated": false, @@ -20539,7 +20555,7 @@ }, "x-appwrite": { "method": "update", - "weight": 152, + "weight": 153, "cookies": false, "type": "", "deprecated": false, @@ -20666,7 +20682,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 168, + "weight": 169, "cookies": false, "type": "", "deprecated": false, @@ -20728,7 +20744,7 @@ }, "x-appwrite": { "method": "updateApiStatus", - "weight": 156, + "weight": 157, "cookies": false, "type": "", "deprecated": false, @@ -20822,7 +20838,7 @@ }, "x-appwrite": { "method": "updateApiStatusAll", - "weight": 157, + "weight": 158, "cookies": false, "type": "", "deprecated": false, @@ -20902,7 +20918,7 @@ }, "x-appwrite": { "method": "updateAuthDuration", - "weight": 161, + "weight": 162, "cookies": false, "type": "", "deprecated": false, @@ -20982,7 +20998,7 @@ }, "x-appwrite": { "method": "updateAuthLimit", - "weight": 160, + "weight": 161, "cookies": false, "type": "", "deprecated": false, @@ -21062,7 +21078,7 @@ }, "x-appwrite": { "method": "updateAuthSessionsLimit", - "weight": 166, + "weight": 167, "cookies": false, "type": "", "deprecated": false, @@ -21142,7 +21158,7 @@ }, "x-appwrite": { "method": "updateMockNumbers", - "weight": 167, + "weight": 168, "cookies": false, "type": "", "deprecated": false, @@ -21225,7 +21241,7 @@ }, "x-appwrite": { "method": "updateAuthPasswordDictionary", - "weight": 164, + "weight": 165, "cookies": false, "type": "", "deprecated": false, @@ -21305,7 +21321,7 @@ }, "x-appwrite": { "method": "updateAuthPasswordHistory", - "weight": 163, + "weight": 164, "cookies": false, "type": "", "deprecated": false, @@ -21385,7 +21401,7 @@ }, "x-appwrite": { "method": "updatePersonalDataCheck", - "weight": 165, + "weight": 166, "cookies": false, "type": "", "deprecated": false, @@ -21465,7 +21481,7 @@ }, "x-appwrite": { "method": "updateSessionAlerts", - "weight": 159, + "weight": 160, "cookies": false, "type": "", "deprecated": false, @@ -21545,7 +21561,7 @@ }, "x-appwrite": { "method": "updateAuthStatus", - "weight": 162, + "weight": 163, "cookies": false, "type": "", "deprecated": false, @@ -21644,7 +21660,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 180, + "weight": 181, "cookies": false, "type": "", "deprecated": false, @@ -21733,7 +21749,7 @@ }, "x-appwrite": { "method": "listKeys", - "weight": 176, + "weight": 177, "cookies": false, "type": "", "deprecated": false, @@ -21793,7 +21809,7 @@ }, "x-appwrite": { "method": "createKey", - "weight": 175, + "weight": 176, "cookies": false, "type": "", "deprecated": false, @@ -21889,7 +21905,7 @@ }, "x-appwrite": { "method": "getKey", - "weight": 177, + "weight": 178, "cookies": false, "type": "", "deprecated": false, @@ -21957,7 +21973,7 @@ }, "x-appwrite": { "method": "updateKey", - "weight": 178, + "weight": 179, "cookies": false, "type": "", "deprecated": false, @@ -22054,7 +22070,7 @@ }, "x-appwrite": { "method": "deleteKey", - "weight": 179, + "weight": 180, "cookies": false, "type": "", "deprecated": false, @@ -22124,7 +22140,7 @@ }, "x-appwrite": { "method": "updateOAuth2", - "weight": 158, + "weight": 159, "cookies": false, "type": "", "deprecated": false, @@ -22265,7 +22281,7 @@ }, "x-appwrite": { "method": "listPlatforms", - "weight": 182, + "weight": 183, "cookies": false, "type": "", "deprecated": false, @@ -22325,7 +22341,7 @@ }, "x-appwrite": { "method": "createPlatform", - "weight": 181, + "weight": 182, "cookies": false, "type": "", "deprecated": false, @@ -22449,7 +22465,7 @@ }, "x-appwrite": { "method": "getPlatform", - "weight": 183, + "weight": 184, "cookies": false, "type": "", "deprecated": false, @@ -22517,7 +22533,7 @@ }, "x-appwrite": { "method": "updatePlatform", - "weight": 184, + "weight": 185, "cookies": false, "type": "", "deprecated": false, @@ -22616,7 +22632,7 @@ }, "x-appwrite": { "method": "deletePlatform", - "weight": 185, + "weight": 186, "cookies": false, "type": "", "deprecated": false, @@ -22686,7 +22702,7 @@ }, "x-appwrite": { "method": "updateServiceStatus", - "weight": 154, + "weight": 155, "cookies": false, "type": "", "deprecated": false, @@ -22788,7 +22804,7 @@ }, "x-appwrite": { "method": "updateServiceStatusAll", - "weight": 155, + "weight": 156, "cookies": false, "type": "", "deprecated": false, @@ -22868,7 +22884,7 @@ }, "x-appwrite": { "method": "updateSmtp", - "weight": 186, + "weight": 187, "cookies": false, "type": "", "deprecated": false, @@ -22997,7 +23013,7 @@ }, "x-appwrite": { "method": "createSmtpTest", - "weight": 187, + "weight": 188, "cookies": false, "type": "", "deprecated": false, @@ -23137,7 +23153,7 @@ }, "x-appwrite": { "method": "updateTeam", - "weight": 153, + "weight": 154, "cookies": false, "type": "", "deprecated": false, @@ -23217,7 +23233,7 @@ }, "x-appwrite": { "method": "getEmailTemplate", - "weight": 189, + "weight": 190, "cookies": false, "type": "", "deprecated": false, @@ -23439,7 +23455,7 @@ }, "x-appwrite": { "method": "updateEmailTemplate", - "weight": 191, + "weight": 192, "cookies": false, "type": "", "deprecated": false, @@ -23704,7 +23720,7 @@ }, "x-appwrite": { "method": "deleteEmailTemplate", - "weight": 193, + "weight": 194, "cookies": false, "type": "", "deprecated": false, @@ -23928,7 +23944,7 @@ }, "x-appwrite": { "method": "getSmsTemplate", - "weight": 188, + "weight": 189, "cookies": false, "type": "", "deprecated": false, @@ -24147,7 +24163,7 @@ }, "x-appwrite": { "method": "updateSmsTemplate", - "weight": 190, + "weight": 191, "cookies": false, "type": "", "deprecated": false, @@ -24384,7 +24400,7 @@ }, "x-appwrite": { "method": "deleteSmsTemplate", - "weight": 192, + "weight": 193, "cookies": false, "type": "", "deprecated": false, @@ -24605,7 +24621,7 @@ }, "x-appwrite": { "method": "listWebhooks", - "weight": 170, + "weight": 171, "cookies": false, "type": "", "deprecated": false, @@ -24665,7 +24681,7 @@ }, "x-appwrite": { "method": "createWebhook", - "weight": 169, + "weight": 170, "cookies": false, "type": "", "deprecated": false, @@ -24787,7 +24803,7 @@ }, "x-appwrite": { "method": "getWebhook", - "weight": 171, + "weight": 172, "cookies": false, "type": "", "deprecated": false, @@ -24855,7 +24871,7 @@ }, "x-appwrite": { "method": "updateWebhook", - "weight": 172, + "weight": 173, "cookies": false, "type": "", "deprecated": false, @@ -24978,7 +24994,7 @@ }, "x-appwrite": { "method": "deleteWebhook", - "weight": 174, + "weight": 175, "cookies": false, "type": "", "deprecated": false, @@ -25048,7 +25064,7 @@ }, "x-appwrite": { "method": "updateWebhookSignature", - "weight": 173, + "weight": 174, "cookies": false, "type": "", "deprecated": false, @@ -25118,7 +25134,7 @@ }, "x-appwrite": { "method": "listRules", - "weight": 315, + "weight": 316, "cookies": false, "type": "", "deprecated": false, @@ -25191,7 +25207,7 @@ }, "x-appwrite": { "method": "createRule", - "weight": 314, + "weight": 315, "cookies": false, "type": "", "deprecated": false, @@ -25282,7 +25298,7 @@ }, "x-appwrite": { "method": "getRule", - "weight": 316, + "weight": 317, "cookies": false, "type": "", "deprecated": false, @@ -25337,7 +25353,7 @@ }, "x-appwrite": { "method": "deleteRule", - "weight": 317, + "weight": 318, "cookies": false, "type": "", "deprecated": false, @@ -25399,7 +25415,7 @@ }, "x-appwrite": { "method": "updateRuleVerification", - "weight": 318, + "weight": 319, "cookies": false, "type": "", "deprecated": false, @@ -25461,7 +25477,7 @@ }, "x-appwrite": { "method": "listBuckets", - "weight": 201, + "weight": 202, "cookies": false, "type": "", "deprecated": false, @@ -25535,7 +25551,7 @@ }, "x-appwrite": { "method": "createBucket", - "weight": 200, + "weight": 201, "cookies": false, "type": "", "deprecated": false, @@ -25676,7 +25692,7 @@ }, "x-appwrite": { "method": "getBucket", - "weight": 202, + "weight": 203, "cookies": false, "type": "", "deprecated": false, @@ -25737,7 +25753,7 @@ }, "x-appwrite": { "method": "updateBucket", - "weight": 203, + "weight": 204, "cookies": false, "type": "", "deprecated": false, @@ -25872,7 +25888,7 @@ }, "x-appwrite": { "method": "deleteBucket", - "weight": 204, + "weight": 205, "cookies": false, "type": "", "deprecated": false, @@ -25935,7 +25951,7 @@ }, "x-appwrite": { "method": "listFiles", - "weight": 206, + "weight": 207, "cookies": false, "type": "", "deprecated": false, @@ -26020,7 +26036,7 @@ }, "x-appwrite": { "method": "createFile", - "weight": 205, + "weight": 206, "cookies": false, "type": "upload", "deprecated": false, @@ -26114,7 +26130,7 @@ }, "x-appwrite": { "method": "getFile", - "weight": 207, + "weight": 208, "cookies": false, "type": "", "deprecated": false, @@ -26186,7 +26202,7 @@ }, "x-appwrite": { "method": "updateFile", - "weight": 212, + "weight": 213, "cookies": false, "type": "", "deprecated": false, @@ -26277,7 +26293,7 @@ }, "x-appwrite": { "method": "deleteFile", - "weight": 213, + "weight": 214, "cookies": false, "type": "", "deprecated": false, @@ -26351,7 +26367,7 @@ }, "x-appwrite": { "method": "getFileDownload", - "weight": 209, + "weight": 210, "cookies": false, "type": "location", "deprecated": false, @@ -26425,7 +26441,7 @@ }, "x-appwrite": { "method": "getFilePreview", - "weight": 208, + "weight": 209, "cookies": false, "type": "location", "deprecated": false, @@ -26626,7 +26642,7 @@ }, "x-appwrite": { "method": "getFileView", - "weight": 210, + "weight": 211, "cookies": false, "type": "location", "deprecated": false, @@ -26700,7 +26716,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 214, + "weight": 215, "cookies": false, "type": "", "deprecated": false, @@ -26774,7 +26790,7 @@ }, "x-appwrite": { "method": "getBucketUsage", - "weight": 215, + "weight": 216, "cookies": false, "type": "", "deprecated": false, @@ -26856,7 +26872,7 @@ }, "x-appwrite": { "method": "list", - "weight": 217, + "weight": 218, "cookies": false, "type": "", "deprecated": false, @@ -26933,7 +26949,7 @@ }, "x-appwrite": { "method": "create", - "weight": 216, + "weight": 217, "cookies": false, "type": "", "deprecated": false, @@ -27027,7 +27043,7 @@ }, "x-appwrite": { "method": "get", - "weight": 218, + "weight": 219, "cookies": false, "type": "", "deprecated": false, @@ -27091,7 +27107,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 220, + "weight": 221, "cookies": false, "type": "", "deprecated": false, @@ -27168,7 +27184,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 222, + "weight": 223, "cookies": false, "type": "", "deprecated": false, @@ -27234,7 +27250,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 229, + "weight": 230, "cookies": false, "type": "", "deprecated": false, @@ -27308,7 +27324,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 224, + "weight": 225, "cookies": false, "type": "", "deprecated": false, @@ -27393,7 +27409,7 @@ }, "x-appwrite": { "method": "createMembership", - "weight": 223, + "weight": 224, "cookies": false, "type": "", "deprecated": false, @@ -27510,7 +27526,7 @@ }, "x-appwrite": { "method": "getMembership", - "weight": 225, + "weight": 226, "cookies": false, "type": "", "deprecated": false, @@ -27582,7 +27598,7 @@ }, "x-appwrite": { "method": "updateMembership", - "weight": 226, + "weight": 227, "cookies": false, "type": "", "deprecated": false, @@ -27670,7 +27686,7 @@ }, "x-appwrite": { "method": "deleteMembership", - "weight": 228, + "weight": 229, "cookies": false, "type": "", "deprecated": false, @@ -27744,7 +27760,7 @@ }, "x-appwrite": { "method": "updateMembershipStatus", - "weight": 227, + "weight": 228, "cookies": false, "type": "", "deprecated": false, @@ -27841,7 +27857,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 219, + "weight": 220, "cookies": false, "type": "", "deprecated": false, @@ -27903,7 +27919,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 221, + "weight": 222, "cookies": false, "type": "", "deprecated": false, @@ -27985,7 +28001,7 @@ }, "x-appwrite": { "method": "list", - "weight": 239, + "weight": 240, "cookies": false, "type": "", "deprecated": false, @@ -28059,7 +28075,7 @@ }, "x-appwrite": { "method": "create", - "weight": 230, + "weight": 231, "cookies": false, "type": "", "deprecated": false, @@ -28156,7 +28172,7 @@ }, "x-appwrite": { "method": "createArgon2User", - "weight": 233, + "weight": 234, "cookies": false, "type": "", "deprecated": false, @@ -28249,7 +28265,7 @@ }, "x-appwrite": { "method": "createBcryptUser", - "weight": 231, + "weight": 232, "cookies": false, "type": "", "deprecated": false, @@ -28342,7 +28358,7 @@ }, "x-appwrite": { "method": "listIdentities", - "weight": 247, + "weight": 248, "cookies": false, "type": "", "deprecated": false, @@ -28413,7 +28429,7 @@ }, "x-appwrite": { "method": "deleteIdentity", - "weight": 270, + "weight": 271, "cookies": false, "type": "", "deprecated": false, @@ -28476,7 +28492,7 @@ }, "x-appwrite": { "method": "createMD5User", - "weight": 232, + "weight": 233, "cookies": false, "type": "", "deprecated": false, @@ -28569,7 +28585,7 @@ }, "x-appwrite": { "method": "createPHPassUser", - "weight": 235, + "weight": 236, "cookies": false, "type": "", "deprecated": false, @@ -28662,7 +28678,7 @@ }, "x-appwrite": { "method": "createScryptUser", - "weight": 236, + "weight": 237, "cookies": false, "type": "", "deprecated": false, @@ -28790,7 +28806,7 @@ }, "x-appwrite": { "method": "createScryptModifiedUser", - "weight": 237, + "weight": 238, "cookies": false, "type": "", "deprecated": false, @@ -28904,7 +28920,7 @@ }, "x-appwrite": { "method": "createSHAUser", - "weight": 234, + "weight": 235, "cookies": false, "type": "", "deprecated": false, @@ -29018,7 +29034,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 272, + "weight": 273, "cookies": false, "type": "", "deprecated": false, @@ -29092,7 +29108,7 @@ }, "x-appwrite": { "method": "get", - "weight": 240, + "weight": 241, "cookies": false, "type": "", "deprecated": false, @@ -29148,7 +29164,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 268, + "weight": 269, "cookies": false, "type": "", "deprecated": false, @@ -29211,7 +29227,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 253, + "weight": 254, "cookies": false, "type": "", "deprecated": false, @@ -29292,7 +29308,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 271, + "weight": 272, "cookies": false, "type": "", "deprecated": false, @@ -29376,7 +29392,7 @@ }, "x-appwrite": { "method": "updateLabels", - "weight": 249, + "weight": 250, "cookies": false, "type": "", "deprecated": false, @@ -29460,7 +29476,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 245, + "weight": 246, "cookies": false, "type": "", "deprecated": false, @@ -29535,7 +29551,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 244, + "weight": 245, "cookies": false, "type": "", "deprecated": false, @@ -29598,7 +29614,7 @@ }, "x-appwrite": { "method": "updateMfa", - "weight": 258, + "weight": 259, "cookies": false, "type": "", "deprecated": false, @@ -29679,7 +29695,7 @@ }, "x-appwrite": { "method": "deleteMfaAuthenticator", - "weight": 263, + "weight": 264, "cookies": false, "type": "", "deprecated": false, @@ -29755,7 +29771,7 @@ }, "x-appwrite": { "method": "listMfaFactors", - "weight": 259, + "weight": 260, "cookies": false, "type": "", "deprecated": false, @@ -29818,7 +29834,7 @@ }, "x-appwrite": { "method": "getMfaRecoveryCodes", - "weight": 260, + "weight": 261, "cookies": false, "type": "", "deprecated": false, @@ -29879,7 +29895,7 @@ }, "x-appwrite": { "method": "updateMfaRecoveryCodes", - "weight": 262, + "weight": 263, "cookies": false, "type": "", "deprecated": false, @@ -29940,7 +29956,7 @@ }, "x-appwrite": { "method": "createMfaRecoveryCodes", - "weight": 261, + "weight": 262, "cookies": false, "type": "", "deprecated": false, @@ -30003,7 +30019,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 251, + "weight": 252, "cookies": false, "type": "", "deprecated": false, @@ -30084,7 +30100,7 @@ }, "x-appwrite": { "method": "updatePassword", - "weight": 252, + "weight": 253, "cookies": false, "type": "", "deprecated": false, @@ -30165,7 +30181,7 @@ }, "x-appwrite": { "method": "updatePhone", - "weight": 254, + "weight": 255, "cookies": false, "type": "", "deprecated": false, @@ -30246,7 +30262,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 241, + "weight": 242, "cookies": false, "type": "", "deprecated": false, @@ -30307,7 +30323,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 256, + "weight": 257, "cookies": false, "type": "", "deprecated": false, @@ -30388,7 +30404,7 @@ }, "x-appwrite": { "method": "listSessions", - "weight": 243, + "weight": 244, "cookies": false, "type": "", "deprecated": false, @@ -30449,7 +30465,7 @@ }, "x-appwrite": { "method": "createSession", - "weight": 264, + "weight": 265, "cookies": false, "type": "", "deprecated": false, @@ -30505,7 +30521,7 @@ }, "x-appwrite": { "method": "deleteSessions", - "weight": 267, + "weight": 268, "cookies": false, "type": "", "deprecated": false, @@ -30563,7 +30579,7 @@ }, "x-appwrite": { "method": "deleteSession", - "weight": 266, + "weight": 267, "cookies": false, "type": "", "deprecated": false, @@ -30634,7 +30650,7 @@ }, "x-appwrite": { "method": "updateStatus", - "weight": 248, + "weight": 249, "cookies": false, "type": "", "deprecated": false, @@ -30715,7 +30731,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 246, + "weight": 247, "cookies": false, "type": "", "deprecated": false, @@ -30789,7 +30805,7 @@ }, "x-appwrite": { "method": "createTarget", - "weight": 238, + "weight": 239, "cookies": false, "type": "", "deprecated": false, @@ -30904,7 +30920,7 @@ }, "x-appwrite": { "method": "getTarget", - "weight": 242, + "weight": 243, "cookies": false, "type": "", "deprecated": false, @@ -30974,7 +30990,7 @@ }, "x-appwrite": { "method": "updateTarget", - "weight": 257, + "weight": 258, "cookies": false, "type": "", "deprecated": false, @@ -31068,7 +31084,7 @@ }, "x-appwrite": { "method": "deleteTarget", - "weight": 269, + "weight": 270, "cookies": false, "type": "", "deprecated": false, @@ -31140,7 +31156,7 @@ }, "x-appwrite": { "method": "createToken", - "weight": 265, + "weight": 266, "cookies": false, "type": "", "deprecated": false, @@ -31224,7 +31240,7 @@ }, "x-appwrite": { "method": "updateEmailVerification", - "weight": 255, + "weight": 256, "cookies": false, "type": "", "deprecated": false, @@ -31305,7 +31321,7 @@ }, "x-appwrite": { "method": "updatePhoneVerification", - "weight": 250, + "weight": 251, "cookies": false, "type": "", "deprecated": false, @@ -31386,7 +31402,7 @@ }, "x-appwrite": { "method": "listRepositories", - "weight": 277, + "weight": 278, "cookies": false, "type": "", "deprecated": false, @@ -31455,7 +31471,7 @@ }, "x-appwrite": { "method": "createRepository", - "weight": 278, + "weight": 279, "cookies": false, "type": "", "deprecated": false, @@ -31542,7 +31558,7 @@ }, "x-appwrite": { "method": "getRepository", - "weight": 279, + "weight": 280, "cookies": false, "type": "", "deprecated": false, @@ -31612,7 +31628,7 @@ }, "x-appwrite": { "method": "listRepositoryBranches", - "weight": 280, + "weight": 281, "cookies": false, "type": "", "deprecated": false, @@ -31682,7 +31698,7 @@ }, "x-appwrite": { "method": "getRepositoryContents", - "weight": 275, + "weight": 276, "cookies": false, "type": "", "deprecated": false, @@ -31761,7 +31777,7 @@ }, "x-appwrite": { "method": "createRepositoryDetection", - "weight": 276, + "weight": 277, "cookies": false, "type": "", "deprecated": false, @@ -31841,7 +31857,7 @@ }, "x-appwrite": { "method": "updateExternalDeployments", - "weight": 285, + "weight": 286, "cookies": false, "type": "", "deprecated": false, @@ -31929,7 +31945,7 @@ }, "x-appwrite": { "method": "listInstallations", - "weight": 282, + "weight": 283, "cookies": false, "type": "", "deprecated": false, @@ -32004,7 +32020,7 @@ }, "x-appwrite": { "method": "getInstallation", - "weight": 283, + "weight": 284, "cookies": false, "type": "", "deprecated": false, @@ -32059,7 +32075,7 @@ }, "x-appwrite": { "method": "deleteInstallation", - "weight": 284, + "weight": 285, "cookies": false, "type": "", "deprecated": false, @@ -36232,7 +36248,7 @@ "format": "int32" }, "responseBody": { - "type": "payload", + "type": "string", "description": "HTTP response body. This will return empty unless execution is created as synchronous.", "x-example": "" }, @@ -36563,6 +36579,17 @@ "description": "SMTP server secure protocol", "x-example": "tls" }, + "pingCount": { + "type": "integer", + "description": "Number of times the ping was received for this project.", + "x-example": 1, + "format": "int32" + }, + "pingedAt": { + "type": "string", + "description": "Last ping datetime in ISO 8601 format.", + "x-example": "2020-10-15T06:38:00.000+00:00" + }, "authEmailPassword": { "type": "boolean", "description": "Email\/Password auth method status", @@ -36690,6 +36717,8 @@ "smtpUsername", "smtpPassword", "smtpSecure", + "pingCount", + "pingedAt", "authEmailPassword", "authUsersAuthMagicURL", "authEmailOtp", @@ -38983,7 +39012,7 @@ }, "$createdAt": { "type": "string", - "description": "Variable creation date in ISO 8601 format.", + "description": "Migration creation date in ISO 8601 format.", "x-example": "2020-10-15T06:38:00.000+00:00" }, "$updatedAt": { diff --git a/app/config/specs/swagger2-1.6.x-server.json b/app/config/specs/swagger2-1.6.x-server.json index 1501681fcf..37018916fa 100644 --- a/app/config/specs/swagger2-1.6.x-server.json +++ b/app/config/specs/swagger2-1.6.x-server.json @@ -102,7 +102,7 @@ }, "x-appwrite": { "method": "get", - "weight": 8, + "weight": 9, "cookies": false, "type": "", "deprecated": false, @@ -156,7 +156,7 @@ }, "x-appwrite": { "method": "create", - "weight": 7, + "weight": 8, "cookies": false, "type": "", "deprecated": false, @@ -249,7 +249,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 33, + "weight": 34, "cookies": false, "type": "", "deprecated": false, @@ -332,7 +332,7 @@ }, "x-appwrite": { "method": "listIdentities", - "weight": 56, + "weight": 57, "cookies": false, "type": "", "deprecated": false, @@ -397,7 +397,7 @@ }, "x-appwrite": { "method": "deleteIdentity", - "weight": 57, + "weight": 58, "cookies": false, "type": "", "deprecated": false, @@ -463,7 +463,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 28, + "weight": 29, "cookies": false, "type": "", "deprecated": false, @@ -516,7 +516,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 30, + "weight": 31, "cookies": false, "type": "", "deprecated": false, @@ -586,7 +586,7 @@ }, "x-appwrite": { "method": "updateMFA", - "weight": 43, + "weight": 44, "cookies": false, "type": "", "deprecated": false, @@ -662,7 +662,7 @@ }, "x-appwrite": { "method": "createMfaAuthenticator", - "weight": 45, + "weight": 46, "cookies": false, "type": "", "deprecated": false, @@ -731,7 +731,7 @@ }, "x-appwrite": { "method": "updateMfaAuthenticator", - "weight": 46, + "weight": 47, "cookies": false, "type": "", "deprecated": false, @@ -813,7 +813,7 @@ }, "x-appwrite": { "method": "deleteMfaAuthenticator", - "weight": 50, + "weight": 51, "cookies": false, "type": "", "deprecated": false, @@ -884,7 +884,7 @@ }, "x-appwrite": { "method": "createMfaChallenge", - "weight": 51, + "weight": 52, "cookies": false, "type": "", "deprecated": false, @@ -958,7 +958,7 @@ }, "x-appwrite": { "method": "updateMfaChallenge", - "weight": 52, + "weight": 53, "cookies": false, "type": "", "deprecated": false, @@ -1041,7 +1041,7 @@ }, "x-appwrite": { "method": "listMfaFactors", - "weight": 44, + "weight": 45, "cookies": false, "type": "", "deprecated": false, @@ -1097,7 +1097,7 @@ }, "x-appwrite": { "method": "getMfaRecoveryCodes", - "weight": 49, + "weight": 50, "cookies": false, "type": "", "deprecated": false, @@ -1151,7 +1151,7 @@ }, "x-appwrite": { "method": "createMfaRecoveryCodes", - "weight": 47, + "weight": 48, "cookies": false, "type": "", "deprecated": false, @@ -1205,7 +1205,7 @@ }, "x-appwrite": { "method": "updateMfaRecoveryCodes", - "weight": 48, + "weight": 49, "cookies": false, "type": "", "deprecated": false, @@ -1261,7 +1261,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 31, + "weight": 32, "cookies": false, "type": "", "deprecated": false, @@ -1337,7 +1337,7 @@ }, "x-appwrite": { "method": "updatePassword", - "weight": 32, + "weight": 33, "cookies": false, "type": "", "deprecated": false, @@ -1419,7 +1419,7 @@ }, "x-appwrite": { "method": "updatePhone", - "weight": 34, + "weight": 35, "cookies": false, "type": "", "deprecated": false, @@ -1502,7 +1502,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 29, + "weight": 30, "cookies": false, "type": "", "deprecated": false, @@ -1556,7 +1556,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 35, + "weight": 36, "cookies": false, "type": "", "deprecated": false, @@ -1632,7 +1632,7 @@ }, "x-appwrite": { "method": "createRecovery", - "weight": 37, + "weight": 38, "cookies": false, "type": "", "deprecated": false, @@ -1716,7 +1716,7 @@ }, "x-appwrite": { "method": "updateRecovery", - "weight": 38, + "weight": 39, "cookies": false, "type": "", "deprecated": false, @@ -1806,7 +1806,7 @@ }, "x-appwrite": { "method": "listSessions", - "weight": 10, + "weight": 11, "cookies": false, "type": "", "deprecated": false, @@ -1855,7 +1855,7 @@ }, "x-appwrite": { "method": "deleteSessions", - "weight": 11, + "weight": 12, "cookies": false, "type": "", "deprecated": false, @@ -1911,7 +1911,7 @@ }, "x-appwrite": { "method": "createAnonymousSession", - "weight": 16, + "weight": 17, "cookies": false, "type": "", "deprecated": false, @@ -1964,7 +1964,7 @@ }, "x-appwrite": { "method": "createEmailPasswordSession", - "weight": 15, + "weight": 16, "cookies": false, "type": "", "deprecated": false, @@ -2044,7 +2044,7 @@ }, "x-appwrite": { "method": "updateMagicURLSession", - "weight": 25, + "weight": 26, "cookies": false, "type": "", "deprecated": true, @@ -2124,7 +2124,7 @@ }, "x-appwrite": { "method": "updatePhoneSession", - "weight": 26, + "weight": 27, "cookies": false, "type": "", "deprecated": true, @@ -2204,7 +2204,7 @@ }, "x-appwrite": { "method": "createSession", - "weight": 17, + "weight": 18, "cookies": false, "type": "", "deprecated": false, @@ -2284,7 +2284,7 @@ }, "x-appwrite": { "method": "getSession", - "weight": 12, + "weight": 13, "cookies": false, "type": "", "deprecated": false, @@ -2348,7 +2348,7 @@ }, "x-appwrite": { "method": "updateSession", - "weight": 14, + "weight": 15, "cookies": false, "type": "", "deprecated": false, @@ -2407,7 +2407,7 @@ }, "x-appwrite": { "method": "deleteSession", - "weight": 13, + "weight": 14, "cookies": false, "type": "", "deprecated": false, @@ -2473,7 +2473,7 @@ }, "x-appwrite": { "method": "updateStatus", - "weight": 36, + "weight": 37, "cookies": false, "type": "", "deprecated": false, @@ -2529,7 +2529,7 @@ }, "x-appwrite": { "method": "createEmailToken", - "weight": 24, + "weight": 25, "cookies": false, "type": "", "deprecated": false, @@ -2615,7 +2615,7 @@ }, "x-appwrite": { "method": "createMagicURLToken", - "weight": 23, + "weight": 24, "cookies": false, "type": "", "deprecated": false, @@ -2707,7 +2707,7 @@ }, "x-appwrite": { "method": "createOAuth2Token", - "weight": 22, + "weight": 23, "cookies": false, "type": "webAuth", "deprecated": false, @@ -2845,7 +2845,7 @@ }, "x-appwrite": { "method": "createPhoneToken", - "weight": 27, + "weight": 28, "cookies": false, "type": "", "deprecated": false, @@ -2928,7 +2928,7 @@ }, "x-appwrite": { "method": "createVerification", - "weight": 39, + "weight": 40, "cookies": false, "type": "", "deprecated": false, @@ -3002,7 +3002,7 @@ }, "x-appwrite": { "method": "updateVerification", - "weight": 40, + "weight": 41, "cookies": false, "type": "", "deprecated": false, @@ -3085,7 +3085,7 @@ }, "x-appwrite": { "method": "createPhoneVerification", - "weight": 41, + "weight": 42, "cookies": false, "type": "", "deprecated": false, @@ -3142,7 +3142,7 @@ }, "x-appwrite": { "method": "updatePhoneVerification", - "weight": 42, + "weight": 43, "cookies": false, "type": "", "deprecated": false, @@ -3225,7 +3225,7 @@ }, "x-appwrite": { "method": "getBrowser", - "weight": 59, + "weight": 60, "cookies": false, "type": "location", "deprecated": false, @@ -3356,7 +3356,7 @@ }, "x-appwrite": { "method": "getCreditCard", - "weight": 58, + "weight": 59, "cookies": false, "type": "location", "deprecated": false, @@ -3491,7 +3491,7 @@ }, "x-appwrite": { "method": "getFavicon", - "weight": 62, + "weight": 63, "cookies": false, "type": "location", "deprecated": false, @@ -3560,7 +3560,7 @@ }, "x-appwrite": { "method": "getFlag", - "weight": 60, + "weight": 61, "cookies": false, "type": "location", "deprecated": false, @@ -4053,7 +4053,7 @@ }, "x-appwrite": { "method": "getImage", - "weight": 61, + "weight": 62, "cookies": false, "type": "location", "deprecated": false, @@ -4142,7 +4142,7 @@ }, "x-appwrite": { "method": "getInitials", - "weight": 64, + "weight": 65, "cookies": false, "type": "location", "deprecated": false, @@ -4239,7 +4239,7 @@ }, "x-appwrite": { "method": "getQR", - "weight": 63, + "weight": 64, "cookies": false, "type": "location", "deprecated": false, @@ -4336,7 +4336,7 @@ }, "x-appwrite": { "method": "list", - "weight": 69, + "weight": 70, "cookies": false, "type": "", "deprecated": false, @@ -4411,7 +4411,7 @@ }, "x-appwrite": { "method": "create", - "weight": 68, + "weight": 69, "cookies": false, "type": "", "deprecated": false, @@ -4498,7 +4498,7 @@ }, "x-appwrite": { "method": "get", - "weight": 70, + "weight": 71, "cookies": false, "type": "", "deprecated": false, @@ -4560,7 +4560,7 @@ }, "x-appwrite": { "method": "update", - "weight": 72, + "weight": 73, "cookies": false, "type": "", "deprecated": false, @@ -4641,7 +4641,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 73, + "weight": 74, "cookies": false, "type": "", "deprecated": false, @@ -4705,7 +4705,7 @@ }, "x-appwrite": { "method": "listCollections", - "weight": 75, + "weight": 76, "cookies": false, "type": "", "deprecated": false, @@ -4788,7 +4788,7 @@ }, "x-appwrite": { "method": "createCollection", - "weight": 74, + "weight": 75, "cookies": false, "type": "", "deprecated": false, @@ -4898,7 +4898,7 @@ }, "x-appwrite": { "method": "getCollection", - "weight": 76, + "weight": 77, "cookies": false, "type": "", "deprecated": false, @@ -4968,7 +4968,7 @@ }, "x-appwrite": { "method": "updateCollection", - "weight": 78, + "weight": 79, "cookies": false, "type": "", "deprecated": false, @@ -5072,7 +5072,7 @@ }, "x-appwrite": { "method": "deleteCollection", - "weight": 79, + "weight": 80, "cookies": false, "type": "", "deprecated": false, @@ -5144,7 +5144,7 @@ }, "x-appwrite": { "method": "listAttributes", - "weight": 90, + "weight": 91, "cookies": false, "type": "", "deprecated": false, @@ -5228,7 +5228,7 @@ }, "x-appwrite": { "method": "createBooleanAttribute", - "weight": 87, + "weight": 88, "cookies": false, "type": "", "deprecated": false, @@ -5335,7 +5335,7 @@ }, "x-appwrite": { "method": "updateBooleanAttribute", - "weight": 99, + "weight": 100, "cookies": false, "type": "", "deprecated": false, @@ -5446,7 +5446,7 @@ }, "x-appwrite": { "method": "createDatetimeAttribute", - "weight": 88, + "weight": 89, "cookies": false, "type": "", "deprecated": false, @@ -5553,7 +5553,7 @@ }, "x-appwrite": { "method": "updateDatetimeAttribute", - "weight": 100, + "weight": 101, "cookies": false, "type": "", "deprecated": false, @@ -5664,7 +5664,7 @@ }, "x-appwrite": { "method": "createEmailAttribute", - "weight": 81, + "weight": 82, "cookies": false, "type": "", "deprecated": false, @@ -5771,7 +5771,7 @@ }, "x-appwrite": { "method": "updateEmailAttribute", - "weight": 93, + "weight": 94, "cookies": false, "type": "", "deprecated": false, @@ -5882,7 +5882,7 @@ }, "x-appwrite": { "method": "createEnumAttribute", - "weight": 82, + "weight": 83, "cookies": false, "type": "", "deprecated": false, @@ -5999,7 +5999,7 @@ }, "x-appwrite": { "method": "updateEnumAttribute", - "weight": 94, + "weight": 95, "cookies": false, "type": "", "deprecated": false, @@ -6120,7 +6120,7 @@ }, "x-appwrite": { "method": "createFloatAttribute", - "weight": 86, + "weight": 87, "cookies": false, "type": "", "deprecated": false, @@ -6239,7 +6239,7 @@ }, "x-appwrite": { "method": "updateFloatAttribute", - "weight": 98, + "weight": 99, "cookies": false, "type": "", "deprecated": false, @@ -6364,7 +6364,7 @@ }, "x-appwrite": { "method": "createIntegerAttribute", - "weight": 85, + "weight": 86, "cookies": false, "type": "", "deprecated": false, @@ -6483,7 +6483,7 @@ }, "x-appwrite": { "method": "updateIntegerAttribute", - "weight": 97, + "weight": 98, "cookies": false, "type": "", "deprecated": false, @@ -6608,7 +6608,7 @@ }, "x-appwrite": { "method": "createIpAttribute", - "weight": 83, + "weight": 84, "cookies": false, "type": "", "deprecated": false, @@ -6715,7 +6715,7 @@ }, "x-appwrite": { "method": "updateIpAttribute", - "weight": 95, + "weight": 96, "cookies": false, "type": "", "deprecated": false, @@ -6826,7 +6826,7 @@ }, "x-appwrite": { "method": "createRelationshipAttribute", - "weight": 89, + "weight": 90, "cookies": false, "type": "", "deprecated": false, @@ -6962,7 +6962,7 @@ }, "x-appwrite": { "method": "createStringAttribute", - "weight": 80, + "weight": 81, "cookies": false, "type": "", "deprecated": false, @@ -7082,7 +7082,7 @@ }, "x-appwrite": { "method": "updateStringAttribute", - "weight": 92, + "weight": 93, "cookies": false, "type": "", "deprecated": false, @@ -7199,7 +7199,7 @@ }, "x-appwrite": { "method": "createUrlAttribute", - "weight": 84, + "weight": 85, "cookies": false, "type": "", "deprecated": false, @@ -7306,7 +7306,7 @@ }, "x-appwrite": { "method": "updateUrlAttribute", - "weight": 96, + "weight": 97, "cookies": false, "type": "", "deprecated": false, @@ -7448,7 +7448,7 @@ }, "x-appwrite": { "method": "getAttribute", - "weight": 91, + "weight": 92, "cookies": false, "type": "", "deprecated": false, @@ -7520,7 +7520,7 @@ }, "x-appwrite": { "method": "deleteAttribute", - "weight": 102, + "weight": 103, "cookies": false, "type": "", "deprecated": false, @@ -7597,7 +7597,7 @@ }, "x-appwrite": { "method": "updateRelationshipAttribute", - "weight": 101, + "weight": 102, "cookies": false, "type": "", "deprecated": false, @@ -7704,7 +7704,7 @@ }, "x-appwrite": { "method": "listDocuments", - "weight": 108, + "weight": 109, "cookies": false, "type": "", "deprecated": false, @@ -7790,7 +7790,7 @@ }, "x-appwrite": { "method": "createDocument", - "weight": 107, + "weight": 108, "cookies": false, "type": "", "deprecated": false, @@ -7900,7 +7900,7 @@ }, "x-appwrite": { "method": "getDocument", - "weight": 109, + "weight": 110, "cookies": false, "type": "", "deprecated": false, @@ -7994,7 +7994,7 @@ }, "x-appwrite": { "method": "updateDocument", - "weight": 111, + "weight": 112, "cookies": false, "type": "", "deprecated": false, @@ -8095,7 +8095,7 @@ }, "x-appwrite": { "method": "deleteDocument", - "weight": 112, + "weight": 113, "cookies": false, "type": "", "deprecated": false, @@ -8179,7 +8179,7 @@ }, "x-appwrite": { "method": "listIndexes", - "weight": 104, + "weight": 105, "cookies": false, "type": "", "deprecated": false, @@ -8261,7 +8261,7 @@ }, "x-appwrite": { "method": "createIndex", - "weight": 103, + "weight": 104, "cookies": false, "type": "", "deprecated": false, @@ -8384,7 +8384,7 @@ }, "x-appwrite": { "method": "getIndex", - "weight": 105, + "weight": 106, "cookies": false, "type": "", "deprecated": false, @@ -8456,7 +8456,7 @@ }, "x-appwrite": { "method": "deleteIndex", - "weight": 106, + "weight": 107, "cookies": false, "type": "", "deprecated": false, @@ -8535,7 +8535,7 @@ }, "x-appwrite": { "method": "list", - "weight": 287, + "weight": 288, "cookies": false, "type": "", "deprecated": false, @@ -8610,7 +8610,7 @@ }, "x-appwrite": { "method": "create", - "weight": 286, + "weight": 287, "cookies": false, "type": "", "deprecated": false, @@ -8669,6 +8669,7 @@ "node-19.0", "node-20.0", "node-21.0", + "node-22", "php-8.0", "php-8.1", "php-8.2", @@ -8687,6 +8688,8 @@ "deno-1.24", "deno-1.35", "deno-1.40", + "deno-1.46", + "deno-2.0", "dart-2.15", "dart-2.16", "dart-2.17", @@ -8694,23 +8697,28 @@ "dart-3.0", "dart-3.1", "dart-3.3", - "dotnet-3.1", + "dart-3.5", "dotnet-6.0", "dotnet-7.0", + "dotnet-8.0", "java-8.0", "java-11.0", "java-17.0", "java-18.0", "java-21.0", + "java-22", "swift-5.5", "swift-5.8", "swift-5.9", + "swift-5.10", "kotlin-1.6", "kotlin-1.8", "kotlin-1.9", + "kotlin-2.0", "cpp-17", "cpp-20", "bun-1.0", + "bun-1.1", "go-1.23" ], "x-enum-name": null, @@ -8874,7 +8882,7 @@ }, "x-appwrite": { "method": "listRuntimes", - "weight": 288, + "weight": 289, "cookies": false, "type": "", "deprecated": false, @@ -8928,7 +8936,7 @@ }, "x-appwrite": { "method": "listSpecifications", - "weight": 289, + "weight": 290, "cookies": false, "type": "", "deprecated": false, @@ -8983,7 +8991,7 @@ }, "x-appwrite": { "method": "get", - "weight": 290, + "weight": 291, "cookies": false, "type": "", "deprecated": false, @@ -9045,7 +9053,7 @@ }, "x-appwrite": { "method": "update", - "weight": 293, + "weight": 294, "cookies": false, "type": "", "deprecated": false, @@ -9106,6 +9114,7 @@ "node-19.0", "node-20.0", "node-21.0", + "node-22", "php-8.0", "php-8.1", "php-8.2", @@ -9124,6 +9133,8 @@ "deno-1.24", "deno-1.35", "deno-1.40", + "deno-1.46", + "deno-2.0", "dart-2.15", "dart-2.16", "dart-2.17", @@ -9131,23 +9142,28 @@ "dart-3.0", "dart-3.1", "dart-3.3", - "dotnet-3.1", + "dart-3.5", "dotnet-6.0", "dotnet-7.0", + "dotnet-8.0", "java-8.0", "java-11.0", "java-17.0", "java-18.0", "java-21.0", + "java-22", "swift-5.5", "swift-5.8", "swift-5.9", + "swift-5.10", "kotlin-1.6", "kotlin-1.8", "kotlin-1.9", + "kotlin-2.0", "cpp-17", "cpp-20", "bun-1.0", + "bun-1.1", "go-1.23" ], "x-enum-name": null, @@ -9279,7 +9295,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 296, + "weight": 297, "cookies": false, "type": "", "deprecated": false, @@ -9343,7 +9359,7 @@ }, "x-appwrite": { "method": "listDeployments", - "weight": 298, + "weight": 299, "cookies": false, "type": "", "deprecated": false, @@ -9426,7 +9442,7 @@ }, "x-appwrite": { "method": "createDeployment", - "weight": 297, + "weight": 298, "cookies": false, "type": "upload", "deprecated": false, @@ -9521,7 +9537,7 @@ }, "x-appwrite": { "method": "getDeployment", - "weight": 299, + "weight": 300, "cookies": false, "type": "", "deprecated": false, @@ -9591,7 +9607,7 @@ }, "x-appwrite": { "method": "updateDeployment", - "weight": 295, + "weight": 296, "cookies": false, "type": "", "deprecated": false, @@ -9656,7 +9672,7 @@ }, "x-appwrite": { "method": "deleteDeployment", - "weight": 300, + "weight": 301, "cookies": false, "type": "", "deprecated": false, @@ -9723,7 +9739,7 @@ }, "x-appwrite": { "method": "createBuild", - "weight": 301, + "weight": 302, "cookies": false, "type": "", "deprecated": false, @@ -9808,7 +9824,7 @@ }, "x-appwrite": { "method": "updateDeploymentBuild", - "weight": 302, + "weight": 303, "cookies": false, "type": "", "deprecated": false, @@ -9880,7 +9896,7 @@ }, "x-appwrite": { "method": "getDeploymentDownload", - "weight": 294, + "weight": 295, "cookies": false, "type": "location", "deprecated": false, @@ -9954,7 +9970,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 304, + "weight": 305, "cookies": false, "type": "", "deprecated": false, @@ -10041,7 +10057,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 303, + "weight": 304, "cookies": false, "type": "", "deprecated": false, @@ -10164,7 +10180,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 305, + "weight": 306, "cookies": false, "type": "", "deprecated": false, @@ -10233,7 +10249,7 @@ }, "x-appwrite": { "method": "deleteExecution", - "weight": 306, + "weight": 307, "cookies": false, "type": "", "deprecated": false, @@ -10305,7 +10321,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 308, + "weight": 309, "cookies": false, "type": "", "deprecated": false, @@ -10367,7 +10383,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 307, + "weight": 308, "cookies": false, "type": "", "deprecated": false, @@ -10456,7 +10472,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 309, + "weight": 310, "cookies": false, "type": "", "deprecated": false, @@ -10526,7 +10542,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 310, + "weight": 311, "cookies": false, "type": "", "deprecated": false, @@ -10615,7 +10631,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 311, + "weight": 312, "cookies": false, "type": "", "deprecated": false, @@ -10687,7 +10703,7 @@ }, "x-appwrite": { "method": "query", - "weight": 329, + "weight": 330, "cookies": false, "type": "graphql", "deprecated": false, @@ -10765,7 +10781,7 @@ }, "x-appwrite": { "method": "mutation", - "weight": 328, + "weight": 329, "cookies": false, "type": "graphql", "deprecated": false, @@ -10843,7 +10859,7 @@ }, "x-appwrite": { "method": "get", - "weight": 124, + "weight": 125, "cookies": false, "type": "", "deprecated": false, @@ -10897,7 +10913,7 @@ }, "x-appwrite": { "method": "getAntivirus", - "weight": 146, + "weight": 147, "cookies": false, "type": "", "deprecated": false, @@ -10951,7 +10967,7 @@ }, "x-appwrite": { "method": "getCache", - "weight": 127, + "weight": 128, "cookies": false, "type": "", "deprecated": false, @@ -11005,7 +11021,7 @@ }, "x-appwrite": { "method": "getCertificate", - "weight": 133, + "weight": 134, "cookies": false, "type": "", "deprecated": false, @@ -11068,7 +11084,7 @@ }, "x-appwrite": { "method": "getDB", - "weight": 126, + "weight": 127, "cookies": false, "type": "", "deprecated": false, @@ -11122,7 +11138,7 @@ }, "x-appwrite": { "method": "getPubSub", - "weight": 129, + "weight": 130, "cookies": false, "type": "", "deprecated": false, @@ -11176,7 +11192,7 @@ }, "x-appwrite": { "method": "getQueue", - "weight": 128, + "weight": 129, "cookies": false, "type": "", "deprecated": false, @@ -11230,7 +11246,7 @@ }, "x-appwrite": { "method": "getQueueBuilds", - "weight": 135, + "weight": 136, "cookies": false, "type": "", "deprecated": false, @@ -11295,7 +11311,7 @@ }, "x-appwrite": { "method": "getQueueCertificates", - "weight": 134, + "weight": 135, "cookies": false, "type": "", "deprecated": false, @@ -11360,7 +11376,7 @@ }, "x-appwrite": { "method": "getQueueDatabases", - "weight": 136, + "weight": 137, "cookies": false, "type": "", "deprecated": false, @@ -11434,7 +11450,7 @@ }, "x-appwrite": { "method": "getQueueDeletes", - "weight": 137, + "weight": 138, "cookies": false, "type": "", "deprecated": false, @@ -11499,7 +11515,7 @@ }, "x-appwrite": { "method": "getFailedJobs", - "weight": 147, + "weight": 148, "cookies": false, "type": "", "deprecated": false, @@ -11588,7 +11604,7 @@ }, "x-appwrite": { "method": "getQueueFunctions", - "weight": 141, + "weight": 142, "cookies": false, "type": "", "deprecated": false, @@ -11653,7 +11669,7 @@ }, "x-appwrite": { "method": "getQueueLogs", - "weight": 132, + "weight": 133, "cookies": false, "type": "", "deprecated": false, @@ -11718,7 +11734,7 @@ }, "x-appwrite": { "method": "getQueueMails", - "weight": 138, + "weight": 139, "cookies": false, "type": "", "deprecated": false, @@ -11783,7 +11799,7 @@ }, "x-appwrite": { "method": "getQueueMessaging", - "weight": 139, + "weight": 140, "cookies": false, "type": "", "deprecated": false, @@ -11848,7 +11864,7 @@ }, "x-appwrite": { "method": "getQueueMigrations", - "weight": 140, + "weight": 141, "cookies": false, "type": "", "deprecated": false, @@ -11913,7 +11929,7 @@ }, "x-appwrite": { "method": "getQueueUsage", - "weight": 142, + "weight": 143, "cookies": false, "type": "", "deprecated": false, @@ -11978,7 +11994,7 @@ }, "x-appwrite": { "method": "getQueueUsageDump", - "weight": 143, + "weight": 144, "cookies": false, "type": "", "deprecated": false, @@ -12043,7 +12059,7 @@ }, "x-appwrite": { "method": "getQueueWebhooks", - "weight": 131, + "weight": 132, "cookies": false, "type": "", "deprecated": false, @@ -12108,7 +12124,7 @@ }, "x-appwrite": { "method": "getStorage", - "weight": 145, + "weight": 146, "cookies": false, "type": "", "deprecated": false, @@ -12162,7 +12178,7 @@ }, "x-appwrite": { "method": "getStorageLocal", - "weight": 144, + "weight": 145, "cookies": false, "type": "", "deprecated": false, @@ -12216,7 +12232,7 @@ }, "x-appwrite": { "method": "getTime", - "weight": 130, + "weight": 131, "cookies": false, "type": "", "deprecated": false, @@ -12270,7 +12286,7 @@ }, "x-appwrite": { "method": "get", - "weight": 116, + "weight": 117, "cookies": false, "type": "", "deprecated": false, @@ -12328,7 +12344,7 @@ }, "x-appwrite": { "method": "listCodes", - "weight": 117, + "weight": 118, "cookies": false, "type": "", "deprecated": false, @@ -12386,7 +12402,7 @@ }, "x-appwrite": { "method": "listContinents", - "weight": 121, + "weight": 122, "cookies": false, "type": "", "deprecated": false, @@ -12444,7 +12460,7 @@ }, "x-appwrite": { "method": "listCountries", - "weight": 118, + "weight": 119, "cookies": false, "type": "", "deprecated": false, @@ -12502,7 +12518,7 @@ }, "x-appwrite": { "method": "listCountriesEU", - "weight": 119, + "weight": 120, "cookies": false, "type": "", "deprecated": false, @@ -12560,7 +12576,7 @@ }, "x-appwrite": { "method": "listCountriesPhones", - "weight": 120, + "weight": 121, "cookies": false, "type": "", "deprecated": false, @@ -12618,7 +12634,7 @@ }, "x-appwrite": { "method": "listCurrencies", - "weight": 122, + "weight": 123, "cookies": false, "type": "", "deprecated": false, @@ -12676,7 +12692,7 @@ }, "x-appwrite": { "method": "listLanguages", - "weight": 123, + "weight": 124, "cookies": false, "type": "", "deprecated": false, @@ -12734,7 +12750,7 @@ }, "x-appwrite": { "method": "listMessages", - "weight": 388, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -12812,7 +12828,7 @@ }, "x-appwrite": { "method": "createEmail", - "weight": 385, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -12973,7 +12989,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 392, + "weight": 393, "cookies": false, "type": "", "deprecated": false, @@ -13131,7 +13147,7 @@ }, "x-appwrite": { "method": "createPush", - "weight": 387, + "weight": 388, "cookies": false, "type": "", "deprecated": false, @@ -13307,7 +13323,7 @@ }, "x-appwrite": { "method": "updatePush", - "weight": 394, + "weight": 395, "cookies": false, "type": "", "deprecated": false, @@ -13480,7 +13496,7 @@ }, "x-appwrite": { "method": "createSms", - "weight": 386, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -13601,7 +13617,7 @@ }, "x-appwrite": { "method": "updateSms", - "weight": 393, + "weight": 394, "cookies": false, "type": "", "deprecated": false, @@ -13720,7 +13736,7 @@ }, "x-appwrite": { "method": "getMessage", - "weight": 391, + "weight": 392, "cookies": false, "type": "", "deprecated": false, @@ -13780,7 +13796,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 395, + "weight": 396, "cookies": false, "type": "", "deprecated": false, @@ -13845,7 +13861,7 @@ }, "x-appwrite": { "method": "listMessageLogs", - "weight": 389, + "weight": 390, "cookies": false, "type": "", "deprecated": false, @@ -13922,7 +13938,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 390, + "weight": 391, "cookies": false, "type": "", "deprecated": false, @@ -13999,7 +14015,7 @@ }, "x-appwrite": { "method": "listProviders", - "weight": 360, + "weight": 361, "cookies": false, "type": "", "deprecated": false, @@ -14077,7 +14093,7 @@ }, "x-appwrite": { "method": "createApnsProvider", - "weight": 359, + "weight": 360, "cookies": false, "type": "", "deprecated": false, @@ -14195,7 +14211,7 @@ }, "x-appwrite": { "method": "updateApnsProvider", - "weight": 372, + "weight": 373, "cookies": false, "type": "", "deprecated": false, @@ -14311,7 +14327,7 @@ }, "x-appwrite": { "method": "createFcmProvider", - "weight": 358, + "weight": 359, "cookies": false, "type": "", "deprecated": false, @@ -14405,7 +14421,7 @@ }, "x-appwrite": { "method": "updateFcmProvider", - "weight": 371, + "weight": 372, "cookies": false, "type": "", "deprecated": false, @@ -14497,7 +14513,7 @@ }, "x-appwrite": { "method": "createMailgunProvider", - "weight": 350, + "weight": 351, "cookies": false, "type": "", "deprecated": false, @@ -14627,7 +14643,7 @@ }, "x-appwrite": { "method": "updateMailgunProvider", - "weight": 363, + "weight": 364, "cookies": false, "type": "", "deprecated": false, @@ -14755,7 +14771,7 @@ }, "x-appwrite": { "method": "createMsg91Provider", - "weight": 353, + "weight": 354, "cookies": false, "type": "", "deprecated": false, @@ -14861,7 +14877,7 @@ }, "x-appwrite": { "method": "updateMsg91Provider", - "weight": 366, + "weight": 367, "cookies": false, "type": "", "deprecated": false, @@ -14965,7 +14981,7 @@ }, "x-appwrite": { "method": "createSendgridProvider", - "weight": 351, + "weight": 352, "cookies": false, "type": "", "deprecated": false, @@ -15083,7 +15099,7 @@ }, "x-appwrite": { "method": "updateSendgridProvider", - "weight": 364, + "weight": 365, "cookies": false, "type": "", "deprecated": false, @@ -15199,7 +15215,7 @@ }, "x-appwrite": { "method": "createSmtpProvider", - "weight": 352, + "weight": 353, "cookies": false, "type": "", "deprecated": false, @@ -15361,7 +15377,7 @@ }, "x-appwrite": { "method": "updateSmtpProvider", - "weight": 365, + "weight": 366, "cookies": false, "type": "", "deprecated": false, @@ -15520,7 +15536,7 @@ }, "x-appwrite": { "method": "createTelesignProvider", - "weight": 354, + "weight": 355, "cookies": false, "type": "", "deprecated": false, @@ -15626,7 +15642,7 @@ }, "x-appwrite": { "method": "updateTelesignProvider", - "weight": 367, + "weight": 368, "cookies": false, "type": "", "deprecated": false, @@ -15730,7 +15746,7 @@ }, "x-appwrite": { "method": "createTextmagicProvider", - "weight": 355, + "weight": 356, "cookies": false, "type": "", "deprecated": false, @@ -15836,7 +15852,7 @@ }, "x-appwrite": { "method": "updateTextmagicProvider", - "weight": 368, + "weight": 369, "cookies": false, "type": "", "deprecated": false, @@ -15940,7 +15956,7 @@ }, "x-appwrite": { "method": "createTwilioProvider", - "weight": 356, + "weight": 357, "cookies": false, "type": "", "deprecated": false, @@ -16046,7 +16062,7 @@ }, "x-appwrite": { "method": "updateTwilioProvider", - "weight": 369, + "weight": 370, "cookies": false, "type": "", "deprecated": false, @@ -16150,7 +16166,7 @@ }, "x-appwrite": { "method": "createVonageProvider", - "weight": 357, + "weight": 358, "cookies": false, "type": "", "deprecated": false, @@ -16256,7 +16272,7 @@ }, "x-appwrite": { "method": "updateVonageProvider", - "weight": 370, + "weight": 371, "cookies": false, "type": "", "deprecated": false, @@ -16360,7 +16376,7 @@ }, "x-appwrite": { "method": "getProvider", - "weight": 362, + "weight": 363, "cookies": false, "type": "", "deprecated": false, @@ -16420,7 +16436,7 @@ }, "x-appwrite": { "method": "deleteProvider", - "weight": 373, + "weight": 374, "cookies": false, "type": "", "deprecated": false, @@ -16485,7 +16501,7 @@ }, "x-appwrite": { "method": "listProviderLogs", - "weight": 361, + "weight": 362, "cookies": false, "type": "", "deprecated": false, @@ -16562,7 +16578,7 @@ }, "x-appwrite": { "method": "listSubscriberLogs", - "weight": 382, + "weight": 383, "cookies": false, "type": "", "deprecated": false, @@ -16639,7 +16655,7 @@ }, "x-appwrite": { "method": "listTopics", - "weight": 375, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -16715,7 +16731,7 @@ }, "x-appwrite": { "method": "createTopic", - "weight": 374, + "weight": 375, "cookies": false, "type": "", "deprecated": false, @@ -16808,7 +16824,7 @@ }, "x-appwrite": { "method": "getTopic", - "weight": 377, + "weight": 378, "cookies": false, "type": "", "deprecated": false, @@ -16871,7 +16887,7 @@ }, "x-appwrite": { "method": "updateTopic", - "weight": 378, + "weight": 379, "cookies": false, "type": "", "deprecated": false, @@ -16955,7 +16971,7 @@ }, "x-appwrite": { "method": "deleteTopic", - "weight": 379, + "weight": 380, "cookies": false, "type": "", "deprecated": false, @@ -17020,7 +17036,7 @@ }, "x-appwrite": { "method": "listTopicLogs", - "weight": 376, + "weight": 377, "cookies": false, "type": "", "deprecated": false, @@ -17097,7 +17113,7 @@ }, "x-appwrite": { "method": "listSubscribers", - "weight": 381, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -17181,7 +17197,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 380, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -17275,7 +17291,7 @@ }, "x-appwrite": { "method": "getSubscriber", - "weight": 383, + "weight": 384, "cookies": false, "type": "", "deprecated": false, @@ -17343,7 +17359,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 384, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -17420,7 +17436,7 @@ }, "x-appwrite": { "method": "listBuckets", - "weight": 201, + "weight": 202, "cookies": false, "type": "", "deprecated": false, @@ -17495,7 +17511,7 @@ }, "x-appwrite": { "method": "createBucket", - "weight": 200, + "weight": 201, "cookies": false, "type": "", "deprecated": false, @@ -17637,7 +17653,7 @@ }, "x-appwrite": { "method": "getBucket", - "weight": 202, + "weight": 203, "cookies": false, "type": "", "deprecated": false, @@ -17699,7 +17715,7 @@ }, "x-appwrite": { "method": "updateBucket", - "weight": 203, + "weight": 204, "cookies": false, "type": "", "deprecated": false, @@ -17835,7 +17851,7 @@ }, "x-appwrite": { "method": "deleteBucket", - "weight": 204, + "weight": 205, "cookies": false, "type": "", "deprecated": false, @@ -17899,7 +17915,7 @@ }, "x-appwrite": { "method": "listFiles", - "weight": 206, + "weight": 207, "cookies": false, "type": "", "deprecated": false, @@ -17986,7 +18002,7 @@ }, "x-appwrite": { "method": "createFile", - "weight": 205, + "weight": 206, "cookies": false, "type": "upload", "deprecated": false, @@ -18082,7 +18098,7 @@ }, "x-appwrite": { "method": "getFile", - "weight": 207, + "weight": 208, "cookies": false, "type": "", "deprecated": false, @@ -18156,7 +18172,7 @@ }, "x-appwrite": { "method": "updateFile", - "weight": 212, + "weight": 213, "cookies": false, "type": "", "deprecated": false, @@ -18249,7 +18265,7 @@ }, "x-appwrite": { "method": "deleteFile", - "weight": 213, + "weight": 214, "cookies": false, "type": "", "deprecated": false, @@ -18325,7 +18341,7 @@ }, "x-appwrite": { "method": "getFileDownload", - "weight": 209, + "weight": 210, "cookies": false, "type": "location", "deprecated": false, @@ -18401,7 +18417,7 @@ }, "x-appwrite": { "method": "getFilePreview", - "weight": 208, + "weight": 209, "cookies": false, "type": "location", "deprecated": false, @@ -18604,7 +18620,7 @@ }, "x-appwrite": { "method": "getFileView", - "weight": 210, + "weight": 211, "cookies": false, "type": "location", "deprecated": false, @@ -18680,7 +18696,7 @@ }, "x-appwrite": { "method": "list", - "weight": 217, + "weight": 218, "cookies": false, "type": "", "deprecated": false, @@ -18759,7 +18775,7 @@ }, "x-appwrite": { "method": "create", - "weight": 216, + "weight": 217, "cookies": false, "type": "", "deprecated": false, @@ -18855,7 +18871,7 @@ }, "x-appwrite": { "method": "get", - "weight": 218, + "weight": 219, "cookies": false, "type": "", "deprecated": false, @@ -18921,7 +18937,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 220, + "weight": 221, "cookies": false, "type": "", "deprecated": false, @@ -19000,7 +19016,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 222, + "weight": 223, "cookies": false, "type": "", "deprecated": false, @@ -19068,7 +19084,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 224, + "weight": 225, "cookies": false, "type": "", "deprecated": false, @@ -19155,7 +19171,7 @@ }, "x-appwrite": { "method": "createMembership", - "weight": 223, + "weight": 224, "cookies": false, "type": "", "deprecated": false, @@ -19274,7 +19290,7 @@ }, "x-appwrite": { "method": "getMembership", - "weight": 225, + "weight": 226, "cookies": false, "type": "", "deprecated": false, @@ -19348,7 +19364,7 @@ }, "x-appwrite": { "method": "updateMembership", - "weight": 226, + "weight": 227, "cookies": false, "type": "", "deprecated": false, @@ -19438,7 +19454,7 @@ }, "x-appwrite": { "method": "deleteMembership", - "weight": 228, + "weight": 229, "cookies": false, "type": "", "deprecated": false, @@ -19514,7 +19530,7 @@ }, "x-appwrite": { "method": "updateMembershipStatus", - "weight": 227, + "weight": 228, "cookies": false, "type": "", "deprecated": false, @@ -19613,7 +19629,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 219, + "weight": 220, "cookies": false, "type": "", "deprecated": false, @@ -19677,7 +19693,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 221, + "weight": 222, "cookies": false, "type": "", "deprecated": false, @@ -19761,7 +19777,7 @@ }, "x-appwrite": { "method": "list", - "weight": 239, + "weight": 240, "cookies": false, "type": "", "deprecated": false, @@ -19836,7 +19852,7 @@ }, "x-appwrite": { "method": "create", - "weight": 230, + "weight": 231, "cookies": false, "type": "", "deprecated": false, @@ -19934,7 +19950,7 @@ }, "x-appwrite": { "method": "createArgon2User", - "weight": 233, + "weight": 234, "cookies": false, "type": "", "deprecated": false, @@ -20028,7 +20044,7 @@ }, "x-appwrite": { "method": "createBcryptUser", - "weight": 231, + "weight": 232, "cookies": false, "type": "", "deprecated": false, @@ -20122,7 +20138,7 @@ }, "x-appwrite": { "method": "listIdentities", - "weight": 247, + "weight": 248, "cookies": false, "type": "", "deprecated": false, @@ -20194,7 +20210,7 @@ }, "x-appwrite": { "method": "deleteIdentity", - "weight": 270, + "weight": 271, "cookies": false, "type": "", "deprecated": false, @@ -20258,7 +20274,7 @@ }, "x-appwrite": { "method": "createMD5User", - "weight": 232, + "weight": 233, "cookies": false, "type": "", "deprecated": false, @@ -20352,7 +20368,7 @@ }, "x-appwrite": { "method": "createPHPassUser", - "weight": 235, + "weight": 236, "cookies": false, "type": "", "deprecated": false, @@ -20446,7 +20462,7 @@ }, "x-appwrite": { "method": "createScryptUser", - "weight": 236, + "weight": 237, "cookies": false, "type": "", "deprecated": false, @@ -20575,7 +20591,7 @@ }, "x-appwrite": { "method": "createScryptModifiedUser", - "weight": 237, + "weight": 238, "cookies": false, "type": "", "deprecated": false, @@ -20690,7 +20706,7 @@ }, "x-appwrite": { "method": "createSHAUser", - "weight": 234, + "weight": 235, "cookies": false, "type": "", "deprecated": false, @@ -20805,7 +20821,7 @@ }, "x-appwrite": { "method": "get", - "weight": 240, + "weight": 241, "cookies": false, "type": "", "deprecated": false, @@ -20862,7 +20878,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 268, + "weight": 269, "cookies": false, "type": "", "deprecated": false, @@ -20926,7 +20942,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 253, + "weight": 254, "cookies": false, "type": "", "deprecated": false, @@ -21008,7 +21024,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 271, + "weight": 272, "cookies": false, "type": "", "deprecated": false, @@ -21093,7 +21109,7 @@ }, "x-appwrite": { "method": "updateLabels", - "weight": 249, + "weight": 250, "cookies": false, "type": "", "deprecated": false, @@ -21178,7 +21194,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 245, + "weight": 246, "cookies": false, "type": "", "deprecated": false, @@ -21254,7 +21270,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 244, + "weight": 245, "cookies": false, "type": "", "deprecated": false, @@ -21318,7 +21334,7 @@ }, "x-appwrite": { "method": "updateMfa", - "weight": 258, + "weight": 259, "cookies": false, "type": "", "deprecated": false, @@ -21400,7 +21416,7 @@ }, "x-appwrite": { "method": "deleteMfaAuthenticator", - "weight": 263, + "weight": 264, "cookies": false, "type": "", "deprecated": false, @@ -21477,7 +21493,7 @@ }, "x-appwrite": { "method": "listMfaFactors", - "weight": 259, + "weight": 260, "cookies": false, "type": "", "deprecated": false, @@ -21541,7 +21557,7 @@ }, "x-appwrite": { "method": "getMfaRecoveryCodes", - "weight": 260, + "weight": 261, "cookies": false, "type": "", "deprecated": false, @@ -21603,7 +21619,7 @@ }, "x-appwrite": { "method": "updateMfaRecoveryCodes", - "weight": 262, + "weight": 263, "cookies": false, "type": "", "deprecated": false, @@ -21665,7 +21681,7 @@ }, "x-appwrite": { "method": "createMfaRecoveryCodes", - "weight": 261, + "weight": 262, "cookies": false, "type": "", "deprecated": false, @@ -21729,7 +21745,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 251, + "weight": 252, "cookies": false, "type": "", "deprecated": false, @@ -21811,7 +21827,7 @@ }, "x-appwrite": { "method": "updatePassword", - "weight": 252, + "weight": 253, "cookies": false, "type": "", "deprecated": false, @@ -21893,7 +21909,7 @@ }, "x-appwrite": { "method": "updatePhone", - "weight": 254, + "weight": 255, "cookies": false, "type": "", "deprecated": false, @@ -21975,7 +21991,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 241, + "weight": 242, "cookies": false, "type": "", "deprecated": false, @@ -22037,7 +22053,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 256, + "weight": 257, "cookies": false, "type": "", "deprecated": false, @@ -22119,7 +22135,7 @@ }, "x-appwrite": { "method": "listSessions", - "weight": 243, + "weight": 244, "cookies": false, "type": "", "deprecated": false, @@ -22181,7 +22197,7 @@ }, "x-appwrite": { "method": "createSession", - "weight": 264, + "weight": 265, "cookies": false, "type": "", "deprecated": false, @@ -22238,7 +22254,7 @@ }, "x-appwrite": { "method": "deleteSessions", - "weight": 267, + "weight": 268, "cookies": false, "type": "", "deprecated": false, @@ -22297,7 +22313,7 @@ }, "x-appwrite": { "method": "deleteSession", - "weight": 266, + "weight": 267, "cookies": false, "type": "", "deprecated": false, @@ -22369,7 +22385,7 @@ }, "x-appwrite": { "method": "updateStatus", - "weight": 248, + "weight": 249, "cookies": false, "type": "", "deprecated": false, @@ -22451,7 +22467,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 246, + "weight": 247, "cookies": false, "type": "", "deprecated": false, @@ -22526,7 +22542,7 @@ }, "x-appwrite": { "method": "createTarget", - "weight": 238, + "weight": 239, "cookies": false, "type": "", "deprecated": false, @@ -22642,7 +22658,7 @@ }, "x-appwrite": { "method": "getTarget", - "weight": 242, + "weight": 243, "cookies": false, "type": "", "deprecated": false, @@ -22713,7 +22729,7 @@ }, "x-appwrite": { "method": "updateTarget", - "weight": 257, + "weight": 258, "cookies": false, "type": "", "deprecated": false, @@ -22808,7 +22824,7 @@ }, "x-appwrite": { "method": "deleteTarget", - "weight": 269, + "weight": 270, "cookies": false, "type": "", "deprecated": false, @@ -22881,7 +22897,7 @@ }, "x-appwrite": { "method": "createToken", - "weight": 265, + "weight": 266, "cookies": false, "type": "", "deprecated": false, @@ -22966,7 +22982,7 @@ }, "x-appwrite": { "method": "updateEmailVerification", - "weight": 255, + "weight": 256, "cookies": false, "type": "", "deprecated": false, @@ -23048,7 +23064,7 @@ }, "x-appwrite": { "method": "updatePhoneVerification", - "weight": 250, + "weight": 251, "cookies": false, "type": "", "deprecated": false, @@ -26585,7 +26601,7 @@ "format": "int32" }, "responseBody": { - "type": "payload", + "type": "string", "description": "HTTP response body. This will return empty unless execution is created as synchronous.", "x-example": "" }, From 4fa8f01ce2887ac7a4c5316ca94f66cb99a675cd Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 10 Oct 2024 21:15:18 +1300 Subject: [PATCH 059/101] Call migration success on success --- composer.lock | 14 +++++++------- src/Appwrite/Platform/Workers/Migrations.php | 5 +++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/composer.lock b/composer.lock index 1e8c69c70b..083c4c3214 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "f05357728316c79037d22ee26afd2d3e", + "content-hash": "89ebc75f08cd9ee5a5cccd64d0f9938a", "packages": [ { "name": "adhocore/jwt", @@ -2175,16 +2175,16 @@ }, { "name": "utopia-php/migration", - "version": "0.6.6", + "version": "0.6.8", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "667997c7ca6c445001d56f70205b6cf13c6b7343" + "reference": "c3740de52c1b616aa7f054d0fadb9207895b5279" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/667997c7ca6c445001d56f70205b6cf13c6b7343", - "reference": "667997c7ca6c445001d56f70205b6cf13c6b7343", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/c3740de52c1b616aa7f054d0fadb9207895b5279", + "reference": "c3740de52c1b616aa7f054d0fadb9207895b5279", "shasum": "" }, "require": { @@ -2225,9 +2225,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/0.6.6" + "source": "https://github.com/utopia-php/migration/tree/0.6.8" }, - "time": "2024-10-09T09:51:43+00:00" + "time": "2024-10-10T08:09:19+00:00" }, { "name": "utopia-php/mongo", diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index c25d1b59e4..beff0b064b 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -399,6 +399,11 @@ class Migrations extends Action throw new Exception('Migration failed'); } + + if ($migration->getAttribute('status', '') === 'completed') { + $destination->success(); + $source->success(); + } } } } From a246ba1a9e757f21ee604e5c8e50fa4ba7d7a916 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 10 Oct 2024 21:23:11 +1300 Subject: [PATCH 060/101] Lint --- app/config/function-templates.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/function-templates.php b/app/config/function-templates.php index 7dc039e08d..762c33dd9a 100644 --- a/app/config/function-templates.php +++ b/app/config/function-templates.php @@ -2070,4 +2070,4 @@ return [ ], 'scopes' => ['users.read', 'users.write'] ] -]; \ No newline at end of file +]; From 8fea73d3bf2dd093a202a63e63b8a6bb87e539c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Fri, 11 Oct 2024 13:32:48 +0000 Subject: [PATCH 061/101] Fix db issues --- app/controllers/general.php | 1 + src/Appwrite/Platform/Workers/Functions.php | 13 ++++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index f31d0522d8..afc126838d 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -370,6 +370,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo $queueForFunctions ->setType('delayed_execution_write') ->setExecution($execution) + ->setProject($project) ->trigger(); } diff --git a/src/Appwrite/Platform/Workers/Functions.php b/src/Appwrite/Platform/Workers/Functions.php index 5b917b5ac9..aa88f5cfce 100644 --- a/src/Appwrite/Platform/Workers/Functions.php +++ b/src/Appwrite/Platform/Workers/Functions.php @@ -72,6 +72,14 @@ class Functions extends Action } $type = $payload['type'] ?? ''; + + // Short-term solution to offhand write operation from API contianer + if ($type === 'delayed_execution_write') { + $execution = new Document($payload['execution'] ?? []); + $execution = $dbForProject->createDocument('executions', $execution); + return; + } + $events = $payload['events'] ?? []; $data = $payload['body'] ?? ''; $eventData = $payload['payload'] ?? ''; @@ -209,11 +217,6 @@ class Functions extends Action executionId: $execution->getId() ?? null ); break; - case 'delayed_execution_write': - $execution = new Document($payload['execution'] ?? []); - $execution->setAttribute('$collection', 'executions'); - $execution = $dbForProject->createDocument('executions', $execution); - return; } } From de06af6c4b0140ff00420faa539ff0f765b4cf5b Mon Sep 17 00:00:00 2001 From: Fabian Gruber Date: Wed, 9 Oct 2024 17:47:58 +0200 Subject: [PATCH 062/101] chore: update utopia-php/system to 0.9.0 --- composer.json | 2 +- composer.lock | 40 ++++++++++++++++++++-------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/composer.json b/composer.json index e7c7743da7..813e9feff2 100644 --- a/composer.json +++ b/composer.json @@ -69,7 +69,7 @@ "utopia-php/registry": "0.5.*", "utopia-php/storage": "0.18.*", "utopia-php/swoole": "0.8.*", - "utopia-php/system": "0.8.*", + "utopia-php/system": "0.9.*", "utopia-php/vcs": "0.8.*", "utopia-php/websocket": "0.1.*", "matomo/device-detector": "6.1.*", diff --git a/composer.lock b/composer.lock index 083c4c3214..2066feae40 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "89ebc75f08cd9ee5a5cccd64d0f9938a", + "content-hash": "5fef87b7ea4352869a502d1570bd43e7", "packages": [ { "name": "adhocore/jwt", @@ -157,21 +157,21 @@ }, { "name": "appwrite/php-runtimes", - "version": "0.16.1", + "version": "0.16.2", "source": { "type": "git", "url": "https://github.com/appwrite/runtimes.git", - "reference": "b36da25768c2dc66908ae226f6eed1b073a6f642" + "reference": "c33005e3eaaf2d427e9fd1077d5335e31f4d36f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/runtimes/zipball/b36da25768c2dc66908ae226f6eed1b073a6f642", - "reference": "b36da25768c2dc66908ae226f6eed1b073a6f642", + "url": "https://api.github.com/repos/appwrite/runtimes/zipball/c33005e3eaaf2d427e9fd1077d5335e31f4d36f9", + "reference": "c33005e3eaaf2d427e9fd1077d5335e31f4d36f9", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/system": "0.8.*" + "utopia-php/system": "0.9.*" }, "require-dev": { "laravel/pint": "^1.15", @@ -206,9 +206,9 @@ ], "support": { "issues": "https://github.com/appwrite/runtimes/issues", - "source": "https://github.com/appwrite/runtimes/tree/0.16.1" + "source": "https://github.com/appwrite/runtimes/tree/0.16.2" }, - "time": "2024-10-09T10:27:14+00:00" + "time": "2024-10-09T15:02:52+00:00" }, { "name": "beberlei/assert", @@ -2714,16 +2714,16 @@ }, { "name": "utopia-php/system", - "version": "0.8.0", + "version": "0.9.0", "source": { "type": "git", "url": "https://github.com/utopia-php/system.git", - "reference": "a2cbfb3c69b9ecb8b6f06c5774f3cf279ea7665e" + "reference": "8e4a7edaf2dfeb4c9524e9f766d27754f2c4b64d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/system/zipball/a2cbfb3c69b9ecb8b6f06c5774f3cf279ea7665e", - "reference": "a2cbfb3c69b9ecb8b6f06c5774f3cf279ea7665e", + "url": "https://api.github.com/repos/utopia-php/system/zipball/8e4a7edaf2dfeb4c9524e9f766d27754f2c4b64d", + "reference": "8e4a7edaf2dfeb4c9524e9f766d27754f2c4b64d", "shasum": "" }, "require": { @@ -2764,9 +2764,9 @@ ], "support": { "issues": "https://github.com/utopia-php/system/issues", - "source": "https://github.com/utopia-php/system/tree/0.8.0" + "source": "https://github.com/utopia-php/system/tree/0.9.0" }, - "time": "2024-04-01T10:22:28+00:00" + "time": "2024-10-09T14:44:01+00:00" }, { "name": "utopia-php/vcs", @@ -3002,16 +3002,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.39.23", + "version": "0.39.24", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "0acceabb7593c9c07c5db85a84a5ebac60896763" + "reference": "412451c87f6ef17e24e9a5cf41721043d74c60c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/0acceabb7593c9c07c5db85a84a5ebac60896763", - "reference": "0acceabb7593c9c07c5db85a84a5ebac60896763", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/412451c87f6ef17e24e9a5cf41721043d74c60c8", + "reference": "412451c87f6ef17e24e9a5cf41721043d74c60c8", "shasum": "" }, "require": { @@ -3047,9 +3047,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/0.39.23" + "source": "https://github.com/appwrite/sdk-generator/tree/0.39.24" }, - "time": "2024-10-08T00:38:57+00:00" + "time": "2024-10-09T19:13:27+00:00" }, { "name": "doctrine/annotations", From 408dcbc263150b3ca2c9204904ebc8ca63876eeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 14 Oct 2024 13:06:08 +0000 Subject: [PATCH 063/101] Add async exec doc test --- app/controllers/general.php | 2 +- src/Appwrite/Event/Func.php | 2 ++ src/Appwrite/Platform/Workers/Functions.php | 2 +- .../Services/Functions/FunctionsCustomServerTest.php | 11 +++++++++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 03776144e6..b2a07f06f6 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -369,7 +369,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo ; $queueForFunctions - ->setType('delayed_execution_write') + ->setType(Func::TYPE_ASYNC_WRITE) ->setExecution($execution) ->setProject($project) ->trigger(); diff --git a/src/Appwrite/Event/Func.php b/src/Appwrite/Event/Func.php index 0cbaf17b60..4dad5802f7 100644 --- a/src/Appwrite/Event/Func.php +++ b/src/Appwrite/Event/Func.php @@ -8,6 +8,8 @@ use Utopia\Queue\Connection; class Func extends Event { + public const TYPE_ASYNC_WRITE = 'async_write'; + protected string $jwt = ''; protected string $type = ''; protected string $body = ''; diff --git a/src/Appwrite/Platform/Workers/Functions.php b/src/Appwrite/Platform/Workers/Functions.php index aa88f5cfce..7e548f57be 100644 --- a/src/Appwrite/Platform/Workers/Functions.php +++ b/src/Appwrite/Platform/Workers/Functions.php @@ -74,7 +74,7 @@ class Functions extends Action $type = $payload['type'] ?? ''; // Short-term solution to offhand write operation from API contianer - if ($type === 'delayed_execution_write') { + if ($type === Func::TYPE_ASYNC_WRITE) { $execution = new Document($payload['execution'] ?? []); $execution = $dbForProject->createDocument('executions', $execution); return; diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 59a3041a3f..9b9f03a100 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -1675,6 +1675,17 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals($cookie, $response['body']); + // Async execution document creation + $this->assertEventually(function () use ($functionId) { + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(200, $executions['headers']['status-code']); + $this->assertEquals(1, count($executions['body']['executions'])); + }); + // Await Aggregation sleep(System::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', 30)); From c3d6dcad866f93b8ead7a366dab30269df8805f7 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 15 Oct 2024 13:46:00 +0100 Subject: [PATCH 064/101] chore: bump assistant --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 6ecb0ecff8..479ca38b8f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -863,7 +863,7 @@ services: appwrite-assistant: container_name: appwrite-assistant - image: appwrite/assistant:0.4.0 + image: appwrite/assistant:0.5.0 networks: - appwrite environment: From 801724e90e01191edb38ad39711482bbd0814460 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 15 Oct 2024 15:50:02 +0300 Subject: [PATCH 065/101] APP_DATABASE_QUERY_VALUES --- app/init.php | 10 ++-- composer.json | 2 +- composer.lock | 54 +++++++++++-------- .../Database/Validator/Queries/Base.php | 2 +- 4 files changed, 41 insertions(+), 27 deletions(-) diff --git a/app/init.php b/app/init.php index c2777f36f1..dcf9a21f56 100644 --- a/app/init.php +++ b/app/init.php @@ -130,6 +130,7 @@ const APP_DATABASE_ATTRIBUTE_INT_RANGE = 'intRange'; const APP_DATABASE_ATTRIBUTE_FLOAT_RANGE = 'floatRange'; const APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH = 1_073_741_824; // 2^32 bits / 4 bits per char const APP_DATABASE_TIMEOUT_MILLISECONDS = 15_000; +const APP_DATABASE_QUERY_VALUES = 100; const APP_STORAGE_UPLOADS = '/storage/uploads'; const APP_STORAGE_FUNCTIONS = '/storage/functions'; const APP_STORAGE_BUILDS = '/storage/builds'; @@ -1398,7 +1399,8 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForConsole, $database ->setMetadata('host', \gethostname()) ->setMetadata('project', $project->getId()) - ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); + ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS) + ->setMaxQueryValues(APP_DATABASE_QUERY_VALUES); try { $dsn = new DSN($project->getAttribute('database')); @@ -1434,7 +1436,8 @@ App::setResource('dbForConsole', function (Group $pools, Cache $cache) { ->setNamespace('_console') ->setMetadata('host', \gethostname()) ->setMetadata('project', 'console') - ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); + ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS) + ->setMaxQueryValues(APP_DATABASE_QUERY_VALUES); return $database; }, ['pools', 'cache']); @@ -1458,7 +1461,8 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $database ->setMetadata('host', \gethostname()) ->setMetadata('project', $project->getId()) - ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); + ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS) + ->setMaxQueryValues(APP_DATABASE_QUERY_VALUES); if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $database diff --git a/composer.json b/composer.json index 813e9feff2..e12fc531ec 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "utopia-php/cache": "0.10.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.53.6", + "utopia-php/database": "dev-0.53.x-max-query-size as 0.53.6", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", diff --git a/composer.lock b/composer.lock index 7213e1ecd5..6f9b7eb6cd 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "5fef87b7ea4352869a502d1570bd43e7", + "content-hash": "59f5ddee3c1259ce3f63762f2f064cc6", "packages": [ { "name": "adhocore/jwt", @@ -1724,16 +1724,16 @@ }, { "name": "utopia-php/database", - "version": "0.53.6", + "version": "dev-0.53.x-max-query-size", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "feddc8e808eaea9a11c65cca3f01683def422f52" + "reference": "7c15e2ba06e82d7daeb4edbf43564b435324a7d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/feddc8e808eaea9a11c65cca3f01683def422f52", - "reference": "feddc8e808eaea9a11c65cca3f01683def422f52", + "url": "https://api.github.com/repos/utopia-php/database/zipball/7c15e2ba06e82d7daeb4edbf43564b435324a7d4", + "reference": "7c15e2ba06e82d7daeb4edbf43564b435324a7d4", "shasum": "" }, "require": { @@ -1774,9 +1774,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.53.6" + "source": "https://github.com/utopia-php/database/tree/0.53.x-max-query-size" }, - "time": "2024-10-08T02:18:46+00:00" + "time": "2024-10-15T12:20:07+00:00" }, { "name": "utopia-php/domains", @@ -2070,16 +2070,16 @@ }, { "name": "utopia-php/logger", - "version": "0.6.1", + "version": "0.6.2", "source": { "type": "git", "url": "https://github.com/utopia-php/logger.git", - "reference": "7e8ff512c6f04577aba1df67c7b9628971946f9c" + "reference": "25b5bd2ad8bb51292f76332faa7034644fd0941d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/logger/zipball/7e8ff512c6f04577aba1df67c7b9628971946f9c", - "reference": "7e8ff512c6f04577aba1df67c7b9628971946f9c", + "url": "https://api.github.com/repos/utopia-php/logger/zipball/25b5bd2ad8bb51292f76332faa7034644fd0941d", + "reference": "25b5bd2ad8bb51292f76332faa7034644fd0941d", "shasum": "" }, "require": { @@ -2118,9 +2118,9 @@ ], "support": { "issues": "https://github.com/utopia-php/logger/issues", - "source": "https://github.com/utopia-php/logger/tree/0.6.1" + "source": "https://github.com/utopia-php/logger/tree/0.6.2" }, - "time": "2024-09-20T14:02:12+00:00" + "time": "2024-10-14T16:02:49+00:00" }, { "name": "utopia-php/messaging", @@ -3847,6 +3847,7 @@ "issues": "https://github.com/phpbench/dom/issues", "source": "https://github.com/phpbench/dom/tree/0.3.3" }, + "abandoned": true, "time": "2023-03-06T23:46:57+00:00" }, { @@ -4194,16 +4195,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.32.0", + "version": "1.33.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4" + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6ca22b154efdd9e3c68c56f5d94670920a1c19a4", - "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/82a311fd3690fb2bf7b64d5c98f912b3dd746140", + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140", "shasum": "" }, "require": { @@ -4235,9 +4236,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.32.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.33.0" }, - "time": "2024-09-26T07:23:32+00:00" + "time": "2024-10-13T11:25:22+00:00" }, { "name": "phpunit/php-code-coverage", @@ -7002,9 +7003,18 @@ "time": "2024-03-07T20:33:40+00:00" } ], - "aliases": [], + "aliases": [ + { + "package": "utopia-php/database", + "version": "dev-0.53.x-max-query-size", + "alias": "0.53.6", + "alias_normalized": "0.53.6.0" + } + ], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "utopia-php/database": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -7028,5 +7038,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php index a3555c08dd..7dc1bb6800 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php @@ -66,7 +66,7 @@ class Base extends Queries new Limit(), new Offset(), new Cursor(), - new Filter($attributes), + new Filter($attributes, APP_DATABASE_QUERY_VALUES), new Order($attributes), ]; From 20800d760bc0d0bc76c4482e4d190748707acef2 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 15 Oct 2024 15:56:12 +0300 Subject: [PATCH 066/101] Set to 500 --- app/init.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/init.php b/app/init.php index dcf9a21f56..e3c9cb374a 100644 --- a/app/init.php +++ b/app/init.php @@ -130,7 +130,7 @@ const APP_DATABASE_ATTRIBUTE_INT_RANGE = 'intRange'; const APP_DATABASE_ATTRIBUTE_FLOAT_RANGE = 'floatRange'; const APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH = 1_073_741_824; // 2^32 bits / 4 bits per char const APP_DATABASE_TIMEOUT_MILLISECONDS = 15_000; -const APP_DATABASE_QUERY_VALUES = 100; +const APP_DATABASE_QUERY_VALUES = 500; const APP_STORAGE_UPLOADS = '/storage/uploads'; const APP_STORAGE_FUNCTIONS = '/storage/functions'; const APP_STORAGE_BUILDS = '/storage/builds'; From c9e2be0e08d63f88c0064d78be099fffd8c25479 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 15 Oct 2024 16:35:26 +0300 Subject: [PATCH 067/101] Fix failing test --- tests/e2e/Services/Databases/DatabasesBase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index 04f2dbd8c8..39ba3b04df 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -2096,7 +2096,7 @@ trait DatabasesBase */ $conditions = []; - for ($i = 0; $i < 101; $i++) { + for ($i = 0; $i < APP_DATABASE_QUERY_VALUES + 1; $i++) { $conditions[] = $i; } From 7a64987da72180464bdcf5f7dfa6415d86e027ff Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 15 Oct 2024 17:25:10 +0300 Subject: [PATCH 068/101] Fix message --- tests/e2e/Services/Databases/DatabasesBase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index 39ba3b04df..f63865d9a6 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -2109,7 +2109,7 @@ trait DatabasesBase ], ]); $this->assertEquals(400, $documents['headers']['status-code']); - $this->assertEquals('Invalid query: Query on attribute has greater than 100 values: releaseYear', $documents['body']['message']); + $this->assertEquals('Invalid query: Query on attribute has greater than '.APP_DATABASE_QUERY_VALUES.' values: releaseYear', $documents['body']['message']); $value = ''; From 1b93c78c5f7f9d3892426f26e2aeb6947bc08ea1 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Tue, 15 Oct 2024 16:34:33 +0000 Subject: [PATCH 069/101] chore: add missing indexes --- app/config/collections.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/config/collections.php b/app/config/collections.php index bd9a4550c8..a55ab1abd0 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -4575,6 +4575,20 @@ $consoleCollections = array_merge([ 'lengths' => [Database::LENGTH_KEY], 'orders' => [Database::ORDER_ASC], ], + [ + '$id' => ID::custom('_key_pingCount'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['pingCount'], + 'lengths' => [], + 'orders' => [], + ], + [ + '$id' => ID::custom('_key_pingedAt'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['pingedAt'], + 'lengths' => [], + 'orders' => [], + ] ], ], From 0bbfcd3888e27dfd3cc26e8415bdf12c191be686 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Tue, 15 Oct 2024 16:37:01 +0000 Subject: [PATCH 070/101] chore: update dependencies --- composer.lock | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/composer.lock b/composer.lock index 7213e1ecd5..afac75fa5e 100644 --- a/composer.lock +++ b/composer.lock @@ -2070,16 +2070,16 @@ }, { "name": "utopia-php/logger", - "version": "0.6.1", + "version": "0.6.2", "source": { "type": "git", "url": "https://github.com/utopia-php/logger.git", - "reference": "7e8ff512c6f04577aba1df67c7b9628971946f9c" + "reference": "25b5bd2ad8bb51292f76332faa7034644fd0941d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/logger/zipball/7e8ff512c6f04577aba1df67c7b9628971946f9c", - "reference": "7e8ff512c6f04577aba1df67c7b9628971946f9c", + "url": "https://api.github.com/repos/utopia-php/logger/zipball/25b5bd2ad8bb51292f76332faa7034644fd0941d", + "reference": "25b5bd2ad8bb51292f76332faa7034644fd0941d", "shasum": "" }, "require": { @@ -2118,9 +2118,9 @@ ], "support": { "issues": "https://github.com/utopia-php/logger/issues", - "source": "https://github.com/utopia-php/logger/tree/0.6.1" + "source": "https://github.com/utopia-php/logger/tree/0.6.2" }, - "time": "2024-09-20T14:02:12+00:00" + "time": "2024-10-14T16:02:49+00:00" }, { "name": "utopia-php/messaging", @@ -3847,6 +3847,7 @@ "issues": "https://github.com/phpbench/dom/issues", "source": "https://github.com/phpbench/dom/tree/0.3.3" }, + "abandoned": true, "time": "2023-03-06T23:46:57+00:00" }, { @@ -4194,16 +4195,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.32.0", + "version": "1.33.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4" + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6ca22b154efdd9e3c68c56f5d94670920a1c19a4", - "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/82a311fd3690fb2bf7b64d5c98f912b3dd746140", + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140", "shasum": "" }, "require": { @@ -4235,9 +4236,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.32.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.33.0" }, - "time": "2024-09-26T07:23:32+00:00" + "time": "2024-10-13T11:25:22+00:00" }, { "name": "phpunit/php-code-coverage", @@ -7028,5 +7029,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } From 481c0b9d7aa73970ad27e33287d29a4953871b1c Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 16 Oct 2024 21:22:36 +1300 Subject: [PATCH 071/101] Update database --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 813e9feff2..dd5472a0fa 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "utopia-php/cache": "0.10.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.53.6", + "utopia-php/database": "0.53.8", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", diff --git a/composer.lock b/composer.lock index afac75fa5e..867030f289 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "5fef87b7ea4352869a502d1570bd43e7", + "content-hash": "18505aa5baca1170e7cbdbb2a355b173", "packages": [ { "name": "adhocore/jwt", @@ -1724,16 +1724,16 @@ }, { "name": "utopia-php/database", - "version": "0.53.6", + "version": "0.53.8", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "feddc8e808eaea9a11c65cca3f01683def422f52" + "reference": "f4f9297d633b9f8407c6261535549bfd6024a468" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/feddc8e808eaea9a11c65cca3f01683def422f52", - "reference": "feddc8e808eaea9a11c65cca3f01683def422f52", + "url": "https://api.github.com/repos/utopia-php/database/zipball/f4f9297d633b9f8407c6261535549bfd6024a468", + "reference": "f4f9297d633b9f8407c6261535549bfd6024a468", "shasum": "" }, "require": { @@ -1774,9 +1774,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.53.6" + "source": "https://github.com/utopia-php/database/tree/0.53.8" }, - "time": "2024-10-08T02:18:46+00:00" + "time": "2024-10-16T08:16:33+00:00" }, { "name": "utopia-php/domains", From 16d65f32b3214be695557a93be9da816e6365caf Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 16 Oct 2024 21:35:32 +1300 Subject: [PATCH 072/101] Review fixes --- app/init.php | 8 +++--- composer.json | 2 +- composer.lock | 27 +++++++------------ .../Database/Validator/Queries/Base.php | 2 +- .../e2e/Services/Databases/DatabasesBase.php | 4 +-- 5 files changed, 17 insertions(+), 26 deletions(-) diff --git a/app/init.php b/app/init.php index e3c9cb374a..eb149adc04 100644 --- a/app/init.php +++ b/app/init.php @@ -130,7 +130,7 @@ const APP_DATABASE_ATTRIBUTE_INT_RANGE = 'intRange'; const APP_DATABASE_ATTRIBUTE_FLOAT_RANGE = 'floatRange'; const APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH = 1_073_741_824; // 2^32 bits / 4 bits per char const APP_DATABASE_TIMEOUT_MILLISECONDS = 15_000; -const APP_DATABASE_QUERY_VALUES = 500; +const APP_DATABASE_QUERY_MAX_VALUES = 500; const APP_STORAGE_UPLOADS = '/storage/uploads'; const APP_STORAGE_FUNCTIONS = '/storage/functions'; const APP_STORAGE_BUILDS = '/storage/builds'; @@ -1400,7 +1400,7 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForConsole, ->setMetadata('host', \gethostname()) ->setMetadata('project', $project->getId()) ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS) - ->setMaxQueryValues(APP_DATABASE_QUERY_VALUES); + ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES); try { $dsn = new DSN($project->getAttribute('database')); @@ -1437,7 +1437,7 @@ App::setResource('dbForConsole', function (Group $pools, Cache $cache) { ->setMetadata('host', \gethostname()) ->setMetadata('project', 'console') ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS) - ->setMaxQueryValues(APP_DATABASE_QUERY_VALUES); + ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES); return $database; }, ['pools', 'cache']); @@ -1462,7 +1462,7 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, ->setMetadata('host', \gethostname()) ->setMetadata('project', $project->getId()) ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS) - ->setMaxQueryValues(APP_DATABASE_QUERY_VALUES); + ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES); if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $database diff --git a/composer.json b/composer.json index e12fc531ec..dd5472a0fa 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "utopia-php/cache": "0.10.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "dev-0.53.x-max-query-size as 0.53.6", + "utopia-php/database": "0.53.8", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", diff --git a/composer.lock b/composer.lock index 6f9b7eb6cd..867030f289 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "59f5ddee3c1259ce3f63762f2f064cc6", + "content-hash": "18505aa5baca1170e7cbdbb2a355b173", "packages": [ { "name": "adhocore/jwt", @@ -1724,16 +1724,16 @@ }, { "name": "utopia-php/database", - "version": "dev-0.53.x-max-query-size", + "version": "0.53.8", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "7c15e2ba06e82d7daeb4edbf43564b435324a7d4" + "reference": "f4f9297d633b9f8407c6261535549bfd6024a468" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/7c15e2ba06e82d7daeb4edbf43564b435324a7d4", - "reference": "7c15e2ba06e82d7daeb4edbf43564b435324a7d4", + "url": "https://api.github.com/repos/utopia-php/database/zipball/f4f9297d633b9f8407c6261535549bfd6024a468", + "reference": "f4f9297d633b9f8407c6261535549bfd6024a468", "shasum": "" }, "require": { @@ -1774,9 +1774,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.53.x-max-query-size" + "source": "https://github.com/utopia-php/database/tree/0.53.8" }, - "time": "2024-10-15T12:20:07+00:00" + "time": "2024-10-16T08:16:33+00:00" }, { "name": "utopia-php/domains", @@ -7003,18 +7003,9 @@ "time": "2024-03-07T20:33:40+00:00" } ], - "aliases": [ - { - "package": "utopia-php/database", - "version": "dev-0.53.x-max-query-size", - "alias": "0.53.6", - "alias_normalized": "0.53.6.0" - } - ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "utopia-php/database": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php index 7dc1bb6800..af5d59ddfd 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php @@ -66,7 +66,7 @@ class Base extends Queries new Limit(), new Offset(), new Cursor(), - new Filter($attributes, APP_DATABASE_QUERY_VALUES), + new Filter($attributes, APP_DATABASE_QUERY_MAX_VALUES), new Order($attributes), ]; diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index f63865d9a6..6d8622276e 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -2096,7 +2096,7 @@ trait DatabasesBase */ $conditions = []; - for ($i = 0; $i < APP_DATABASE_QUERY_VALUES + 1; $i++) { + for ($i = 0; $i < APP_DATABASE_QUERY_MAX_VALUES + 1; $i++) { $conditions[] = $i; } @@ -2109,7 +2109,7 @@ trait DatabasesBase ], ]); $this->assertEquals(400, $documents['headers']['status-code']); - $this->assertEquals('Invalid query: Query on attribute has greater than '.APP_DATABASE_QUERY_VALUES.' values: releaseYear', $documents['body']['message']); + $this->assertEquals('Invalid query: Query on attribute has greater than '.APP_DATABASE_QUERY_MAX_VALUES.' values: releaseYear', $documents['body']['message']); $value = ''; From 45c8584903e36b37022a6b35fb165c0a96913d57 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 16 Oct 2024 21:36:18 +1300 Subject: [PATCH 073/101] Update migration --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 867030f289..9079512e60 100644 --- a/composer.lock +++ b/composer.lock @@ -2175,16 +2175,16 @@ }, { "name": "utopia-php/migration", - "version": "0.6.8", + "version": "0.6.9", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "c3740de52c1b616aa7f054d0fadb9207895b5279" + "reference": "ce97cdf2ca82e7cec78e2ed484ef2c71ebe8744b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/c3740de52c1b616aa7f054d0fadb9207895b5279", - "reference": "c3740de52c1b616aa7f054d0fadb9207895b5279", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/ce97cdf2ca82e7cec78e2ed484ef2c71ebe8744b", + "reference": "ce97cdf2ca82e7cec78e2ed484ef2c71ebe8744b", "shasum": "" }, "require": { @@ -2225,9 +2225,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/0.6.8" + "source": "https://github.com/utopia-php/migration/tree/0.6.9" }, - "time": "2024-10-10T08:09:19+00:00" + "time": "2024-10-16T08:33:21+00:00" }, { "name": "utopia-php/mongo", From a7f1cc128a4516d70f778fafbdb62c1bfeddd3a7 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 17 Oct 2024 18:41:24 +1300 Subject: [PATCH 074/101] Validate cursor queries to avoid getDocument type error --- app/controllers/api/account.php | 7 +++++++ app/controllers/api/databases.php | 23 +++++++++++++++++++++++ app/controllers/api/functions.php | 19 +++++++++++++++++++ app/controllers/api/messaging.php | 26 ++++++++++++++++++++++++++ app/controllers/api/migrations.php | 7 +++++++ app/controllers/api/projects.php | 7 +++++++ app/controllers/api/proxy.php | 7 +++++++ app/controllers/api/storage.php | 13 +++++++++++++ app/controllers/api/teams.php | 14 ++++++++++++++ app/controllers/api/users.php | 18 ++++++++++++++++++ app/controllers/api/vcs.php | 7 +++++++ 11 files changed, 148 insertions(+) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 64441fee5c..cb71818df3 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -42,6 +42,7 @@ use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Queries; +use Utopia\Database\Validator\Query\Cursor; use Utopia\Database\Validator\Query\Limit; use Utopia\Database\Validator\Query\Offset; use Utopia\Database\Validator\UID; @@ -4486,6 +4487,12 @@ App::get('/v1/account/identities') $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ + + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $identityId = $cursor->getValue(); $cursorDocument = $dbForProject->getDocument('identities', $identityId); diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 2784d7f57b..a44d5dab6d 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -547,6 +547,13 @@ App::get('/v1/databases') }); $cursor = reset($cursor); if ($cursor) { + /** @var Query $cursor */ + + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $databaseId = $cursor->getValue(); $cursorDocument = $dbForProject->getDocument('databases', $databaseId); @@ -879,6 +886,12 @@ App::get('/v1/databases/:databaseId/collections') $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ + + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $collectionId = $cursor->getValue(); $cursorDocument = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId); @@ -1789,6 +1802,11 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') $cursor = \reset($cursor); if ($cursor) { + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $attributeId = $cursor->getValue(); $cursorDocument = Authorization::skip(fn () => $dbForProject->find('attributes', [ Query::equal('collectionInternalId', [$collection->getInternalId()]), @@ -2668,6 +2686,11 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes') $cursor = reset($cursor); if ($cursor) { + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $indexId = $cursor->getValue(); $cursorDocument = Authorization::skip(fn () => $dbForProject->find('indexes', [ Query::equal('collectionInternalId', [$collection->getInternalId()]), diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index f1e8d82a9b..c3051ef476 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -37,6 +37,7 @@ use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Datetime as DatetimeValidator; +use Utopia\Database\Validator\Query\Cursor; use Utopia\Database\Validator\Roles; use Utopia\Database\Validator\UID; use Utopia\Storage\Device; @@ -431,6 +432,12 @@ App::get('/v1/functions') $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ + + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $functionId = $cursor->getValue(); $cursorDocument = $dbForProject->getDocument('functions', $functionId); @@ -1407,6 +1414,12 @@ App::get('/v1/functions/:functionId/deployments') $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); @@ -2153,6 +2166,12 @@ App::get('/v1/functions/:functionId/executions') $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ + + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $executionId = $cursor->getValue(); $cursorDocument = $dbForProject->getDocument('executions', $executionId); diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 7da0348a8f..c68ba91297 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -32,6 +32,7 @@ use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Datetime as DatetimeValidator; use Utopia\Database\Validator\Queries; +use Utopia\Database\Validator\Query\Cursor; use Utopia\Database\Validator\Query\Limit; use Utopia\Database\Validator\Query\Offset; use Utopia\Database\Validator\Roles; @@ -866,6 +867,11 @@ App::get('/v1/messaging/providers') $cursor = reset($cursor); if ($cursor) { + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $providerId = $cursor->getValue(); $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId)); @@ -1998,6 +2004,11 @@ App::get('/v1/messaging/topics') $cursor = reset($cursor); if ($cursor) { + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $topicId = $cursor->getValue(); $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId)); @@ -2352,6 +2363,11 @@ App::get('/v1/messaging/topics/:topicId/subscribers') $cursor = reset($cursor); if ($cursor) { + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $subscriberId = $cursor->getValue(); $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('subscribers', $subscriberId)); @@ -3048,6 +3064,11 @@ App::get('/v1/messaging/messages') $cursor = reset($cursor); if ($cursor) { + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $messageId = $cursor->getValue(); $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('messages', $messageId)); @@ -3202,6 +3223,11 @@ App::get('/v1/messaging/messages/:messageId/targets') $cursor = reset($cursor); if ($cursor) { + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $targetId = $cursor->getValue(); $cursorDocument = $dbForProject->getDocument('targets', $targetId); diff --git a/app/controllers/api/migrations.php b/app/controllers/api/migrations.php index bb89d4a26f..a4880cef86 100644 --- a/app/controllers/api/migrations.php +++ b/app/controllers/api/migrations.php @@ -16,6 +16,7 @@ use Utopia\Database\Document; use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; +use Utopia\Database\Validator\Query\Cursor; use Utopia\Database\Validator\UID; use Utopia\Migration\Sources\Appwrite; use Utopia\Migration\Sources\Firebase; @@ -409,6 +410,12 @@ App::get('/v1/migrations') $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ + + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $migrationId = $cursor->getValue(); $cursorDocument = $dbForProject->getDocument('migrations', $migrationId); diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 934793410b..3bfa416bd8 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -31,6 +31,7 @@ use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Datetime as DatetimeValidator; +use Utopia\Database\Validator\Query\Cursor; use Utopia\Database\Validator\UID; use Utopia\Domains\Validator\PublicDomain; use Utopia\DSN\DSN; @@ -279,6 +280,12 @@ App::get('/v1/projects') $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ + + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $projectId = $cursor->getValue(); $cursorDocument = $dbForConsole->getDocument('projects', $projectId); diff --git a/app/controllers/api/proxy.php b/app/controllers/api/proxy.php index 84484a7209..984a9fb974 100644 --- a/app/controllers/api/proxy.php +++ b/app/controllers/api/proxy.php @@ -13,6 +13,7 @@ use Utopia\Database\Document; use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; +use Utopia\Database\Validator\Query\Cursor; use Utopia\Database\Validator\UID; use Utopia\Domains\Domain; use Utopia\Logger\Log; @@ -185,6 +186,12 @@ App::get('/v1/proxy/rules') $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ + + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $ruleId = $cursor->getValue(); $cursorDocument = $dbForConsole->getDocument('rules', $ruleId); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 4e30832a67..c3d57e5470 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -24,6 +24,7 @@ use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Permissions; +use Utopia\Database\Validator\Query\Cursor; use Utopia\Database\Validator\UID; use Utopia\Image\Image; use Utopia\Storage\Compression\Algorithms\GZIP; @@ -178,6 +179,12 @@ App::get('/v1/storage/buckets') $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ + + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $bucketId = $cursor->getValue(); $cursorDocument = $dbForProject->getDocument('buckets', $bucketId); @@ -744,6 +751,12 @@ App::get('/v1/storage/buckets/:bucketId/files') $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ + + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $fileId = $cursor->getValue(); if ($fileSecurity && !$valid) { diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 146b5d5f81..f9abaeeb44 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -34,6 +34,7 @@ use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Key; use Utopia\Database\Validator\Queries; +use Utopia\Database\Validator\Query\Cursor; use Utopia\Database\Validator\Query\Limit; use Utopia\Database\Validator\Query\Offset; use Utopia\Database\Validator\UID; @@ -170,6 +171,12 @@ App::get('/v1/teams') $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ + + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $teamId = $cursor->getValue(); $cursorDocument = $dbForProject->getDocument('teams', $teamId); @@ -751,6 +758,13 @@ App::get('/v1/teams/:teamId/memberships') $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ + + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + + $membershipId = $cursor->getValue(); $cursorDocument = $dbForProject->getDocument('memberships', $membershipId); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 571df4fdb2..f0378ed0e3 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -36,6 +36,7 @@ use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Queries; +use Utopia\Database\Validator\Query\Cursor; use Utopia\Database\Validator\Query\Limit; use Utopia\Database\Validator\Query\Offset; use Utopia\Database\Validator\UID; @@ -576,6 +577,12 @@ App::get('/v1/users') $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ + + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $userId = $cursor->getValue(); $cursorDocument = $dbForProject->getDocument('users', $userId); @@ -886,6 +893,11 @@ App::get('/v1/users/:userId/targets') $cursor = reset($cursor); if ($cursor) { + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $targetId = $cursor->getValue(); $cursorDocument = $dbForProject->getDocument('targets', $targetId); @@ -938,6 +950,12 @@ App::get('/v1/users/identities') $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ + + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $identityId = $cursor->getValue(); $cursorDocument = $dbForProject->getDocument('identities', $identityId); diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index f3381490ec..e79eb67936 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -20,6 +20,7 @@ use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; +use Utopia\Database\Validator\Query\Cursor; use Utopia\Detector\Adapter\Bun; use Utopia\Detector\Adapter\CPP; use Utopia\Detector\Adapter\Dart; @@ -1069,6 +1070,12 @@ App::get('/v1/vcs/installations') $cursor = reset($cursor); if ($cursor) { /** @var Query $cursor */ + + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $installationId = $cursor->getValue(); $cursorDocument = $dbForConsole->getDocument('installations', $installationId); From ea2192a83cb1b890f2ad4e44bcf456e1833df812 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 17 Oct 2024 18:51:26 +1300 Subject: [PATCH 075/101] Force CI --- app/controllers/api/databases.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index a44d5dab6d..454ab89de3 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -3123,6 +3123,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); } + $documentId = $cursor->getValue(); $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId)); From b32cb30737c749f47f64e7dadd68fc74be1cb887 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 17 Oct 2024 18:51:41 +1300 Subject: [PATCH 076/101] Force CI --- app/controllers/api/databases.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 454ab89de3..473f09cb7c 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -3123,7 +3123,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); } - + $documentId = $cursor->getValue(); $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId)); From 4cd951ae414d8898c9b45954205e0efe65aff9c2 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Thu, 17 Oct 2024 19:35:17 +0530 Subject: [PATCH 077/101] Add tests for project variables --- .../Projects/ProjectsConsoleClientTest.php | 269 ++++++++++++++++++ 1 file changed, 269 insertions(+) diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 05893be3a8..8f022d7e0e 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -3793,4 +3793,273 @@ class ProjectsConsoleClientTest extends Scope return $data; } + + /** + * @depends testCreateProject + */ + public function testCreateProjectVariable(array $data) + { + /** + * Test for SUCCESS + */ + $variable = $this->client->call(Client::METHOD_POST, '/project/variables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders()), [ + 'key' => 'APP_TEST', + 'value' => 'TESTINGVALUE' + ]); + + $this->assertEquals(201, $variable['headers']['status-code']); + $variableId = $variable['body']['$id']; + + /** + * Test for FAILURE + */ + // Test for duplicate key + $variable = $this->client->call(Client::METHOD_POST, '/project/variables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders()), [ + 'key' => 'APP_TEST', + 'value' => 'ANOTHERTESTINGVALUE' + ]); + + $this->assertEquals(409, $variable['headers']['status-code']); + + // Test for invalid key + $variable = $this->client->call(Client::METHOD_POST, '/project/variables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders()), [ + 'key' => str_repeat("A", 256), + 'value' => 'TESTINGVALUE' + ]); + + $this->assertEquals(400, $variable['headers']['status-code']); + + // Test for invalid value + $variable = $this->client->call(Client::METHOD_POST, '/project/variables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders()), [ + 'key' => 'LONGKEY', + 'value' => str_repeat("#", 8193), + ]); + + $this->assertEquals(400, $variable['headers']['status-code']); + + return array_merge( + $data, + [ + 'variableId' => $variableId, + ] + ); + } + + /** + * @depends testCreateProjectVariable + */ + public function testListVariables(array $data) + { + /** + * Test for SUCCESS + */ + + $response = $this->client->call(Client::METHOD_GET, '/project/variables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(1, sizeof($response['body']['variables'])); + $this->assertEquals(1, $response['body']['total']); + $this->assertEquals("APP_TEST", $response['body']['variables'][0]['key']); + $this->assertEquals("TESTINGVALUE", $response['body']['variables'][0]['value']); + + return $data; + } + + /** + * @depends testListVariables + */ + public function testGetVariable(array $data) + { + /** + * Test for SUCCESS + */ + $response = $this->client->call(Client::METHOD_GET, '/project/variables/' . $data['variableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals("APP_TEST", $response['body']['key']); + $this->assertEquals("TESTINGVALUE", $response['body']['value']); + + /** + * Test for FAILURE + */ + + $response = $this->client->call(Client::METHOD_GET, '/project/variables/NON_EXISTING_VARIABLE', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + return $data; + } + + /** + * @depends testGetVariable + */ + public function testUpdateVariable(array $data) + { + /** + * Test for SUCCESS + */ + + $response = $this->client->call(Client::METHOD_PUT, '/project/variables/' . $data['variableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders()), [ + 'key' => 'APP_TEST_UPDATE', + 'value' => 'TESTINGVALUEUPDATED' + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals("APP_TEST_UPDATE", $response['body']['key']); + $this->assertEquals("TESTINGVALUEUPDATED", $response['body']['value']); + + $variable = $this->client->call(Client::METHOD_GET, '/project/variables/' . $data['variableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders())); + + $this->assertEquals(200, $variable['headers']['status-code']); + $this->assertEquals("APP_TEST_UPDATE", $variable['body']['key']); + $this->assertEquals("TESTINGVALUEUPDATED", $variable['body']['value']); + + $response = $this->client->call(Client::METHOD_PUT, '/project/variables/' . $data['variableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders()), [ + 'key' => 'APP_TEST_UPDATE_2', + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals("APP_TEST_UPDATE_2", $response['body']['key']); + $this->assertEquals("TESTINGVALUEUPDATED", $response['body']['value']); + + $variable = $this->client->call(Client::METHOD_GET, '/project/variables/' . $data['variableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders())); + + $this->assertEquals(200, $variable['headers']['status-code']); + $this->assertEquals("APP_TEST_UPDATE_2", $variable['body']['key']); + $this->assertEquals("TESTINGVALUEUPDATED", $variable['body']['value']); + + /** + * Test for FAILURE + */ + + $response = $this->client->call(Client::METHOD_PUT, '/project/variables/' . $data['variableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders())); + + $this->assertEquals(400, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_PUT, '/project/variables/' . $data['variableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders()), [ + 'value' => 'TESTINGVALUEUPDATED_2' + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + $longKey = str_repeat("A", 256); + $response = $this->client->call(Client::METHOD_PUT, '/project/variables/' . $data['variableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders()), [ + 'key' => $longKey, + 'value' => 'TESTINGVALUEUPDATED' + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + $longValue = str_repeat("#", 8193); + $response = $this->client->call(Client::METHOD_PUT, '/project/variables/' . $data['variableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders()), [ + 'key' => 'APP_TEST_UPDATE', + 'value' => $longValue + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + return $data; + } + + /** + * @depends testUpdateVariable + */ + public function testDeleteVariable(array $data) + { + /** + * Test for SUCCESS + */ + + $response = $this->client->call(Client::METHOD_DELETE, '/project/variables/' . $data['variableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders())); + + $this->assertEquals(204, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, '/project/variables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(0, sizeof($response['body']['variables'])); + $this->assertEquals(0, $response['body']['total']); + + /** + * Test for FAILURE + */ + + $response = $this->client->call(Client::METHOD_DELETE, '/project/variables/NON_EXISTING_VARIABLE', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders())); + + $this->assertEquals(404, $response['headers']['status-code']); + + return $data; + } } From 5a74f351dc5cb2d6787df2ab5fafefb7c8e5a2e0 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Fri, 18 Oct 2024 00:07:44 +0530 Subject: [PATCH 078/101] Add tests to update non-existent var --- .../Projects/ProjectsConsoleClientTest.php | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 8f022d7e0e..9e2039077e 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -3950,27 +3950,15 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals("APP_TEST_UPDATE", $variable['body']['key']); $this->assertEquals("TESTINGVALUEUPDATED", $variable['body']['value']); - $response = $this->client->call(Client::METHOD_PUT, '/project/variables/' . $data['variableId'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $data['projectId'], - 'x-appwrite-mode' => 'admin', - ], $this->getHeaders()), [ - 'key' => 'APP_TEST_UPDATE_2', - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals("APP_TEST_UPDATE_2", $response['body']['key']); - $this->assertEquals("TESTINGVALUEUPDATED", $response['body']['value']); - - $variable = $this->client->call(Client::METHOD_GET, '/project/variables/' . $data['variableId'], array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/project/variables', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $data['projectId'], 'x-appwrite-mode' => 'admin', ], $this->getHeaders())); - $this->assertEquals(200, $variable['headers']['status-code']); - $this->assertEquals("APP_TEST_UPDATE_2", $variable['body']['key']); - $this->assertEquals("TESTINGVALUEUPDATED", $variable['body']['value']); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(1, sizeof($response['body']['variables'])); + $this->assertEquals("APP_TEST_UPDATE", $response['body']['variables'][0]['key']); /** * Test for FAILURE @@ -4018,6 +4006,17 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(400, $response['headers']['status-code']); + $response = $this->client->call(Client::METHOD_PUT, '/project/variables/NON_EXISTING_VARIABLE', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders()), [ + 'key' => 'APP_TEST_UPDATE', + 'value' => 'TESTINGVALUEUPDATED' + ]); + + $this->assertEquals(404, $response['headers']['status-code']); + return $data; } From 73918b137e2c7c1712a5738b8dafdf9278224a16 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Fri, 18 Oct 2024 15:48:59 +0530 Subject: [PATCH 079/101] Replace sizeof with assertCount --- tests/e2e/Services/Projects/ProjectsConsoleClientTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 9e2039077e..7b0847126c 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -3877,7 +3877,7 @@ class ProjectsConsoleClientTest extends Scope ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(1, sizeof($response['body']['variables'])); + $this->assertCount(1, $response['body']['variables']); $this->assertEquals(1, $response['body']['total']); $this->assertEquals("APP_TEST", $response['body']['variables'][0]['key']); $this->assertEquals("TESTINGVALUE", $response['body']['variables'][0]['value']); @@ -3957,7 +3957,7 @@ class ProjectsConsoleClientTest extends Scope ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(1, sizeof($response['body']['variables'])); + $this->assertCount(1, $response['body']['variables']); $this->assertEquals("APP_TEST_UPDATE", $response['body']['variables'][0]['key']); /** @@ -4044,7 +4044,7 @@ class ProjectsConsoleClientTest extends Scope ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(0, sizeof($response['body']['variables'])); + $this->assertCount(0, $response['body']['variables']); $this->assertEquals(0, $response['body']['total']); /** From 0f11c249b9450f8611581fec156b3e9692f9e803 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 22 Oct 2024 14:07:32 +1300 Subject: [PATCH 080/101] Update messaging for FCM response fix --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 9079512e60..5c7d48373e 100644 --- a/composer.lock +++ b/composer.lock @@ -2124,16 +2124,16 @@ }, { "name": "utopia-php/messaging", - "version": "0.12.1", + "version": "0.12.2", "source": { "type": "git", "url": "https://github.com/utopia-php/messaging.git", - "reference": "b9dfafb5efc1d12cbee01d03dc98853ef026e35b" + "reference": "f6790fba1fcee12163d51c65d2c226a7856295d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/messaging/zipball/b9dfafb5efc1d12cbee01d03dc98853ef026e35b", - "reference": "b9dfafb5efc1d12cbee01d03dc98853ef026e35b", + "url": "https://api.github.com/repos/utopia-php/messaging/zipball/f6790fba1fcee12163d51c65d2c226a7856295d9", + "reference": "f6790fba1fcee12163d51c65d2c226a7856295d9", "shasum": "" }, "require": { @@ -2169,9 +2169,9 @@ ], "support": { "issues": "https://github.com/utopia-php/messaging/issues", - "source": "https://github.com/utopia-php/messaging/tree/0.12.1" + "source": "https://github.com/utopia-php/messaging/tree/0.12.2" }, - "time": "2024-10-09T08:17:07+00:00" + "time": "2024-10-22T01:02:20+00:00" }, { "name": "utopia-php/migration", From 99710e7b745af2d76b73c36b06daa05551aa5fca Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 22 Oct 2024 14:08:16 +1300 Subject: [PATCH 081/101] Force expired false if updating target identifier --- app/controllers/api/account.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index cb71818df3..de787358a7 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -4384,7 +4384,9 @@ App::put('/v1/account/targets/:targetId/push') } if ($identifier) { - $target->setAttribute('identifier', $identifier); + $target + ->setAttribute('identifier', $identifier) + ->setAtttibute('expired', false); } $detector = new Detector($request->getUserAgent()); From 8ab1600417ca607c38019599c7d9075f35cf99ac Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 22 Oct 2024 14:35:04 +1300 Subject: [PATCH 082/101] Update users API --- app/controllers/api/users.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index f0378ed0e3..3dffded6f6 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -1503,7 +1503,9 @@ App::patch('/v1/users/:userId/targets/:targetId') throw new Exception(Exception::PROVIDER_INCORRECT_TYPE); } - $target->setAttribute('identifier', $identifier); + $target + ->setAttribute('identifier', $identifier) + ->setAttribute('expired', false); } if ($providerId) { @@ -1517,8 +1519,9 @@ App::patch('/v1/users/:userId/targets/:targetId') throw new Exception(Exception::PROVIDER_INCORRECT_TYPE); } - $target->setAttribute('providerId', $provider->getId()); - $target->setAttribute('providerInternalId', $provider->getInternalId()); + $target + ->setAttribute('providerId', $provider->getId()) + ->setAttribute('providerInternalId', $provider->getInternalId()); } if ($name) { From 0a22292d1b81501aaa342565ee67dc2df0746929 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 22 Oct 2024 14:35:11 +1300 Subject: [PATCH 083/101] Update tests --- .../Account/AccountCustomClientTest.php | 44 +++++++++++++++++++ tests/e2e/Services/Users/UsersBase.php | 1 + 2 files changed, 45 insertions(+) diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 244f84b161..88dcf0e1e6 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -2695,4 +2695,48 @@ class AccountCustomClientTest extends Scope return $data; } + + public function testCreatePushTarget(): void + { + $response = $this->client->call(Client::METHOD_POST, '/account/push/targets', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'targetId' => ID::unique(), + 'identifier' => 'test-identifier', + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals('test-identifier', $response['body']['identifier']); + } + + public function testUpdatePushTarget(): void + { + $response = $this->client->call(Client::METHOD_POST, '/account/push/targets', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'targetId' => ID::unique(), + 'identifier' => 'test-identifier', + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals('test-identifier', $response['body']['identifier']); + + $response = $this->client->call(Client::METHOD_PATCH, '/account/push/targets/' . $response['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'identifier' => 'test-identifier-updated', + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('test-identifier-updated', $response['body']['identifier']); + $this->assertEquals(false, $response['body']['expired']); + } } diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index d9105e0790..693a935a69 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -1498,6 +1498,7 @@ trait UsersBase ]); $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals('random-email1@mail.org', $response['body']['identifier']); + $this->assertEquals(false, $response['body']['expired']); return $response['body']; } From 1ed3dee13569a05c46090088640562026b447cff Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 22 Oct 2024 14:54:34 +1300 Subject: [PATCH 084/101] Fix tests --- app/controllers/api/account.php | 4 ++-- src/Appwrite/Utopia/Response/Model/Target.php | 8 ++++++- .../Account/AccountCustomClientTest.php | 21 ++++++++----------- tests/e2e/Services/Users/UsersBase.php | 1 + 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index de787358a7..45970cd29d 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -4315,7 +4315,7 @@ App::post('/v1/account/targets/push') $device = $detector->getDevice(); - $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret); + $sessionId = Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret); $session = $dbForProject->getDocument('sessions', $sessionId); try { @@ -4386,7 +4386,7 @@ App::put('/v1/account/targets/:targetId/push') if ($identifier) { $target ->setAttribute('identifier', $identifier) - ->setAtttibute('expired', false); + ->setAttribute('expired', false); } $detector = new Detector($request->getUserAgent()); diff --git a/src/Appwrite/Utopia/Response/Model/Target.php b/src/Appwrite/Utopia/Response/Model/Target.php index d180b6c4c4..530749e006 100644 --- a/src/Appwrite/Utopia/Response/Model/Target.php +++ b/src/Appwrite/Utopia/Response/Model/Target.php @@ -32,7 +32,7 @@ class Target extends Model 'type' => self::TYPE_STRING, 'description' => 'Target Name.', 'default' => '', - 'example' => 'Aegon apple token', + 'example' => 'Apple iPhone 12', ]) ->addRule('userId', [ 'type' => self::TYPE_STRING, @@ -58,6 +58,12 @@ class Target extends Model 'description' => 'The target identifier.', 'default' => '', 'example' => 'token', + ]) + ->addRule('expired', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Is the target expired.', + 'default' => false, + 'example' => false, ]); } diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 88dcf0e1e6..cca27cc3be 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -2698,11 +2698,10 @@ class AccountCustomClientTest extends Scope public function testCreatePushTarget(): void { - $response = $this->client->call(Client::METHOD_POST, '/account/push/targets', [ + $response = $this->client->call(Client::METHOD_POST, '/account/targets/push', \array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ + 'x-appwrite-project' => $this->getProject()['$id'] + ], $this->getHeaders()), [ 'targetId' => ID::unique(), 'identifier' => 'test-identifier', ]); @@ -2714,24 +2713,22 @@ class AccountCustomClientTest extends Scope public function testUpdatePushTarget(): void { - $response = $this->client->call(Client::METHOD_POST, '/account/push/targets', [ + $response = $this->client->call(Client::METHOD_POST, '/account/targets/push', \array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ + ], $this->getHeaders()), [ 'targetId' => ID::unique(), - 'identifier' => 'test-identifier', + 'identifier' => 'test-identifier-2', ]); $this->assertEquals(201, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals('test-identifier', $response['body']['identifier']); + $this->assertEquals('test-identifier-2', $response['body']['identifier']); - $response = $this->client->call(Client::METHOD_PATCH, '/account/push/targets/' . $response['body']['$id'], [ + $response = $this->client->call(Client::METHOD_PUT, '/account/targets/'. $response['body']['$id'] .'/push', \array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ + ], $this->getHeaders()), [ 'identifier' => 'test-identifier-updated', ]); diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index 693a935a69..bd0a8ef937 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -1511,6 +1511,7 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); + $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals(3, \count($response['body']['targets'])); } From 58658f335a2cb6e0f8ec4bfd7de09c926f9f812e Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 22 Oct 2024 19:18:48 +1300 Subject: [PATCH 085/101] Fix health cert test --- tests/e2e/Services/Health/HealthCustomServerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Health/HealthCustomServerTest.php b/tests/e2e/Services/Health/HealthCustomServerTest.php index 8360af542e..4199eeb927 100644 --- a/tests/e2e/Services/Health/HealthCustomServerTest.php +++ b/tests/e2e/Services/Health/HealthCustomServerTest.php @@ -455,7 +455,7 @@ class HealthCustomServerTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals('/CN=www.google.com', $response['body']['name']); $this->assertEquals('www.google.com', $response['body']['subjectSN']); - $this->assertStringContainsString('Google Trust Services', $response['body']['issuerOrganisation']); + $this->assertEquals('Let\'s Encrypt', $response['body']['issuerOrganisation']); $this->assertIsInt($response['body']['validFrom']); $this->assertIsInt($response['body']['validTo']); From f203800dd95295945b01e040f3d8a9240d696582 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 22 Oct 2024 19:52:38 +1300 Subject: [PATCH 086/101] Fix tests --- tests/e2e/Services/Health/HealthCustomServerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Health/HealthCustomServerTest.php b/tests/e2e/Services/Health/HealthCustomServerTest.php index 4199eeb927..96751bd45b 100644 --- a/tests/e2e/Services/Health/HealthCustomServerTest.php +++ b/tests/e2e/Services/Health/HealthCustomServerTest.php @@ -455,7 +455,7 @@ class HealthCustomServerTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals('/CN=www.google.com', $response['body']['name']); $this->assertEquals('www.google.com', $response['body']['subjectSN']); - $this->assertEquals('Let\'s Encrypt', $response['body']['issuerOrganisation']); + $this->assertContains($response['body']['issuerOrganisation'], ['Let\'s Encrypt', 'Google Trust Services']); $this->assertIsInt($response['body']['validFrom']); $this->assertIsInt($response['body']['validTo']); From 6403048acd7b222c8dabad137f2dccb276fcc3dc Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 22 Oct 2024 20:00:24 +1300 Subject: [PATCH 087/101] Fix tests --- tests/e2e/Services/Health/HealthCustomServerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Health/HealthCustomServerTest.php b/tests/e2e/Services/Health/HealthCustomServerTest.php index 96751bd45b..9d6a04abe6 100644 --- a/tests/e2e/Services/Health/HealthCustomServerTest.php +++ b/tests/e2e/Services/Health/HealthCustomServerTest.php @@ -467,7 +467,7 @@ class HealthCustomServerTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals('/CN=appwrite.io', $response['body']['name']); $this->assertEquals('appwrite.io', $response['body']['subjectSN']); - $this->assertEquals("Let's Encrypt", $response['body']['issuerOrganisation']); + $this->assertContains($response['body']['issuerOrganisation'], ['Let\'s Encrypt', 'Google Trust Services']); $this->assertIsInt($response['body']['validFrom']); $this->assertIsInt($response['body']['validTo']); From a9b648b2a9515c5e20ed8ab4f1b1230c61ae11e8 Mon Sep 17 00:00:00 2001 From: Fabian Gruber Date: Tue, 22 Oct 2024 11:38:05 +0200 Subject: [PATCH 088/101] chore: replace 'Expires' with 'Cache-Control: private' header to avoid CDN caching --- app/controllers/api/avatars.php | 18 +++++++++--------- app/controllers/api/functions.php | 2 +- app/controllers/api/locale.php | 2 +- app/controllers/api/storage.php | 8 ++++---- app/controllers/shared/api.php | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index fcff3e4179..dadd9da5e3 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -55,7 +55,7 @@ $avatarCallback = function (string $type, string $code, int $width, int $height, $output = (empty($output)) ? $type : $output; $data = $image->output($output, $quality); $response - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT') + ->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days ->setContentType('image/png') ->file($data); unset($image); @@ -275,7 +275,7 @@ App::get('/v1/avatars/image') $data = $image->output($output, $quality); $response - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT') + ->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days ->setContentType('image/png') ->file($data); unset($image); @@ -409,7 +409,7 @@ App::get('/v1/avatars/favicon') throw new Exception(Exception::AVATAR_ICON_NOT_FOUND, 'Favicon not found'); } $response - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT') + ->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days ->setContentType('image/x-icon') ->file($data); } @@ -420,7 +420,7 @@ App::get('/v1/avatars/favicon') $data = $image->output($output, $quality); $response - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT') + ->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days ->setContentType('image/png') ->file($data); unset($image); @@ -461,7 +461,7 @@ App::get('/v1/avatars/qr') $image->crop((int) $size, (int) $size); $response - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache + ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days ->setContentType('image/png') ->send($image->output('png', 9)); }); @@ -544,7 +544,7 @@ App::get('/v1/avatars/initials') $image->compositeImage($punch, Imagick::COMPOSITE_COPYOPACITY, 0, 0); $response - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache + ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days ->setContentType('image/png') ->file($image->getImageBlob()); }); @@ -751,7 +751,7 @@ App::get('/v1/cards/cloud') } $response - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache + ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days ->setContentType('image/png') ->file($baseImage->getImageBlob()); }); @@ -829,7 +829,7 @@ App::get('/v1/cards/cloud-back') } $response - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache + ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days ->setContentType('image/png') ->file($baseImage->getImageBlob()); }); @@ -1219,7 +1219,7 @@ App::get('/v1/cards/cloud-og') } $response - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache + ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days ->setContentType('image/png') ->file($baseImage->getImageBlob()); }); diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index c3051ef476..396d2048f2 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -994,7 +994,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download') $response ->setContentType('application/gzip') - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache + ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days ->addHeader('X-Peak', \memory_get_peak_usage()) ->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"'); diff --git a/app/controllers/api/locale.php b/app/controllers/api/locale.php index 2917bc8416..1f042d2239 100644 --- a/app/controllers/api/locale.php +++ b/app/controllers/api/locale.php @@ -63,7 +63,7 @@ App::get('/v1/locale') $response ->addHeader('Cache-Control', 'public, max-age=' . $time) - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache + ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days ; $response->dynamic(new Document($output), Response::MODEL_LOCALE); }); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index c3d57e5470..5261f9c89a 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -999,7 +999,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') $contentType = (\array_key_exists($output, $outputs)) ? $outputs[$output] : $outputs['jpg']; $response - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT') + ->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days ->setContentType($contentType) ->file($data) ; @@ -1062,7 +1062,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') $response ->setContentType($file->getAttribute('mimeType')) - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache + ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days ->addHeader('X-Peak', \memory_get_peak_usage()) ->addHeader('Content-Disposition', 'attachment; filename="' . $file->getAttribute('name', '') . '"') ; @@ -1212,7 +1212,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') ->addHeader('Content-Security-Policy', 'script-src none;') ->addHeader('X-Content-Type-Options', 'nosniff') ->addHeader('Content-Disposition', 'inline; filename="' . $file->getAttribute('name', '') . '"') - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache + ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days ->addHeader('X-Peak', \memory_get_peak_usage()) ; @@ -1366,7 +1366,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push') ->addHeader('Content-Security-Policy', 'script-src none;') ->addHeader('X-Content-Type-Options', 'nosniff') ->addHeader('Content-Disposition', 'inline; filename="' . $file->getAttribute('name', '') . '"') - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache + ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days ->addHeader('X-Peak', \memory_get_peak_usage()); $size = $file->getAttribute('sizeOriginal', 0); diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index f0d896c95a..7a5de8af19 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -508,7 +508,7 @@ App::init() } $response - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $timestamp) . ' GMT') + ->addHeader('Cache-Control', sprintf('private, max-age=%d', $timestamp)) ->addHeader('X-Appwrite-Cache', 'hit') ->setContentType($cacheLog->getAttribute('mimeType')) ->send($data); @@ -516,7 +516,7 @@ App::init() $response ->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate') ->addHeader('Pragma', 'no-cache') - ->addHeader('Expires', 0) + ->addHeader('Expires', '0') ->addHeader('X-Appwrite-Cache', 'miss') ; } From de8d0d440615a5950716a961fdb93a55c5317b23 Mon Sep 17 00:00:00 2001 From: Fabian Gruber Date: Wed, 23 Oct 2024 11:57:50 +0200 Subject: [PATCH 089/101] fix: allow '.wav' as 'audio/x-wav' as well --- app/config/storage/mimes.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/config/storage/mimes.php b/app/config/storage/mimes.php index df325b37e9..26aaf8e1ff 100644 --- a/app/config/storage/mimes.php +++ b/app/config/storage/mimes.php @@ -33,6 +33,7 @@ return [ 'audio/ogg', // Ogg Vorbis RFC 5334 'audio/vorbis', // Vorbis RFC 5215 'audio/vnd.wav', // wav RFC 2361 + 'audio/x-wav', // php reads .wav as this - https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types 'audio/aac', //AAC audio 'audio/x-hx-aac-adts', // AAC audio From 62096dc008161bb859cda06c92414f4fa43ea1fb Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 23 Oct 2024 14:22:37 +0100 Subject: [PATCH 090/101] chore: use 1 instead of 0.5 cpu --- app/config/runtimes/specifications.php | 2 +- app/init.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/config/runtimes/specifications.php b/app/config/runtimes/specifications.php index d3625db8a2..86db2708b1 100644 --- a/app/config/runtimes/specifications.php +++ b/app/config/runtimes/specifications.php @@ -6,7 +6,7 @@ return [ Specification::S_05VCPU_512MB => [ 'slug' => Specification::S_05VCPU_512MB, 'memory' => 512, - 'cpus' => 0.5 + 'cpus' => 1 ], Specification::S_1VCPU_512MB => [ 'slug' => Specification::S_1VCPU_512MB, diff --git a/app/init.php b/app/init.php index b4ab772e0e..2162bf8333 100644 --- a/app/init.php +++ b/app/init.php @@ -148,7 +148,7 @@ const APP_SOCIAL_DEV = 'https://dev.to/appwrite'; const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite'; const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1'; const APP_HOSTNAME_INTERNAL = 'appwrite'; -const APP_FUNCTION_SPECIFICATION_DEFAULT = Specification::S_05VCPU_512MB; +const APP_FUNCTION_SPECIFICATION_DEFAULT = Specification::S_1VCPU_512MB; const APP_FUNCTION_CPUS_DEFAULT = 0.5; const APP_FUNCTION_MEMORY_DEFAULT = 512; From 6f7d8d7f62f919f280f86d08da62c98f00e085c1 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:16:15 +0100 Subject: [PATCH 091/101] chore: add comment --- app/config/runtimes/specifications.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/runtimes/specifications.php b/app/config/runtimes/specifications.php index 86db2708b1..68880f4d4e 100644 --- a/app/config/runtimes/specifications.php +++ b/app/config/runtimes/specifications.php @@ -6,7 +6,7 @@ return [ Specification::S_05VCPU_512MB => [ 'slug' => Specification::S_05VCPU_512MB, 'memory' => 512, - 'cpus' => 1 + 'cpus' => 1 // TODO: revert this, it's a temporary change to test function performance. ], Specification::S_1VCPU_512MB => [ 'slug' => Specification::S_1VCPU_512MB, From 51fe894a3f769db631f995f65dd52a52d7624d85 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:20:45 +0100 Subject: [PATCH 092/101] Revert "Merge pull request #8757 from ItzNotABug/update-walter-references" This reverts commit a49c3a33f0fd831423afa7a0b53df2c5d709fc2b, reversing changes made to 3887ba69d7d5bc2a1d07885f1f1329c58dad9cf6. --- app/assets/security/10k-common-passwords | 4 ---- app/controllers/api/avatars.php | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/assets/security/10k-common-passwords b/app/assets/security/10k-common-passwords index 6902dfb81a..487a4faf54 100644 --- a/app/assets/security/10k-common-passwords +++ b/app/assets/security/10k-common-passwords @@ -412,7 +412,6 @@ august sammy cool brian -brien platinum jake bronco @@ -1936,7 +1935,6 @@ panama lucy buffy brianna -brienna welcome1 vette blue22 @@ -3055,7 +3053,6 @@ randall abstr napster brian1 -brien1 bogart high hitler @@ -4126,7 +4123,6 @@ truman cubbies nitram briana -briena ebony kings warner diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index f4839fdf0d..dadd9da5e3 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -609,9 +609,9 @@ App::get('/v1/cards/cloud') $isPlatinum = $user->getInternalId() % 100 === 0; } else { - $name = $mock === 'normal-long' ? 'Sir First Walter O\'Brien Junior' : 'Walter O\'Brien'; + $name = $mock === 'normal-long' ? 'Sir First Walter O\'Brian Junior' : 'Walter O\'Brian'; $createdAt = new \DateTime('now'); - $githubName = $mock === 'normal-no-github' ? '' : ($mock === 'normal-long' ? 'sir-first-walterobrien-junior' : 'walterobrien'); + $githubName = $mock === 'normal-no-github' ? '' : ($mock === 'normal-long' ? 'sir-first-walterobrian-junior' : 'walterobrian'); $isHero = $mock === 'hero'; $isContributor = $mock === 'contributor'; $isEmployee = \str_starts_with($mock, 'employee'); @@ -900,9 +900,9 @@ App::get('/v1/cards/cloud-og') } else { $bgVariation = \str_ends_with($mock, '-bg2') ? '2' : (\str_ends_with($mock, '-bg3') ? '3' : '1'); $cardVariation = \str_ends_with($mock, '-right') ? '2' : (\str_ends_with($mock, '-middle') ? '3' : '1'); - $name = \str_starts_with($mock, 'normal-long') ? 'Sir First Walter O\'Brien Junior' : 'Walter O\'Brien'; + $name = \str_starts_with($mock, 'normal-long') ? 'Sir First Walter O\'Brian Junior' : 'Walter O\'Brian'; $createdAt = new \DateTime('now'); - $githubName = $mock === 'normal-no-github' ? '' : (\str_starts_with($mock, 'normal-long') ? 'sir-first-walterobrien-junior' : 'walterobrien'); + $githubName = $mock === 'normal-no-github' ? '' : (\str_starts_with($mock, 'normal-long') ? 'sir-first-walterobrian-junior' : 'walterobrian'); $isHero = \str_starts_with($mock, 'hero'); $isContributor = \str_starts_with($mock, 'contributor'); $isEmployee = \str_starts_with($mock, 'employee'); From 225e2c21c37744e113435ec2f106a00e07be811e Mon Sep 17 00:00:00 2001 From: Fabian Gruber Date: Thu, 17 Oct 2024 13:32:42 +0200 Subject: [PATCH 093/101] feat: allow blocking based on resource attributes --- app/config/errors.php | 5 ++++ app/controllers/api/databases.php | 48 +++++++++++++++++++++++++++++++ app/controllers/api/functions.php | 29 +++++++++++++++++++ app/controllers/api/messaging.php | 46 +++++++++++++++++++++++++++++ app/controllers/api/storage.php | 16 +++++++++++ app/controllers/general.php | 26 +++++++++++------ app/init.php | 7 ++++- src/Appwrite/Extend/Exception.php | 1 + 8 files changed, 168 insertions(+), 10 deletions(-) diff --git a/app/config/errors.php b/app/config/errors.php index fc79599b12..3afec4faaf 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -24,6 +24,11 @@ return [ 'description' => 'Access to this API is forbidden.', 'code' => 401, ], + Exception::GENERAL_RESOURCE_BLOCKED => [ + 'name' => Exception::GENERAL_RESOURCE_BLOCKED, + 'description' => 'Access to this resource is blocked.', + 'code' => 401, + ], Exception::GENERAL_UNKNOWN_ORIGIN => [ 'name' => Exception::GENERAL_UNKNOWN_ORIGIN, 'description' => 'The request originated from an unknown origin. If you trust this domain, please list it as a trusted platform in the Appwrite console.', diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 473f09cb7c..942f886417 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -439,6 +439,7 @@ App::post('/v1/databases') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].create') ->label('scope', 'databases.write') + ->label('resourceType', 'databases') ->label('audits.event', 'database.create') ->label('audits.resource', 'database/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -516,6 +517,7 @@ App::get('/v1/databases') ->desc('List databases') ->groups(['api', 'database']) ->label('scope', 'databases.read') + ->label('resourceType', 'databases') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'list') @@ -576,6 +578,7 @@ App::get('/v1/databases/:databaseId') ->desc('Get database') ->groups(['api', 'database']) ->label('scope', 'databases.read') + ->label('resourceType', 'databases') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'get') @@ -601,6 +604,7 @@ App::get('/v1/databases/:databaseId/logs') ->desc('List database logs') ->groups(['api', 'database']) ->label('scope', 'databases.read') + ->label('resourceType', 'databases') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'listLogs') @@ -692,6 +696,7 @@ App::put('/v1/databases/:databaseId') ->desc('Update database') ->groups(['api', 'database', 'schema']) ->label('scope', 'databases.write') + ->label('resourceType', 'databases') ->label('event', 'databases.[databaseId].update') ->label('audits.event', 'database.update') ->label('audits.resource', 'database/{response.$id}') @@ -730,6 +735,7 @@ App::delete('/v1/databases/:databaseId') ->desc('Delete database') ->groups(['api', 'database', 'schema']) ->label('scope', 'databases.write') + ->label('resourceType', 'databases') ->label('event', 'databases.[databaseId].delete') ->label('audits.event', 'database.delete') ->label('audits.resource', 'database/{request.databaseId}') @@ -779,6 +785,7 @@ App::post('/v1/databases/:databaseId/collections') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].create') ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('audits.event', 'collection.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -846,6 +853,7 @@ App::get('/v1/databases/:databaseId/collections') ->desc('List collections') ->groups(['api', 'database']) ->label('scope', 'collections.read') + ->label('resourceType', 'databases') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'listCollections') @@ -915,6 +923,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId') ->desc('Get collection') ->groups(['api', 'database']) ->label('scope', 'collections.read') + ->label('resourceType', 'databases') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'getCollection') @@ -949,6 +958,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs') ->desc('List collection logs') ->groups(['api', 'database']) ->label('scope', 'collections.read') + ->label('resourceType', 'databases') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'listCollectionLogs') @@ -1049,6 +1059,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId') ->desc('Update collection') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('event', 'databases.[databaseId].collections.[collectionId].update') ->label('audits.event', 'collection.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -1112,6 +1123,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId') ->desc('Delete collection') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('event', 'databases.[databaseId].collections.[collectionId].delete') ->label('audits.event', 'collection.delete') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -1168,6 +1180,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -1225,6 +1238,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email' ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1267,6 +1281,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1314,6 +1329,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1356,6 +1372,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1398,6 +1415,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1469,6 +1487,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float' ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1543,6 +1562,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolea ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1584,6 +1604,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1628,6 +1649,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1755,6 +1777,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') ->desc('List attributes') ->groups(['api', 'database']) ->label('scope', 'collections.read') + ->label('resourceType', 'databases') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'listAttributes') @@ -1838,6 +1861,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key') ->desc('Get attribute') ->groups(['api', 'database']) ->label('scope', 'collections.read') + ->label('resourceType', 'databases') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'getAttribute') @@ -1912,6 +1936,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/strin ->desc('Update string attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -1955,6 +1980,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/email ->desc('Update email attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -1996,6 +2022,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/enum/ ->desc('Update enum attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -2039,6 +2066,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/ip/:k ->desc('Update IP address attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -2080,6 +2108,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/url/: ->desc('Update URL attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -2121,6 +2150,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/integ ->desc('Update integer attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -2172,6 +2202,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/float ->desc('Update float attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -2223,6 +2254,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/boole ->desc('Update boolean attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -2263,6 +2295,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datet ->desc('Update dateTime attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -2303,6 +2336,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/ ->desc('Update relationship attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -2360,6 +2394,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key ->desc('Delete attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.delete') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -2474,6 +2509,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].indexes.[indexId].create') ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('audits.event', 'index.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -2643,6 +2679,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes') ->desc('List indexes') ->groups(['api', 'database']) ->label('scope', 'collections.read') + ->label('resourceType', 'databases') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'listIndexes') @@ -2718,6 +2755,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') ->desc('Get index') ->groups(['api', 'database']) ->label('scope', 'collections.read') + ->label('resourceType', 'databases') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'getIndex') @@ -2757,6 +2795,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') ->desc('Delete index') ->groups(['api', 'database']) ->label('scope', 'collections.write') + ->label('resourceType', 'databases') ->label('event', 'databases.[databaseId].collections.[collectionId].indexes.[indexId].update') ->label('audits.event', 'index.delete') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -2822,6 +2861,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].create') ->label('scope', 'documents.write') + ->label('resourceType', 'databases') ->label('audits.event', 'document.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') @@ -3073,6 +3113,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') ->desc('List documents') ->groups(['api', 'database']) ->label('scope', 'documents.read') + ->label('resourceType', 'databases') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'listDocuments') @@ -3234,6 +3275,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->desc('Get document') ->groups(['api', 'database']) ->label('scope', 'documents.read') + ->label('resourceType', 'databases') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'getDocument') @@ -3326,6 +3368,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->desc('List document logs') ->groups(['api', 'database']) ->label('scope', 'documents.read') + ->label('resourceType', 'databases') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'listDocumentLogs') @@ -3431,6 +3474,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].update') ->label('scope', 'documents.write') + ->label('resourceType', 'databases') ->label('audits.event', 'document.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}/document/{response.$id}') ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') @@ -3666,6 +3710,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu ->desc('Delete document') ->groups(['api', 'database']) ->label('scope', 'documents.write') + ->label('resourceType', 'databases') ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].delete') ->label('audits.event', 'document.delete') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}/document/{request.documentId}') @@ -3780,6 +3825,7 @@ App::get('/v1/databases/usage') ->desc('Get databases usage stats') ->groups(['api', 'database', 'usage']) ->label('scope', 'collections.read') + ->label('resourceType', 'databases') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'getUsage') @@ -3861,6 +3907,7 @@ App::get('/v1/databases/:databaseId/usage') ->desc('Get database usage stats') ->groups(['api', 'database', 'usage']) ->label('scope', 'collections.read') + ->label('resourceType', 'databases') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'getDatabaseUsage') @@ -3948,6 +3995,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/usage') ->desc('Get collection usage stats') ->groups(['api', 'database', 'usage']) ->label('scope', 'collections.read') + ->label('resourceType', 'databases') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'getCollectionUsage') diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 396d2048f2..7de443625e 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -138,6 +138,7 @@ App::post('/v1/functions') ->desc('Create function') ->label('scope', 'functions.write') ->label('event', 'functions.[functionId].create') + ->label('resourceType', 'functions') ->label('audits.event', 'function.create') ->label('audits.resource', 'function/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -400,6 +401,7 @@ App::get('/v1/functions') ->groups(['api', 'functions']) ->desc('List functions') ->label('scope', 'functions.read') + ->label('resourceType', 'functions') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'list') @@ -460,6 +462,7 @@ App::get('/v1/functions/runtimes') ->groups(['api', 'functions']) ->desc('List runtimes') ->label('scope', 'functions.read') + ->label('resourceType', 'functions') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'listRuntimes') @@ -493,6 +496,7 @@ App::get('/v1/functions/specifications') ->groups(['api', 'functions']) ->desc('List available function runtime specifications') ->label('scope', 'functions.read') + ->label('resourceType', 'functions') ->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'listSpecifications') @@ -529,6 +533,7 @@ App::get('/v1/functions/:functionId') ->groups(['api', 'functions']) ->desc('Get function') ->label('scope', 'functions.read') + ->label('resourceType', 'functions') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'get') @@ -553,6 +558,7 @@ App::get('/v1/functions/:functionId/usage') ->desc('Get function usage') ->groups(['api', 'functions', 'usage']) ->label('scope', 'functions.read') + ->label('resourceType', 'functions') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'getFunctionUsage') @@ -657,6 +663,7 @@ App::get('/v1/functions/usage') ->desc('Get functions usage') ->groups(['api', 'functions']) ->label('scope', 'functions.read') + ->label('resourceType', 'functions') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'getUsage') @@ -756,6 +763,7 @@ App::put('/v1/functions/:functionId') ->groups(['api', 'functions']) ->desc('Update function') ->label('scope', 'functions.write') + ->label('resourceType', 'functions') ->label('event', 'functions.[functionId].update') ->label('audits.event', 'function.update') ->label('audits.resource', 'function/{response.$id}') @@ -958,6 +966,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download') ->groups(['api', 'functions']) ->desc('Download deployment') ->label('scope', 'functions.read') + ->label('resourceType', 'functions') ->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'getDeploymentDownload') @@ -1043,6 +1052,7 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId') ->groups(['api', 'functions']) ->desc('Update deployment') ->label('scope', 'functions.write') + ->label('resourceType', 'functions') ->label('event', 'functions.[functionId].deployments.[deploymentId].update') ->label('audits.event', 'deployment.update') ->label('audits.resource', 'function/{request.functionId}') @@ -1105,6 +1115,7 @@ App::delete('/v1/functions/:functionId') ->groups(['api', 'functions']) ->desc('Delete function') ->label('scope', 'functions.write') + ->label('resourceType', 'functions') ->label('event', 'functions.[functionId].delete') ->label('audits.event', 'function.delete') ->label('audits.resource', 'function/{request.functionId}') @@ -1152,6 +1163,7 @@ App::post('/v1/functions/:functionId/deployments') ->groups(['api', 'functions']) ->desc('Create deployment') ->label('scope', 'functions.write') + ->label('resourceType', 'functions') ->label('event', 'functions.[functionId].deployments.[deploymentId].create') ->label('audits.event', 'deployment.create') ->label('audits.resource', 'function/{request.functionId}') @@ -1371,6 +1383,7 @@ App::get('/v1/functions/:functionId/deployments') ->groups(['api', 'functions']) ->desc('List deployments') ->label('scope', 'functions.read') + ->label('resourceType', 'functions') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'listDeployments') @@ -1454,6 +1467,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId') ->groups(['api', 'functions']) ->desc('Get deployment') ->label('scope', 'functions.read') + ->label('resourceType', 'functions') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'getDeployment') @@ -1497,6 +1511,7 @@ App::delete('/v1/functions/:functionId/deployments/:deploymentId') ->groups(['api', 'functions']) ->desc('Delete deployment') ->label('scope', 'functions.write') + ->label('resourceType', 'functions') ->label('event', 'functions.[functionId].deployments.[deploymentId].delete') ->label('audits.event', 'deployment.delete') ->label('audits.resource', 'function/{request.functionId}') @@ -1562,6 +1577,7 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/build') ->groups(['api', 'functions']) ->desc('Rebuild deployment') ->label('scope', 'functions.write') + ->label('resourceType', 'functions') ->label('event', 'functions.[functionId].deployments.[deploymentId].update') ->label('audits.event', 'deployment.update') ->label('audits.resource', 'function/{request.functionId}') @@ -1630,6 +1646,7 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId/build') ->groups(['api', 'functions']) ->desc('Cancel deployment') ->label('scope', 'functions.write') + ->label('resourceType', 'functions') ->label('audits.event', 'deployment.update') ->label('audits.resource', 'function/{request.functionId}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -1719,7 +1736,9 @@ App::post('/v1/functions/:functionId/executions') ->groups(['api', 'functions']) ->desc('Create execution') ->label('scope', 'execution.write') + ->label('resourceType', 'functions') ->label('event', 'functions.[functionId].executions.[executionId].create') + ->label('resourceType', 'functions') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'createExecution') @@ -2121,6 +2140,7 @@ App::get('/v1/functions/:functionId/executions') ->groups(['api', 'functions']) ->desc('List executions') ->label('scope', 'execution.read') + ->label('resourceType', 'functions') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'listExecutions') @@ -2208,6 +2228,7 @@ App::get('/v1/functions/:functionId/executions/:executionId') ->groups(['api', 'functions']) ->desc('Get execution') ->label('scope', 'execution.read') + ->label('resourceType', 'functions') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'getExecution') @@ -2255,6 +2276,7 @@ App::delete('/v1/functions/:functionId/executions/:executionId') ->groups(['api', 'functions']) ->desc('Delete execution') ->label('scope', 'execution.write') + ->label('resourceType', 'functions') ->label('event', 'functions.[functionId].executions.[executionId].delete') ->label('audits.event', 'executions.delete') ->label('audits.resource', 'function/{request.functionId}') @@ -2325,6 +2347,7 @@ App::post('/v1/functions/:functionId/variables') ->desc('Create variable') ->groups(['api', 'functions']) ->label('scope', 'functions.write') + ->label('resourceType', 'functions') ->label('audits.event', 'variable.create') ->label('audits.resource', 'function/{request.functionId}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -2389,6 +2412,7 @@ App::get('/v1/functions/:functionId/variables') ->desc('List variables') ->groups(['api', 'functions']) ->label('scope', 'functions.read') + ->label('resourceType', 'functions') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'listVariables') @@ -2416,6 +2440,7 @@ App::get('/v1/functions/:functionId/variables/:variableId') ->desc('Get variable') ->groups(['api', 'functions']) ->label('scope', 'functions.read') + ->label('resourceType', 'functions') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'getVariable') @@ -2455,6 +2480,7 @@ App::put('/v1/functions/:functionId/variables/:variableId') ->desc('Update variable') ->groups(['api', 'functions']) ->label('scope', 'functions.write') + ->label('resourceType', 'functions') ->label('audits.event', 'variable.update') ->label('audits.resource', 'function/{request.functionId}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -2516,6 +2542,7 @@ App::delete('/v1/functions/:functionId/variables/:variableId') ->desc('Delete variable') ->groups(['api', 'functions']) ->label('scope', 'functions.write') + ->label('resourceType', 'functions') ->label('audits.event', 'variable.delete') ->label('audits.resource', 'function/{request.functionId}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -2564,6 +2591,7 @@ App::get('/v1/functions/templates') ->groups(['api']) ->desc('List function templates') ->label('scope', 'public') + ->label('resourceType', 'functions') ->label('sdk.namespace', 'functions') ->label('sdk.method', 'listTemplates') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) @@ -2601,6 +2629,7 @@ App::get('/v1/functions/templates') App::get('/v1/functions/templates/:templateId') ->desc('Get function template') ->label('scope', 'public') + ->label('resourceType', 'functions') ->label('sdk.namespace', 'functions') ->label('sdk.method', 'getTemplate') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index c68ba91297..50fd30420e 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -56,6 +56,7 @@ App::post('/v1/messaging/providers/mailgun') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].create') ->label('scope', 'providers.write') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createMailgunProvider') @@ -143,6 +144,7 @@ App::post('/v1/messaging/providers/sendgrid') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].create') ->label('scope', 'providers.write') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createSendgridProvider') @@ -218,6 +220,7 @@ App::post('/v1/messaging/providers/smtp') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].create') ->label('scope', 'providers.write') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createSmtpProvider') @@ -305,6 +308,7 @@ App::post('/v1/messaging/providers/msg91') ->label('audits.event', 'provider.create') ->label('audits.resource', 'provider/{response.$id}') ->label('scope', 'providers.write') + ->label('resourceType', 'providers') ->label('event', 'providers.[providerId].create') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') @@ -382,6 +386,7 @@ App::post('/v1/messaging/providers/telesign') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].create') ->label('scope', 'providers.write') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createTelesignProvider') @@ -459,6 +464,7 @@ App::post('/v1/messaging/providers/textmagic') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].create') ->label('scope', 'providers.write') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createTextmagicProvider') @@ -536,6 +542,7 @@ App::post('/v1/messaging/providers/twilio') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].create') ->label('scope', 'providers.write') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createTwilioProvider') @@ -613,6 +620,7 @@ App::post('/v1/messaging/providers/vonage') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].create') ->label('scope', 'providers.write') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createVonageProvider') @@ -690,6 +698,7 @@ App::post('/v1/messaging/providers/fcm') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].create') ->label('scope', 'providers.write') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createFcmProvider') @@ -753,6 +762,7 @@ App::post('/v1/messaging/providers/apns') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].create') ->label('scope', 'providers.write') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createApnsProvider') @@ -836,6 +846,7 @@ App::get('/v1/messaging/providers') ->desc('List providers') ->groups(['api', 'messaging']) ->label('scope', 'providers.read') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listProviders') @@ -892,6 +903,7 @@ App::get('/v1/messaging/providers/:providerId/logs') ->desc('List provider logs') ->groups(['api', 'messaging']) ->label('scope', 'providers.read') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listProviderLogs') @@ -980,6 +992,7 @@ App::get('/v1/messaging/providers/:providerId') ->desc('Get provider') ->groups(['api', 'messaging']) ->label('scope', 'providers.read') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'getProvider') @@ -1007,6 +1020,7 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].update') ->label('scope', 'providers.write') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateMailgunProvider') @@ -1113,6 +1127,7 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].update') ->label('scope', 'providers.write') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateSendgridProvider') @@ -1204,6 +1219,7 @@ App::patch('/v1/messaging/providers/smtp/:providerId') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].update') ->label('scope', 'providers.write') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateSmtpProvider') @@ -1326,6 +1342,7 @@ App::patch('/v1/messaging/providers/msg91/:providerId') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].update') ->label('scope', 'providers.write') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateMsg91Provider') @@ -1406,6 +1423,7 @@ App::patch('/v1/messaging/providers/telesign/:providerId') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].update') ->label('scope', 'providers.write') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateTelesignProvider') @@ -1488,6 +1506,7 @@ App::patch('/v1/messaging/providers/textmagic/:providerId') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].update') ->label('scope', 'providers.write') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateTextmagicProvider') @@ -1570,6 +1589,7 @@ App::patch('/v1/messaging/providers/twilio/:providerId') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].update') ->label('scope', 'providers.write') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateTwilioProvider') @@ -1652,6 +1672,7 @@ App::patch('/v1/messaging/providers/vonage/:providerId') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].update') ->label('scope', 'providers.write') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateVonageProvider') @@ -1734,6 +1755,7 @@ App::patch('/v1/messaging/providers/fcm/:providerId') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].update') ->label('scope', 'providers.write') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateFcmProvider') @@ -1803,6 +1825,7 @@ App::patch('/v1/messaging/providers/apns/:providerId') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].update') ->label('scope', 'providers.write') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateApnsProvider') @@ -1898,6 +1921,7 @@ App::delete('/v1/messaging/providers/:providerId') ->label('audits.resource', 'provider/{request.$providerId}') ->label('event', 'providers.[providerId].delete') ->label('scope', 'providers.write') + ->label('resourceType', 'providers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'deleteProvider') @@ -1933,6 +1957,7 @@ App::post('/v1/messaging/topics') ->label('audits.resource', 'topic/{response.$id}') ->label('event', 'topics.[topicId].create') ->label('scope', 'topics.write') + ->label('resourceType', 'topics') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createTopic') @@ -1973,6 +1998,7 @@ App::get('/v1/messaging/topics') ->desc('List topics') ->groups(['api', 'messaging']) ->label('scope', 'topics.read') + ->label('resourceType', 'topics') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listTopics') @@ -2029,6 +2055,7 @@ App::get('/v1/messaging/topics/:topicId/logs') ->desc('List topic logs') ->groups(['api', 'messaging']) ->label('scope', 'topics.read') + ->label('resourceType', 'topics') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listTopicLogs') @@ -2118,6 +2145,7 @@ App::get('/v1/messaging/topics/:topicId') ->desc('Get topic') ->groups(['api', 'messaging']) ->label('scope', 'topics.read') + ->label('resourceType', 'topics') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'getTopic') @@ -2146,6 +2174,7 @@ App::patch('/v1/messaging/topics/:topicId') ->label('audits.resource', 'topic/{response.$id}') ->label('event', 'topics.[topicId].update') ->label('scope', 'topics.write') + ->label('resourceType', 'topics') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateTopic') @@ -2190,6 +2219,7 @@ App::delete('/v1/messaging/topics/:topicId') ->label('audits.resource', 'topic/{request.$topicId}') ->label('event', 'topics.[topicId].delete') ->label('scope', 'topics.write') + ->label('resourceType', 'topics') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'deleteTopic') @@ -2230,6 +2260,7 @@ App::post('/v1/messaging/topics/:topicId/subscribers') ->label('audits.resource', 'subscriber/{response.$id}') ->label('event', 'topics.[topicId].subscribers.[subscriberId].create') ->label('scope', 'subscribers.write') + ->label('resourceType', 'subscribers') ->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createSubscriber') @@ -2323,6 +2354,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers') ->desc('List subscribers') ->groups(['api', 'messaging']) ->label('scope', 'subscribers.read') + ->label('resourceType', 'subscribers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listSubscribers') @@ -2402,6 +2434,7 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs') ->desc('List subscriber logs') ->groups(['api', 'messaging']) ->label('scope', 'subscribers.read') + ->label('resourceType', 'subscribers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listSubscriberLogs') @@ -2491,6 +2524,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId') ->desc('Get subscriber') ->groups(['api', 'messaging']) ->label('scope', 'subscribers.read') + ->label('resourceType', 'subscribers') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'getSubscriber') @@ -2533,6 +2567,7 @@ App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId') ->label('audits.resource', 'subscriber/{request.$subscriberId}') ->label('event', 'topics.[topicId].subscribers.[subscriberId].delete') ->label('scope', 'subscribers.write') + ->label('resourceType', 'subscribers') ->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'deleteSubscriber') @@ -2592,6 +2627,7 @@ App::post('/v1/messaging/messages/email') ->label('audits.resource', 'message/{response.$id}') ->label('event', 'messages.[messageId].create') ->label('scope', 'messages.write') + ->label('resourceType', 'messages') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createEmail') @@ -2744,6 +2780,7 @@ App::post('/v1/messaging/messages/sms') ->label('audits.resource', 'message/{response.$id}') ->label('event', 'messages.[messageId].create') ->label('scope', 'messages.write') + ->label('resourceType', 'messages') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createSms') @@ -2860,6 +2897,7 @@ App::post('/v1/messaging/messages/push') ->label('audits.resource', 'message/{response.$id}') ->label('event', 'messages.[messageId].create') ->label('scope', 'messages.write') + ->label('resourceType', 'messages') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createPush') @@ -3033,6 +3071,7 @@ App::get('/v1/messaging/messages') ->desc('List messages') ->groups(['api', 'messaging']) ->label('scope', 'messages.read') + ->label('resourceType', 'messages') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listMessages') @@ -3089,6 +3128,7 @@ App::get('/v1/messaging/messages/:messageId/logs') ->desc('List message logs') ->groups(['api', 'messaging']) ->label('scope', 'messages.read') + ->label('resourceType', 'messages') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listMessageLogs') @@ -3178,6 +3218,7 @@ App::get('/v1/messaging/messages/:messageId/targets') ->desc('List message targets') ->groups(['api', 'messaging']) ->label('scope', 'messages.read') + ->label('resourceType', 'messages') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listTargets') @@ -3248,6 +3289,7 @@ App::get('/v1/messaging/messages/:messageId') ->desc('Get message') ->groups(['api', 'messaging']) ->label('scope', 'messages.read') + ->label('resourceType', 'messages') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'getMessage') @@ -3275,6 +3317,7 @@ App::patch('/v1/messaging/messages/email/:messageId') ->label('audits.resource', 'message/{response.$id}') ->label('event', 'messages.[messageId].update') ->label('scope', 'messages.write') + ->label('resourceType', 'messages') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateEmail') @@ -3475,6 +3518,7 @@ App::patch('/v1/messaging/messages/sms/:messageId') ->label('audits.resource', 'message/{response.$id}') ->label('event', 'messages.[messageId].update') ->label('scope', 'messages.write') + ->label('resourceType', 'messages') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateSms') @@ -3630,6 +3674,7 @@ App::patch('/v1/messaging/messages/push/:messageId') ->label('audits.resource', 'message/{response.$id}') ->label('event', 'messages.[messageId].update') ->label('scope', 'messages.write') + ->label('resourceType', 'messages') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updatePush') @@ -3868,6 +3913,7 @@ App::delete('/v1/messaging/messages/:messageId') ->label('audits.resource', 'message/{request.messageId}') ->label('event', 'messages.[messageId].delete') ->label('scope', 'messages.write') + ->label('resourceType', 'messages') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'delete') diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 5261f9c89a..afd3c4687a 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -49,6 +49,7 @@ App::post('/v1/storage/buckets') ->desc('Create bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.write') + ->label('resourceType', 'buckets') ->label('event', 'buckets.[bucketId].create') ->label('audits.event', 'bucket.create') ->label('audits.resource', 'bucket/{response.$id}') @@ -147,6 +148,7 @@ App::get('/v1/storage/buckets') ->desc('List buckets') ->groups(['api', 'storage']) ->label('scope', 'buckets.read') + ->label('resourceType', 'buckets') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'listBuckets') @@ -207,6 +209,7 @@ App::get('/v1/storage/buckets/:bucketId') ->desc('Get bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.read') + ->label('resourceType', 'buckets') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'getBucket') @@ -232,6 +235,7 @@ App::put('/v1/storage/buckets/:bucketId') ->desc('Update bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.write') + ->label('resourceType', 'buckets') ->label('event', 'buckets.[bucketId].update') ->label('audits.event', 'bucket.update') ->label('audits.resource', 'bucket/{response.$id}') @@ -299,6 +303,7 @@ App::delete('/v1/storage/buckets/:bucketId') ->desc('Delete bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.write') + ->label('resourceType', 'buckets') ->label('audits.event', 'bucket.delete') ->label('event', 'buckets.[bucketId].delete') ->label('audits.resource', 'bucket/{request.bucketId}') @@ -341,6 +346,7 @@ App::post('/v1/storage/buckets/:bucketId/files') ->desc('Create file') ->groups(['api', 'storage']) ->label('scope', 'files.write') + ->label('resourceType', 'buckets') ->label('audits.event', 'file.create') ->label('event', 'buckets.[bucketId].files.[fileId].create') ->label('audits.resource', 'file/{response.$id}') @@ -702,6 +708,7 @@ App::get('/v1/storage/buckets/:bucketId/files') ->desc('List files') ->groups(['api', 'storage']) ->label('scope', 'files.read') + ->label('resourceType', 'buckets') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'listFiles') @@ -793,6 +800,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId') ->desc('Get file') ->groups(['api', 'storage']) ->label('scope', 'files.read') + ->label('resourceType', 'buckets') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'getFile') @@ -840,6 +848,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') ->desc('Get file preview') ->groups(['api', 'storage']) ->label('scope', 'files.read') + ->label('resourceType', 'buckets') ->label('cache', true) ->label('cache.resourceType', 'bucket/{request.bucketId}') ->label('cache.resource', 'file/{request.fileId}') @@ -1012,6 +1021,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') ->desc('Get file for download') ->groups(['api', 'storage']) ->label('scope', 'files.read') + ->label('resourceType', 'buckets') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'getFileDownload') @@ -1152,6 +1162,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') ->desc('Get file for view') ->groups(['api', 'storage']) ->label('scope', 'files.read') + ->label('resourceType', 'buckets') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'getFileView') @@ -1303,6 +1314,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push') ->desc('Get file for push notification') ->groups(['api', 'storage']) ->label('scope', 'public') + ->label('resourceType', 'buckets') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', '*/*') ->label('sdk.methodType', 'location') @@ -1457,6 +1469,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') ->desc('Update file') ->groups(['api', 'storage']) ->label('scope', 'files.write') + ->label('resourceType', 'buckets') ->label('event', 'buckets.[bucketId].files.[fileId].update') ->label('audits.event', 'file.update') ->label('audits.resource', 'file/{response.$id}') @@ -1561,6 +1574,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') ->desc('Delete file') ->groups(['api', 'storage']) ->label('scope', 'files.write') + ->label('resourceType', 'buckets') ->label('event', 'buckets.[bucketId].files.[fileId].delete') ->label('audits.event', 'file.delete') ->label('audits.resource', 'file/{request.fileId}') @@ -1654,6 +1668,7 @@ App::get('/v1/storage/usage') ->desc('Get storage usage stats') ->groups(['api', 'storage']) ->label('scope', 'files.read') + ->label('resourceType', 'buckets') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'getUsage') @@ -1733,6 +1748,7 @@ App::get('/v1/storage/:bucketId/usage') ->desc('Get bucket usage stats') ->groups(['api', 'storage']) ->label('scope', 'files.read') + ->label('resourceType', 'buckets') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'getBucketUsage') diff --git a/app/controllers/general.php b/app/controllers/general.php index b2a07f06f6..a5f6b38a44 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -46,7 +46,7 @@ Config::setParam('domainVerification', false); Config::setParam('cookieDomain', 'localhost'); Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE); -function router(App $utopia, Database $dbForConsole, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb) +function router(App $utopia, Database $dbForConsole, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked) { $utopia->getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml'); @@ -137,6 +137,10 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo throw new AppwriteException(AppwriteException::FUNCTION_NOT_FOUND); } + if ($isResourceBlocked($project, 'functions', $functionId)) { + throw new AppwriteException(AppwriteException::GENERAL_RESOURCE_BLOCKED); + } + $version = $function->getAttribute('version', 'v2'); $runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []); $spec = Config::getParam('runtime-specifications')[$function->getAttribute('specification', APP_FUNCTION_SPECIFICATION_DEFAULT)]; @@ -457,7 +461,8 @@ App::init() ->inject('queueForEvents') ->inject('queueForCertificates') ->inject('queueForFunctions') - ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, Reader $geodb, Usage $queueForUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions) { + ->inject('isResourceBlocked') + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, Reader $geodb, Usage $queueForUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, callable $isResourceBlocked) { /* * Appwrite Router */ @@ -465,7 +470,7 @@ App::init() $mainDomain = System::getEnv('_APP_DOMAIN', ''); // Only run Router when external domain if ($host !== $mainDomain) { - if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb)) { + if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked)) { return; } } @@ -675,7 +680,8 @@ App::options() ->inject('queueForUsage') ->inject('queueForFunctions') ->inject('geodb') - ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb) { + ->inject('isResourceBlocked') + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked) { /* * Appwrite Router */ @@ -683,7 +689,7 @@ App::options() $mainDomain = System::getEnv('_APP_DOMAIN', ''); // Only run Router when external domain if ($host !== $mainDomain) { - if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb)) { + if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked)) { return; } } @@ -967,7 +973,8 @@ App::get('/robots.txt') ->inject('queueForUsage') ->inject('queueForFunctions') ->inject('geodb') - ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb) { + ->inject('isResourceBlocked') + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked) { $host = $request->getHostname() ?? ''; $mainDomain = System::getEnv('_APP_DOMAIN', ''); @@ -975,7 +982,7 @@ App::get('/robots.txt') $template = new View(__DIR__ . '/../views/general/robots.phtml'); $response->text($template->render(false)); } else { - router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb); + router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked); } }); @@ -993,7 +1000,8 @@ App::get('/humans.txt') ->inject('queueForUsage') ->inject('queueForFunctions') ->inject('geodb') - ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb) { + ->inject('isResourceBlocked') + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked) { $host = $request->getHostname() ?? ''; $mainDomain = System::getEnv('_APP_DOMAIN', ''); @@ -1001,7 +1009,7 @@ App::get('/humans.txt') $template = new View(__DIR__ . '/../views/general/humans.phtml'); $response->text($template->render(false)); } else { - router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb); + router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked); } }); diff --git a/app/init.php b/app/init.php index 337255b6db..50ab123754 100644 --- a/app/init.php +++ b/app/init.php @@ -1771,11 +1771,11 @@ App::setResource('requestTimestamp', function ($request) { } return $requestTimestamp; }, ['request']); + App::setResource('plan', function (array $plan = []) { return []; }); - App::setResource('team', function (Document $project, Database $dbForConsole, App $utopia, Request $request) { $teamInternalId = ''; if ($project->getId() !== 'console') { @@ -1806,3 +1806,8 @@ App::setResource('team', function (Document $project, Database $dbForConsole, Ap } return $team; }, ['project', 'dbForConsole', 'utopia', 'request']); + +App::setResource( + 'isResourceBlocked', + fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false +); diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index d25332126c..54bf6d96ea 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -39,6 +39,7 @@ class Exception extends \Exception public const GENERAL_UNKNOWN = 'general_unknown'; public const GENERAL_MOCK = 'general_mock'; public const GENERAL_ACCESS_FORBIDDEN = 'general_access_forbidden'; + public const GENERAL_RESOURCE_BLOCKED = 'general_resource_blocked'; public const GENERAL_UNKNOWN_ORIGIN = 'general_unknown_origin'; public const GENERAL_API_DISABLED = 'general_api_disabled'; public const GENERAL_SERVICE_DISABLED = 'general_service_disabled'; From 4313162b5e80ff9e62e2b3e77268e2e408330b81 Mon Sep 17 00:00:00 2001 From: Fabian Gruber Date: Fri, 25 Oct 2024 11:58:51 +0200 Subject: [PATCH 094/101] fix(blocks): check if resource is blocked inside functions worker --- app/worker.php | 7 +- src/Appwrite/Platform/Workers/Functions.php | 98 +++++++++++++-------- 2 files changed, 65 insertions(+), 40 deletions(-) diff --git a/app/worker.php b/app/worker.php index 2d59259284..4741afe7ea 100644 --- a/app/worker.php +++ b/app/worker.php @@ -58,7 +58,7 @@ Server::setResource('project', function (Message $message, Database $dbForConsol $payload = $message->getPayload() ?? []; $project = new Document($payload['project'] ?? []); - if ($project->getId() === 'console' || $project->isEmpty() || ! empty($project->getInternalId())) { + if ($project->getId() === 'console') { return $project; } @@ -272,6 +272,11 @@ Server::setResource('deviceForCache', function (Document $project) { return getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId()); }, ['project']); +Server::setResource( + 'isResourceBlocked', + fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false +); + $pools = $register->get('pools'); $platform = new Appwrite(); diff --git a/src/Appwrite/Platform/Workers/Functions.php b/src/Appwrite/Platform/Workers/Functions.php index 7e548f57be..3dc3e65eee 100644 --- a/src/Appwrite/Platform/Workers/Functions.php +++ b/src/Appwrite/Platform/Workers/Functions.php @@ -41,29 +41,33 @@ class Functions extends Action $this ->desc('Functions worker') ->groups(['functions']) + ->inject('project') ->inject('message') ->inject('dbForProject') ->inject('queueForFunctions') ->inject('queueForEvents') ->inject('queueForUsage') ->inject('log') - ->callback(fn (Message $message, Database $dbForProject, Func $queueForFunctions, Event $queueForEvents, Usage $queueForUsage, Log $log) => $this->action($message, $dbForProject, $queueForFunctions, $queueForEvents, $queueForUsage, $log)); + ->inject('isResourceBlocked') + ->callback(fn (Document $project, Message $message, Database $dbForProject, Func $queueForFunctions, Event $queueForEvents, Usage $queueForUsage, Log $log, callable $isResourceBlocked) => $this->action($project, $message, $dbForProject, $queueForFunctions, $queueForEvents, $queueForUsage, $log, $isResourceBlocked)); } /** + * @param Document $project * @param Message $message * @param Database $dbForProject * @param Func $queueForFunctions * @param Event $queueForEvents * @param Usage $queueForUsage * @param Log $log + * @param callable $isResourceBlocked * @return void * @throws Authorization * @throws Structure * @throws \Utopia\Database\Exception * @throws Conflict */ - public function action(Message $message, Database $dbForProject, Func $queueForFunctions, Event $queueForEvents, Usage $queueForUsage, Log $log): void + public function action(Document $project, Message $message, Database $dbForProject, Func $queueForFunctions, Event $queueForEvents, Usage $queueForUsage, Log $log, callable $isResourceBlocked): void { $payload = $message->getPayload() ?? []; @@ -73,55 +77,21 @@ class Functions extends Action $type = $payload['type'] ?? ''; - // Short-term solution to offhand write operation from API contianer + // Short-term solution to offhand write operation from API container if ($type === Func::TYPE_ASYNC_WRITE) { $execution = new Document($payload['execution'] ?? []); - $execution = $dbForProject->createDocument('executions', $execution); + $dbForProject->createDocument('executions', $execution); return; } - $events = $payload['events'] ?? []; - $data = $payload['body'] ?? ''; $eventData = $payload['payload'] ?? ''; - $project = new Document($payload['project'] ?? []); - $function = new Document($payload['function'] ?? []); - $functionId = $payload['functionId'] ?? ''; $user = new Document($payload['user'] ?? []); - $userId = $payload['userId'] ?? ''; - $method = $payload['method'] ?? 'POST'; - $headers = $payload['headers'] ?? []; - $path = $payload['path'] ?? '/'; - $jwt = $payload['jwt'] ?? ''; - - if ($user->isEmpty() && !empty($userId)) { - $user = $dbForProject->getDocument('users', $userId); - } - - if (empty($jwt) && !$user->isEmpty()) { - $jwtExpiry = $function->getAttribute('timeout', 900); - $jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0); - $jwt = $jwtObj->encode([ - 'userId' => $user->getId(), - ]); - } - - if ($project->getId() === 'console') { - return; - } - - if ($function->isEmpty() && !empty($functionId)) { - $function = $dbForProject->getDocument('functions', $functionId); - } - - $log->addTag('functionId', $function->getId()); - $log->addTag('projectId', $project->getId()); - $log->addTag('type', $type); + $events = $payload['events'] ?? []; if (!empty($events)) { $limit = 30; $sum = 30; $offset = 0; - /** @var Document[] $functions */ while ($sum >= $limit) { $functions = $dbForProject->find('functions', [ Query::limit($limit), @@ -138,6 +108,12 @@ class Functions extends Action if (!array_intersect($events, $function->getAttribute('events', []))) { continue; } + + if ($isResourceBlocked($project, 'functions', $function->getId())) { + Console::log('Function ' . $function->getId() . ' is blocked, skipping execution.'); + continue; + } + Console::success('Iterating function: ' . $function->getAttribute('name')); $this->execute( @@ -168,6 +144,50 @@ class Functions extends Action return; } + $data = $payload['body'] ?? ''; + $function = new Document($payload['function'] ?? []); + $functionId = $payload['functionId'] ?? ''; + $userId = $payload['userId'] ?? ''; + $method = $payload['method'] ?? 'POST'; + $headers = $payload['headers'] ?? []; + $path = $payload['path'] ?? '/'; + $jwt = $payload['jwt'] ?? ''; + + if ($user->isEmpty() && !empty($userId)) { + $user = $dbForProject->getDocument('users', $userId); + } + + if (empty($jwt) && !$user->isEmpty()) { + $jwtExpiry = $function->getAttribute('timeout', 900); + $jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0); + $jwt = $jwtObj->encode([ + 'userId' => $user->getId(), + ]); + } + + if ($project->getId() === 'console') { + return; + } + + if ($function->isEmpty() && !empty($functionId)) { + $function = $dbForProject->getDocument('functions', $functionId); + } + + // $function still empty, we can't execute this + if ($function->isEmpty()) { + Console::warning('Got empty function without functionId.'); + return; + } + + if ($isResourceBlocked($project, 'functions', $function->getId())) { + Console::log('Function ' . $function->getId() . ' is blocked, skipping execution.'); + return; + } + + $log->addTag('functionId', $function->getId()); + $log->addTag('projectId', $project->getId()); + $log->addTag('type', $type); + /** * Handle Schedule and HTTP execution. */ From 299664d18ba02f8c3d59c3e29d25b301d2ee4f5d Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 29 Oct 2024 15:23:46 +1300 Subject: [PATCH 095/101] Fix missing allow attribute --- src/Appwrite/Utopia/Database/Validator/Queries/Migrations.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Migrations.php b/src/Appwrite/Utopia/Database/Validator/Queries/Migrations.php index 6b9e9e6d32..436a95534b 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Migrations.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Migrations.php @@ -8,6 +8,7 @@ class Migrations extends Base 'status', 'stage', 'source', + 'destination', 'resources', 'statusCounters', 'resourceData', From 64dd4bdce1a90c80250d43753898a38fdab743f5 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 29 Oct 2024 15:56:08 +1300 Subject: [PATCH 096/101] Add response model rule --- src/Appwrite/Utopia/Response/Model/Migration.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Utopia/Response/Model/Migration.php b/src/Appwrite/Utopia/Response/Model/Migration.php index bb13c2cb04..f70dc37027 100644 --- a/src/Appwrite/Utopia/Response/Model/Migration.php +++ b/src/Appwrite/Utopia/Response/Model/Migration.php @@ -46,9 +46,15 @@ class Migration extends Model 'default' => '', 'example' => 'Appwrite', ]) + ->addRule('destination', [ + 'type' => self::TYPE_STRING, + 'description' => 'A string containing the type of destination of the migration.', + 'default' => 'Appwrite', + 'example' => 'Appwrite', + ]) ->addRule('resources', [ 'type' => self::TYPE_STRING, - 'description' => 'Resources to migration.', + 'description' => 'Resources to migrate.', 'default' => [], 'example' => ['user'], 'array' => true From 242130a752bad5427a452871fc9eae0d7f6b157c Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:33:21 +0000 Subject: [PATCH 097/101] fix: otpSession.hello --- app/config/locale/templates/email-otp.tpl | 2 +- app/config/locale/translations/en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/config/locale/templates/email-otp.tpl b/app/config/locale/templates/email-otp.tpl index 9552185f84..84802c1603 100644 --- a/app/config/locale/templates/email-otp.tpl +++ b/app/config/locale/templates/email-otp.tpl @@ -1,4 +1,4 @@ -

{{hello}},

+

{{hello}}

{{description}}

diff --git a/app/config/locale/translations/en.json b/app/config/locale/translations/en.json index 937ab298de..860671946f 100644 --- a/app/config/locale/translations/en.json +++ b/app/config/locale/translations/en.json @@ -28,7 +28,7 @@ "emails.sessionAlert.thanks": "Thanks,", "emails.sessionAlert.signature": "{{project}} team", "emails.otpSession.subject": "OTP for {{project}} Login", - "emails.otpSession.hello": "Hello {{user}}", + "emails.otpSession.hello": "Hello {{user}},", "emails.otpSession.description": "Enter the following verification code when prompted to securely sign in to your {{b}}{{project}}{{/b}} account. This code will expire in 15 minutes.", "emails.otpSession.clientInfo": "This sign in was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the sign in, you can safely ignore this email.", "emails.otpSession.securityPhrase": "Security phrase for this email is {{b}}{{phrase}}{{/b}}. You can trust this email if this phrase matches the phrase shown during sign in.", From be67725fea3b7d8c34e869da75564a0ef3f9e69f Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 29 Oct 2024 12:43:08 +0000 Subject: [PATCH 098/101] feat: localise commas --- .../locale/templates/email-inner-base.tpl | 4 +-- .../locale/templates/email-magic-url.tpl | 2 +- .../locale/templates/email-mfa-challenge.tpl | 2 +- .../locale/templates/email-session-alert.tpl | 2 +- app/config/locale/translations/af.json | 16 ++++++------ app/config/locale/translations/ar-ma.json | 20 +++++++------- app/config/locale/translations/ar.json | 18 ++++++------- app/config/locale/translations/as.json | 22 ++++++++-------- app/config/locale/translations/az.json | 18 ++++++------- app/config/locale/translations/be.json | 22 ++++++++-------- app/config/locale/translations/bg.json | 18 ++++++------- app/config/locale/translations/bh.json | 24 ++++++++--------- app/config/locale/translations/bn.json | 18 ++++++------- app/config/locale/translations/bs.json | 18 ++++++------- app/config/locale/translations/ca.json | 18 ++++++------- app/config/locale/translations/cs.json | 18 ++++++------- app/config/locale/translations/da.json | 18 ++++++------- app/config/locale/translations/de.json | 18 ++++++------- app/config/locale/translations/el.json | 18 ++++++------- app/config/locale/translations/en.json | 24 ++++++++--------- app/config/locale/translations/eo.json | 18 ++++++------- app/config/locale/translations/es.json | 20 +++++++------- app/config/locale/translations/fa.json | 16 ++++++------ app/config/locale/translations/fi.json | 18 ++++++------- app/config/locale/translations/fo.json | 18 ++++++------- app/config/locale/translations/fr.json | 18 ++++++------- app/config/locale/translations/ga.json | 18 ++++++------- app/config/locale/translations/gu.json | 18 ++++++------- app/config/locale/translations/he.json | 18 ++++++------- app/config/locale/translations/hi.json | 18 ++++++------- app/config/locale/translations/hr.json | 18 ++++++------- app/config/locale/translations/hu.json | 18 ++++++------- app/config/locale/translations/hy.json | 18 ++++++------- app/config/locale/translations/id.json | 18 ++++++------- app/config/locale/translations/is.json | 18 ++++++------- app/config/locale/translations/it.json | 18 ++++++------- app/config/locale/translations/ja.json | 22 ++++++++-------- app/config/locale/translations/jv.json | 18 ++++++------- app/config/locale/translations/km.json | 18 ++++++------- app/config/locale/translations/kn.json | 18 ++++++------- app/config/locale/translations/ko.json | 22 ++++++++-------- app/config/locale/translations/la.json | 20 +++++++------- app/config/locale/translations/lb.json | 18 ++++++------- app/config/locale/translations/lt.json | 18 ++++++------- app/config/locale/translations/lv.json | 18 ++++++------- app/config/locale/translations/ml.json | 22 ++++++++-------- app/config/locale/translations/mr.json | 18 ++++++------- app/config/locale/translations/ms.json | 18 ++++++------- app/config/locale/translations/nb.json | 18 ++++++------- app/config/locale/translations/ne.json | 18 ++++++------- app/config/locale/translations/nl.json | 16 ++++++------ app/config/locale/translations/nn.json | 22 ++++++++-------- app/config/locale/translations/or.json | 18 ++++++------- app/config/locale/translations/pa.json | 18 ++++++------- app/config/locale/translations/pl.json | 18 ++++++------- app/config/locale/translations/pt-br.json | 18 ++++++------- app/config/locale/translations/pt-pt.json | 18 ++++++------- app/config/locale/translations/ro.json | 18 ++++++------- app/config/locale/translations/ru.json | 18 ++++++------- app/config/locale/translations/sa.json | 22 ++++++++-------- app/config/locale/translations/sd.json | 26 +++++++++---------- app/config/locale/translations/si.json | 18 ++++++------- app/config/locale/translations/sk.json | 18 ++++++------- app/config/locale/translations/sl.json | 18 ++++++------- app/config/locale/translations/sn.json | 18 ++++++------- app/config/locale/translations/sq.json | 18 ++++++------- app/config/locale/translations/sv.json | 18 ++++++------- app/config/locale/translations/ta.json | 18 ++++++------- app/config/locale/translations/te.json | 18 ++++++------- app/config/locale/translations/th.json | 20 +++++++------- app/config/locale/translations/tl.json | 18 ++++++------- app/config/locale/translations/tr.json | 18 ++++++------- app/config/locale/translations/uk.json | 18 ++++++------- app/config/locale/translations/ur.json | 18 ++++++------- app/config/locale/translations/vi.json | 18 ++++++------- app/config/locale/translations/zh-cn.json | 22 ++++++++-------- app/config/locale/translations/zh-tw.json | 22 ++++++++-------- 77 files changed, 691 insertions(+), 691 deletions(-) diff --git a/app/config/locale/templates/email-inner-base.tpl b/app/config/locale/templates/email-inner-base.tpl index 52e1093ffb..8cef391d2f 100644 --- a/app/config/locale/templates/email-inner-base.tpl +++ b/app/config/locale/templates/email-inner-base.tpl @@ -1,9 +1,9 @@ -

{{hello}},

+

{{hello}}

{{body}}

{{redirect}}

{{footer}}

- {{thanks}}, + {{thanks}}
{{signature}}

\ No newline at end of file diff --git a/app/config/locale/templates/email-magic-url.tpl b/app/config/locale/templates/email-magic-url.tpl index def1ea2395..21988c5bc1 100644 --- a/app/config/locale/templates/email-magic-url.tpl +++ b/app/config/locale/templates/email-magic-url.tpl @@ -1,4 +1,4 @@ -

{{hello}},

+

{{hello}}

{{optionButton}}

diff --git a/app/config/locale/templates/email-mfa-challenge.tpl b/app/config/locale/templates/email-mfa-challenge.tpl index e3cb6b444d..3e55227055 100644 --- a/app/config/locale/templates/email-mfa-challenge.tpl +++ b/app/config/locale/templates/email-mfa-challenge.tpl @@ -1,4 +1,4 @@ -

{{hello}},

+

{{hello}}

{{description}}

diff --git a/app/config/locale/templates/email-session-alert.tpl b/app/config/locale/templates/email-session-alert.tpl index 20cecf212d..bd2f52af79 100644 --- a/app/config/locale/templates/email-session-alert.tpl +++ b/app/config/locale/templates/email-session-alert.tpl @@ -1,4 +1,4 @@ -

{{hello}},

+

{{hello}}

{{body}}

diff --git a/app/config/locale/translations/af.json b/app/config/locale/translations/af.json index 238c6777bd..e68fda2c75 100644 --- a/app/config/locale/translations/af.json +++ b/app/config/locale/translations/af.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s span", "emails.verification.subject": "Rekening Bevestiging", - "emails.verification.hello": "Goeie dag {{user}}", + "emails.verification.hello": "Goeie dag {{user}},", "emails.verification.body": "Volg hierdie skakel om u e-pos adres te bevestig.", "emails.verification.footer": "Ignoreer gerus hierdie boodskap as u nie die versoek gestuur het om u adres te bevestig nie.", - "emails.verification.thanks": "Baie dankie", + "emails.verification.thanks": "Baie dankie,", "emails.verification.signature": "Die {{project}} span", "emails.magicSession.subject": "Teken aan", - "emails.magicSession.hello": "Goeie dag", + "emails.magicSession.hello": "Goeie dag,", "emails.magicSession.body": "Volg hierdie skakel om in te teken.", "emails.magicSession.footer": "Ignoreer gerus hierdie boodskap as u nie die versoek gestuur het om met die' adres in te teken nie.", - "emails.magicSession.thanks": "Baie dankie", + "emails.magicSession.thanks": "Baie dankie,", "emails.magicSession.signature": "Die {{project}} span", "emails.recovery.subject": "Herstel Wagwoord", - "emails.recovery.hello": "Goeie dag {{user}}", + "emails.recovery.hello": "Goeie dag {{user}},", "emails.recovery.body": "Volg hierdie skakel om u {{project}} wagwoord te herstel.", "emails.recovery.footer": "Ignoreer gerus hierdie boodskap as u nie die versoek gestuur het om u wagwoord te herstel nie.", - "emails.recovery.thanks": "Baie dankie", + "emails.recovery.thanks": "Baie dankie,", "emails.recovery.signature": "Die {{project}} span", "emails.invitation.subject": "Uitnodiging om by die %s span aan te sluit by %s", "emails.invitation.hello": "Goeie dag,", "emails.invitation.body": "Hierdie boodskap is aan u gestuur omdat {{owner}} u uitnooi om 'n lid van die {{team}} groep by die {{project}} projek te wees.", "emails.invitation.footer": "As u nie belang stel nie, kan u gerus hierdie boodskap ignoreer.", - "emails.invitation.thanks": "Baie dankie", + "emails.invitation.thanks": "Baie dankie,", "emails.invitation.signature": "Die {{project}} span", "locale.country.unknown": "Onbekend", "countries.af": "Afghanistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Die veiligheidsfrase vir hierdie e-pos is {{phrase}}. Jy kan hierdie e-pos vertrou as hierdie frase ooreenstem met die frase wat gewys is tydens aanmelding.", "emails.otpSession.thanks": "Dankie,", "emails.otpSession.signature": "{{project}} span" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/ar-ma.json b/app/config/locale/translations/ar-ma.json index 453de25c80..efd2e95c31 100644 --- a/app/config/locale/translations/ar-ma.json +++ b/app/config/locale/translations/ar-ma.json @@ -4,34 +4,34 @@ "settings.direction": "rtl", "emails.sender": "فرقة %s", "emails.verification.subject": "التيْقان ديال الحساب", - "emails.verification.hello": "السلام {{user}}", + "emails.verification.hello": "السلام {{user}}،", "emails.verification.body": "تبّع هاد الوصلة باش تيقّن لادريسة تاع ليميل ديالك.", "emails.verification.footer": "إلا ماشي نتا اللي طلبتي تيقّن هاد لادريسة تاع ليميل، ممكن تنخّل هاد البرية.", - "emails.verification.thanks": "شكرا", + "emails.verification.thanks": "شكرا،", "emails.verification.signature": "فرقة {{project}}", "emails.magicSession.subject": "تكونيكطا", - "emails.magicSession.hello": "السلام,", + "emails.magicSession.hello": "السلام،", "emails.magicSession.body": "تبّع هاد الوصلة باش تتكونيكطا.", "emails.magicSession.footer": "إلا ماشي نتا اللي طلبتي تتكونيكطا بهاد ليميل، ممكن تنخّل هاد البرية.", - "emails.magicSession.thanks": "شكرا", + "emails.magicSession.thanks": "شكرا،", "emails.magicSession.signature": "فرقة {{project}}", "emails.recovery.subject": "تبدال كلمة السر", - "emails.recovery.hello": "السلام {{user}}", + "emails.recovery.hello": "السلام {{user}}،", "emails.recovery.body": "تبّع هاد الوصلة باش تبدّل كلمة السر تاع {{project}}.", "emails.recovery.footer": "إلا ماشي نتا اللي طلبتي تبدّل كلمة السر، ممكن تنخّل هاد البرية.", - "emails.recovery.thanks": "شكرا", + "emails.recovery.thanks": "شكرا،", "emails.recovery.signature": "فرقة {{project}}", "emails.invitation.subject": "عراضة ل فرقة %s ف %s", - "emails.invitation.hello": "السلام", + "emails.invitation.hello": "السلام،", "emails.invitation.body": "هاد البرية تصيفطات ليك حيت {{owner}} بغى يعرض عليك تولّي عضو ف فرقة {{team}} عند {{project}}.", "emails.invitation.footer": "إلا كنتي ما مسوّقش, ممكن تنخّل هاد البرية.", - "emails.invitation.thanks": "شكرا", + "emails.invitation.thanks": "شكرا،", "emails.invitation.signature": "فرقة {{project}}", "emails.certificate.subject": "السرتافيكة فشلات ل %s", - "emails.certificate.hello": "السلام", + "emails.certificate.hello": "السلام،", "emails.certificate.body": "السرتافيكة ديال الضومين ديالك '{{domain}}' ما قدّاتش تجينيرا. هادي هي المحاولة نمرة {{attempt}}, السبب ديال هاد الفشل هو: {{error}}", "emails.certificate.footer": "السرتافيكة الفايتة ديالك غاتبقى مزيانة لمدة 30 يوم من عند أول فشل. كانشجعوك بزاف أنك تبقشش فهاد الموضوع, وا إلّا الضومين ديالك ما غايبقاش خدّام فيه الـ SSL.", - "emails.certificate.thanks": "شكرا", + "emails.certificate.thanks": "شكرا،", "emails.certificate.signature": "فرقة {{project}}", "locale.country.unknown": "ما معروفش", "countries.af": "أفغانستان", diff --git a/app/config/locale/translations/ar.json b/app/config/locale/translations/ar.json index cd45b32e02..1d67c2ecf7 100644 --- a/app/config/locale/translations/ar.json +++ b/app/config/locale/translations/ar.json @@ -4,28 +4,28 @@ "settings.direction": "rtl", "emails.sender": "فريق %s", "emails.verification.subject": "تأكيد الحساب", - "emails.verification.hello": "مرحبا {{user}}", + "emails.verification.hello": "مرحبا {{user}}،", "emails.verification.body": "برجاء اتباع الرابط التالي لتأكيد بريدك الإلكتروني", "emails.verification.footer": "لو لم تطلب تأكيد هذا البريد الإلكتروني، يمكنك تجاهل هذه الرسالة", - "emails.verification.thanks": "شكرا", + "emails.verification.thanks": "شكرا،", "emails.verification.signature": "فريق {{project}}", "emails.magicSession.subject": "تسجيل الدخول", - "emails.magicSession.hello": "أهلا", + "emails.magicSession.hello": "أهلا،", "emails.magicSession.body": "اتبع هذا الرابط لتسجيل الدخول", "emails.magicSession.footer": "لو لم تطلب تسجيل الدخول بهذا البريد الاكتروني ، يمكنك تجاهل هذه الرسالة", - "emails.magicSession.thanks": "شكرا", + "emails.magicSession.thanks": "شكرا،", "emails.magicSession.signature": "فريق {{project}}", "emails.recovery.subject": "تغيير كلمة السر", - "emails.recovery.hello": "أهلا {{user}}", + "emails.recovery.hello": "أهلا {{user}}،", "emails.recovery.body": "برجاء اتباع الراط التالي لتغيير كلمة السر الخاصة بـ{{project}}", "emails.recovery.footer": "لولم تطلب تغيير كلمة السر، يمكنك تجاهل هذه الرسالة", - "emails.recovery.thanks": "شكرا", + "emails.recovery.thanks": "شكرا،", "emails.recovery.signature": "فريق {{project}}", "emails.invitation.subject": "دعوة لفريق %s في %s", - "emails.invitation.hello": "أهلا", + "emails.invitation.hello": "أهلا،", "emails.invitation.body": "هذة الرسالة تم ارسالها لك لأن {{owner}} ارسل لك دعوة لتكون عضوا بفريق {{team}} في {{project}}", "emails.invitation.footer": "اذا كنت غير مهتم، يمكنك تجاهل هذه الرسالة", - "emails.invitation.thanks": "شكرا", + "emails.invitation.thanks": "شكرا،", "emails.invitation.signature": "فريق {{project}}", "locale.country.unknown": "مجهول", "countries.af": "أفغانستان", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "عبارة الأمان لهذا البريد الإلكتروني هي {{phrase}}. يمكنك الوثوق بهذا البريد الإلكتروني إذا كانت هذه العبارة تتطابق مع العبارة المعروضة أثناء تسجيل الدخول.", "emails.otpSession.thanks": "شكرًا،", "emails.otpSession.signature": "فريق {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/as.json b/app/config/locale/translations/as.json index aa42483dde..572ed80f1a 100644 --- a/app/config/locale/translations/as.json +++ b/app/config/locale/translations/as.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s দল", "emails.verification.subject": "একাউণ্ট প্ৰমাণীকৰণ", - "emails.verification.hello": "নমস্কাৰ {{user}}", + "emails.verification.hello": "নমস্কাৰ {{user}},", "emails.verification.body": "আপোনাৰ ইমেইল ঠিকনা প্ৰমাণিত কৰিবলৈ এই লিংকটো অনুসৰণ কৰক।", "emails.verification.footer": "যদি আপুনি এই ঠিকনাটো সত্যাপিত কৰিবলৈ কোৱা নাই, আপুনি এই বাৰ্তাটো উপেক্ষা কৰিব পাৰে।", - "emails.verification.thanks": "ধন্যবাদ", + "emails.verification.thanks": "ধন্যবাদ,", "emails.verification.signature": "{{project}} দল", "emails.magicSession.subject": "লগইন", - "emails.magicSession.hello": "নমস্কাৰ", + "emails.magicSession.hello": "নমস্কাৰ,", "emails.magicSession.body": "লগইন কৰিবলৈ এই লিংকটো অনুসৰণ কৰক।", "emails.magicSession.footer": "যদি আপুনি এই ইমেইল ব্যৱহাৰ কৰি লগইন কৰিবলৈ কোৱা নাছিল, আপুনি এই বাৰ্তাটো উপেক্ষা কৰিব পাৰে।", - "emails.magicSession.thanks": "ধন্যবাদ", + "emails.magicSession.thanks": "ধন্যবাদ,", "emails.magicSession.signature": "{{project}} দল", "emails.recovery.subject": "পাছৱাৰ্ড ৰিছেট", - "emails.recovery.hello": "ধন্যবাদ {{user}}", + "emails.recovery.hello": "ধন্যবাদ {{user}},", "emails.recovery.body": "আপোনাৰ {{project}} পাছৱৰ্ড ৰিছেট কৰিবলৈ এই লিংকটো অনুসৰণ কৰক।.", "emails.recovery.footer": "যদি আপুনি আপোনাৰ পাছৱৰ্ড ৰিছেট কৰিবলৈ কোৱা নাছিল, আপুনি এই বাৰ্তাটো উপেক্ষা কৰিব পাৰে।", - "emails.recovery.thanks": "ধন্যবাদ", + "emails.recovery.thanks": "ধন্যবাদ,", "emails.recovery.signature": "{{project}} দল", "emails.invitation.subject": "%s বছৰত %s দললৈ নিমন্ত্ৰণ", - "emails.invitation.hello": "নমস্কাৰ", + "emails.invitation.hello": "নমস্কাৰ,", "emails.invitation.body": "এই মেইলটো আপোনালৈ প্ৰেৰণ কৰা হৈছিল কাৰণ {{owner}} জনে আপোনাক {{project}} বছৰবয়সত {{team}} দলৰ সদস্য হ'বলৈ আমন্ত্ৰণ জনাব বিচাৰিছিল।", "emails.invitation.footer": "যদি আপুনি আগ্ৰহী নহয়, আপুনি এই বাৰ্তাটো উপেক্ষা কৰিব পাৰে।", - "emails.invitation.thanks": "ধন্যবাদ", + "emails.invitation.thanks": "ধন্যবাদ,", "emails.invitation.signature": "{{project}} দল", "locale.country.unknown": "অজ্ঞাত ", "countries.af": "আফগানিস্তান ", @@ -245,10 +245,10 @@ "emails.otpSession.thanks": "ধন্যবাদ,", "emails.otpSession.signature": "{{project}} দল", "emails.certificate.subject": "%sৰ বাবে প্ৰমাণপত্ৰ ব্যৰ্থতা", - "emails.certificate.hello": "নমস্কাৰ", + "emails.certificate.hello": "নমস্কাৰ,", "emails.certificate.body": "আপোনাৰ ডোমেইন '{{domain}}' ৰ বাবে প্ৰমাণপত্ৰটো উত্‌পন্ন কৰিব পৰা নগ'ল। এয়া প্ৰচেষ্টা নম্বৰ {{attempt}}, আৰু বিফলতাৰ কাৰণ হ'ল: {{error}}", "emails.certificate.footer": "আপোনাৰ পূৰ্বৰ প্ৰমাণপত্ৰটো প্ৰথম ব্ৰিফল হোৱাৰ দিনৰ পৰা ৩০ দিনলৈ বৈধ থাকিব। আমি এই ঘটনাটোৰ তদন্ত কৰিবলৈ উচ্চ পৰামৰ্শ দিয়ে, অন্যথা আপোনাৰ ডোমেইনটো অবৈধ SSL যোগাযোগ অবিহনে থাকিব।", - "emails.certificate.thanks": "ধন্যবাদ", + "emails.certificate.thanks": "ধন্যবাদ,", "emails.certificate.signature": "{{project}} দল", "sms.verification.body": "{{secret}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/az.json b/app/config/locale/translations/az.json index df1264fe8d..5988c51786 100644 --- a/app/config/locale/translations/az.json +++ b/app/config/locale/translations/az.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s Komandası", "emails.verification.subject": "Hesab Doğrulama", - "emails.verification.hello": "Salam {{user}}", + "emails.verification.hello": "Salam {{user}},", "emails.verification.body": "E-poçt ünvanınızı təsdiq etmək üçün bu linki izləyin.", "emails.verification.footer": "Bu ünvanı doğrulamağı xahiş etməmisinizsə, bu mesajı gözardı edə bilərsiniz.", - "emails.verification.thanks": "Təşəkkürlər", + "emails.verification.thanks": "Təşəkkürlər,", "emails.verification.signature": "{{project}} komandası", "emails.magicSession.subject": "Daxil Olmaq", - "emails.magicSession.hello": "Salam", + "emails.magicSession.hello": "Salam,", "emails.magicSession.body": "Daxil olmaq üçün bu linki izləyin.", "emails.magicSession.footer": "Bu e-poçtdan istifadə edərək giriş istəməmisinizsə, bu mesajı görməməzlikdən gələ bilərsiniz.", - "emails.magicSession.thanks": "Təşəkkürlər", + "emails.magicSession.thanks": "Təşəkkürlər,", "emails.magicSession.signature": "{{project}} komandası", "emails.recovery.subject": "Şifrə Sıfırlanması", - "emails.recovery.hello": "Salam {{user}}", + "emails.recovery.hello": "Salam {{user}},", "emails.recovery.body": "{{project}} şifrənizi sıfırlamaq üçün bu linki izləyin.", "emails.recovery.footer": "Şifrənizi sıfırlamağı xahiş etməmisinizsə, bu mesajı gözardı edə bilərsiniz.", - "emails.recovery.thanks": "Təşəkkürlər", + "emails.recovery.thanks": "Təşəkkürlər,", "emails.recovery.signature": "{{project}} komandası", "emails.invitation.subject": "%s Komandasına Dəvət %sdə", - "emails.invitation.hello": "Salam", + "emails.invitation.hello": "Salam,", "emails.invitation.body": "{{owner}}, {{project}}də {{team}} komandasına üzv olmağa dəvət etmək istədiyi üçün bu məktub sizə göndərildi.", "emails.invitation.footer": "Əgər maraqlanmırsınızsa, bu mesajı gözardı edə bilərsiniz.", - "emails.invitation.thanks": "Təşəkkürlər", + "emails.invitation.thanks": "Təşəkkürlər,", "emails.invitation.signature": "{{project}} komandası", "locale.country.unknown": "Naməlum", "countries.af": "Əfqanıstan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Bu e-poçtun təhlükəsizlik ifadəsi {{phrase}}-dir. Əgər bu ifadə daxil olarkən göstərilən ifadə ilə üst-üstə düşürsə, bu e-poçta etibar edə bilərsiniz.", "emails.otpSession.thanks": "Sağ olun,", "emails.otpSession.signature": "{{project}} komandası" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/be.json b/app/config/locale/translations/be.json index a33916f623..f03a9d5bef 100644 --- a/app/config/locale/translations/be.json +++ b/app/config/locale/translations/be.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "Каманда %s", "emails.verification.subject": "Верыфікацыя акаўнта", - "emails.verification.hello": "Прывітанне {{user}}", + "emails.verification.hello": "Прывітанне {{user}},", "emails.verification.body": "Перайдзіце па гэтай спасылцы, каб пацвердзіць свой адрас электроннай пошты", "emails.verification.footer": "Калі вы не запытвалі пацвярджэнне гэтага адрасу, праігнаруйце гэтае паведамленне.", - "emails.verification.thanks": "Дзякуем", + "emails.verification.thanks": "Дзякуем,", "emails.verification.signature": "каманда {{project}}", "emails.magicSession.subject": "Лагін", - "emails.magicSession.hello": "Прывітанне", + "emails.magicSession.hello": "Прывітанне,", "emails.magicSession.body": "Перайдзіце па спасылцы, каб увайсці.", "emails.magicSession.footer": "Калі вы не прасілі ўвайсці, выкарыстоўваючы гэты адрас электроннай пошты, праігнаруйце гэтае паведамленне.", - "emails.magicSession.thanks": "Дзякуем", + "emails.magicSession.thanks": "Дзякуем,", "emails.magicSession.signature": "каманда {{project}}", "emails.recovery.subject": "Скід пароля", - "emails.recovery.hello": "Прывітанне, {{user}}", + "emails.recovery.hello": "Прывітанне, {{user}},", "emails.recovery.body": "Перайдзіце па гэтай спасылцы, каб скінуць пароль для праекта {{project}}.", "emails.recovery.footer": "Калі вы не прасілі скінуць пароль, вы можаце праігнараваць гэта паведамленне.", - "emails.recovery.thanks": "Дзякуем", + "emails.recovery.thanks": "Дзякуем,", "emails.recovery.signature": "каманда {{project}}", "emails.invitation.subject": "Запрошення до Команди %s у %s", - "emails.invitation.hello": "Прывітанне", + "emails.invitation.hello": "Прывітанне,", "emails.invitation.body": "Гэта паведамленне было адпраўлена вам, таму што {{owner}} хацеў запрасіць вас стаць членам каманды {{team}} у {{project}}.", "emails.invitation.footer": "Калі вам гэта не цікава, вы можаце праігнараваць гэтае паведамленне.", - "emails.invitation.thanks": "Дзякуем", + "emails.invitation.thanks": "Дзякуем,", "emails.invitation.signature": "каманда {{project}}", "locale.country.unknown": "Невядомы", "countries.af": "Афганістан", @@ -245,10 +245,10 @@ "emails.otpSession.thanks": "Дзякуй,", "emails.otpSession.signature": "каманда {{project}}", "emails.certificate.subject": "Сведчанне няўдалае для %s", - "emails.certificate.hello": "Прывітанне", + "emails.certificate.hello": "Прывітанне,", "emails.certificate.body": "Сертыфікат для вашага дамена '{{domain}}' не можа быць створаны. Гэта спроба нумар {{attempt}}, і прычынай няўдачы з'яўляецца: {{error}}", "emails.certificate.footer": "Ваш папярэдні сертыфікат будзе дзейнічаць 30 дзён з моманту першай няўдачы. Мы высока рэкамендуем расследаваць гэтую сітуацыю, інакш ваш дамен апынецца без дзейнага сертыфіката SSL-злучэння.", - "emails.certificate.thanks": "Дзякуй", + "emails.certificate.thanks": "Дзякуй,", "emails.certificate.signature": "каманда {{project}}", "sms.verification.body": "{{secret}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/bg.json b/app/config/locale/translations/bg.json index 2dad3aecbe..086c6b283e 100644 --- a/app/config/locale/translations/bg.json +++ b/app/config/locale/translations/bg.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s Екип", "emails.verification.subject": "", - "emails.verification.hello": "", + "emails.verification.hello": ",", "emails.verification.body": "", "emails.verification.footer": "", - "emails.verification.thanks": "", + "emails.verification.thanks": ",", "emails.verification.signature": "", "emails.magicSession.subject": "", - "emails.magicSession.hello": "", + "emails.magicSession.hello": ",", "emails.magicSession.body": "", "emails.magicSession.footer": "", - "emails.magicSession.thanks": "", + "emails.magicSession.thanks": ",", "emails.magicSession.signature": "", "emails.recovery.subject": "", - "emails.recovery.hello": "", + "emails.recovery.hello": ",", "emails.recovery.body": "", "emails.recovery.footer": "", - "emails.recovery.thanks": "", + "emails.recovery.thanks": ",", "emails.recovery.signature": "", "emails.invitation.subject": "", - "emails.invitation.hello": "", + "emails.invitation.hello": ",", "emails.invitation.body": "", "emails.invitation.footer": "", - "emails.invitation.thanks": "", + "emails.invitation.thanks": ",", "emails.invitation.signature": "", "locale.country.unknown": "Неизвестно", "countries.af": "Афганистан", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Фразата за сигурност за този имейл е {{phrase}}. Можете да се доверите на този имейл, ако тази фраза съвпада с фразата, показана по време на вписването.", "emails.otpSession.thanks": "Благодаря,", "emails.otpSession.signature": "екип на {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/bh.json b/app/config/locale/translations/bh.json index 347f6f5d31..5cf06bd1dd 100644 --- a/app/config/locale/translations/bh.json +++ b/app/config/locale/translations/bh.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s टीम", "emails.verification.subject": "खाता प्रमाणिकरण", - "emails.verification.hello": "नमस्ते {{user}}", + "emails.verification.hello": "नमस्ते {{user}},", "emails.verification.body": "ईमेल प्रमाणिकरण करे क लेल दिहल गइल लिंक फॉलो करें|", "emails.verification.footer": "अगर ई पता को सत्यापित करे के लिए ना कहाले, तो आप ई संदेश क अनदेखा कर सकत अछि।", - "emails.verification.thanks": "धन्यवाद", + "emails.verification.thanks": "धन्यवाद,", "emails.verification.signature": "{{project}} टीम", "emails.magicSession.subject": "लॉग इन करीं|", - "emails.magicSession.hello": "प्रणाम", + "emails.magicSession.hello": "प्रणाम,", "emails.magicSession.body": "लॉग इन करें लेल दिहल गइल लिंक फॉलो करें|", "emails.magicSession.footer": "अगर लॉग इन करे के लिए ना कहाले, तो आप ई संदेश क अनदेखा कर सकत अछि।", - "emails.magicSession.thanks": "धन्यवाद", + "emails.magicSession.thanks": "धन्यवाद,", "emails.magicSession.signature": "{{project}} टीम", "emails.recovery.subject": "पासवर्ड बदल क लेल|", - "emails.recovery.hello": "प्रणाम {{user}}", + "emails.recovery.hello": "प्रणाम {{user}},", "emails.recovery.body": "पासवर्ड बदल क लेल दिहल गइल लिंक फॉलो करें|", "emails.recovery.footer": "अगर पासवर्ड बदल क लेल ना कहाले, तो आप ई संदेश क अनदेखा कर सकत अछि।", - "emails.recovery.thanks": "धन्यवाद", + "emails.recovery.thanks": "धन्यवाद,", "emails.recovery.signature": "{{project}} टीम", "emails.invitation.subject": "%s टीम क %s पे न्योता देवे क लेल|", - "emails.invitation.hello": "प्रणाम", + "emails.invitation.hello": "प्रणाम,", "emails.invitation.body": "ई मेल आपके एही लेल भेजल गईल रहल काहे क {{owner}} आपके {{project}} क {{team}} टीम का सदस्य बनावे चाहित रहे|", "emails.invitation.footer": "अगर आवे क इच्छा ना होवत, तो आप ई संदेश क अनदेखा कर सकत अछि।", - "emails.invitation.thanks": "धन्यवाद", + "emails.invitation.thanks": "धन्यवाद,", "emails.invitation.signature": "{{project}} टीम", "locale.country.unknown": "अनजान", "countries.af": "अफ़ग़ानिस्तान", @@ -242,13 +242,13 @@ "emails.otpSession.description": "जब आपको सुरक्षित रूप से अपना {{project}} खाता में साइन इन करे खातिर कहल जाए तऽ निम्नलिखित सत्यापन कोड दर्ज करीं। ई 15 मिनट में खत्म हो जई।", "emails.otpSession.clientInfo": "एह साइन इन के अनुरोध {{agentClient}} पर {{agentDevice}} {{agentOs}} का प्रयोग करि कऽ कइल गइल बा। यदि तूँ एह साइन इन के अनुरोध ना कइले रहीं, त तूँ एह ईमेल के नजरअंदाज कर सकेला।", "emails.otpSession.securityPhrase": "एही ईमेल खातिर सुरक्षा वाक्य {{phrase}} हऽ। अगर ई वाक्य साइन इन कइला के समय देखावल गेल वाक्य से मेल खाता, त एह ईमेल पर भरोसा कर सकैत छी।", - "emails.otpSession.thanks": "धन्यवाद", + "emails.otpSession.thanks": "धन्यवाद,", "emails.otpSession.signature": "{{project}} टीम", "emails.certificate.subject": "%s लेल प्रमाणपत्र असफलта", - "emails.certificate.hello": "नमस्ते", + "emails.certificate.hello": "नमस्ते,", "emails.certificate.body": "आपके डोमेन '{{domain}}' के लिए प्रमाणपत्र नहीं बनाया जा सका। ई प्रयास संख्या {{attempt}} है, और ई असफलता के कारण रहे: {{error}}", "emails.certificate.footer": "तोहार पिछलका प्रमाणपत्र पहिल असफलता से 30 दिन धरी मान्य होईत। हम बहुत जोर देके सलाह देतानी कि एह मामला के जांच करीं, नहीं त तोहार डोमेन बिना कोनो मान्य SSL संवाद के रहि जाईत।", - "emails.certificate.thanks": "धन्यवाद", + "emails.certificate.thanks": "धन्यवाद,", "emails.certificate.signature": "{{project}} टीम", "sms.verification.body": "{{secret}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/bn.json b/app/config/locale/translations/bn.json index 897faea7c1..495f56e012 100644 --- a/app/config/locale/translations/bn.json +++ b/app/config/locale/translations/bn.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s টীম", "emails.verification.subject": "বিষয়", - "emails.verification.hello": "নমস্কার {{user}}", + "emails.verification.hello": "নমস্কার {{user}},", "emails.verification.body": "এই লিঙ্কের মাধ্যমে ইমেইল যাচাই করুন।", "emails.verification.footer": "আপনি যদি এই ঠিকানা যাচাই করতে না বলেন, তাহলে আপনি এই বার্তাটি উপেক্ষা করতে পারেন।", - "emails.verification.thanks": "ধন্যবাদ", + "emails.verification.thanks": "ধন্যবাদ,", "emails.verification.signature": "{{project}} টীম", "emails.magicSession.subject": "লগ ইন", - "emails.magicSession.hello": "নমস্কার", + "emails.magicSession.hello": "নমস্কার,", "emails.magicSession.body": "এই লিঙ্কের মাধ্যমে লগ ইন করুন।", "emails.magicSession.footer": "আপনি যদি এই ইমেলটি ব্যবহার করে লগইন করতে না বলেন, তাহলে আপনি এই বার্তাটি উপেক্ষা করতে পারেন।", - "emails.magicSession.thanks": "ধন্যবাদ", + "emails.magicSession.thanks": "ধন্যবাদ,", "emails.magicSession.signature": "{{project}} টীম", "emails.recovery.subject": "পাসওয়ার্ড রিসেট", - "emails.recovery.hello": "নমস্কার {{user}}", + "emails.recovery.hello": "নমস্কার {{user}},", "emails.recovery.body": "এই লিঙ্কের মাধ্যমে আপনার {{project}} পাসওয়ার্ড পুনরায় সেট করুন।", "emails.recovery.footer": "আপনি যদি আপনার পাসওয়ার্ড পুনরায় সেট করতে না বলেন, তাহলে আপনি এই বার্তাটি উপেক্ষা করতে পারেন।", - "emails.recovery.thanks": "ধন্যবাদ", + "emails.recovery.thanks": "ধন্যবাদ,", "emails.recovery.signature": "{{project}} টীম", "emails.invitation.subject": "%s টিমকে %s তে আমন্ত্রণ জানান", - "emails.invitation.hello": "নমস্কার", + "emails.invitation.hello": "নমস্কার,", "emails.invitation.body": "এই মেইলটি আপনাকে পাঠানো হয়েছে কারণ {{owner}} আপনাকে {{project}} এর সাথে যুক্ত {{team}} টিমের সদস্য হওয়ার জন্য আমন্ত্রণ জানাতে চেয়েছিলেন।", "emails.invitation.footer": "যদি এটি আপনার জন্য প্রয়োজনীয় না হয়, আপনি এই বার্তাটি উপেক্ষা করতে পারেন।", - "emails.invitation.thanks": "ধন্যবাদ", + "emails.invitation.thanks": "ধন্যবাদ,", "emails.invitation.signature": "{{project}} টীম", "locale.country.unknown": "অজানা", "countries.af": "আফগানিস্তান", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "এই ইমেইলের জন্য সুরক্ষা বাক্য হলো {{phrase}}। যদি এই বাক্যটি সাইন ইনের সময় দেখানো বাক্যের সাথে মেলে, তাহলে আপনি এই ইমেইলটিকে বিশ্বাস করতে পারেন।", "emails.otpSession.thanks": "ধন্যবাদ,", "emails.otpSession.signature": "{{project}} দল" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/bs.json b/app/config/locale/translations/bs.json index 1ce2d57a3e..1c69619c01 100644 --- a/app/config/locale/translations/bs.json +++ b/app/config/locale/translations/bs.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s Tim", "emails.verification.subject": "", - "emails.verification.hello": "", + "emails.verification.hello": ",", "emails.verification.body": "", "emails.verification.footer": "", - "emails.verification.thanks": "", + "emails.verification.thanks": ",", "emails.verification.signature": "", "emails.magicSession.subject": "", - "emails.magicSession.hello": "", + "emails.magicSession.hello": ",", "emails.magicSession.body": "", "emails.magicSession.footer": "", - "emails.magicSession.thanks": "", + "emails.magicSession.thanks": ",", "emails.magicSession.signature": "", "emails.recovery.subject": "", - "emails.recovery.hello": "", + "emails.recovery.hello": ",", "emails.recovery.body": "", "emails.recovery.footer": "", - "emails.recovery.thanks": "", + "emails.recovery.thanks": ",", "emails.recovery.signature": "", "emails.invitation.subject": "", - "emails.invitation.hello": "", + "emails.invitation.hello": ",", "emails.invitation.body": "", "emails.invitation.footer": "", - "emails.invitation.thanks": "", + "emails.invitation.thanks": ",", "emails.invitation.signature": "", "locale.country.unknown": "Nepoznat", "countries.af": "Afganistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Sigurnosna fraza za ovaj email je {{phrase}}. Možete vjerovati ovom emailu ako se ova fraza podudara sa frazom prikazanom prilikom prijave.", "emails.otpSession.thanks": "Hvala,", "emails.otpSession.signature": "tim {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/ca.json b/app/config/locale/translations/ca.json index 94e3ae24c5..98940a4a48 100644 --- a/app/config/locale/translations/ca.json +++ b/app/config/locale/translations/ca.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s Equip", "emails.verification.subject": "Verificació del compte", - "emails.verification.hello": "Hola {{user}}", + "emails.verification.hello": "Hola {{user}},", "emails.verification.body": "Accedeix a aquest enllaç per tal de verificar la teva adreça electrònica.", "emails.verification.footer": "Si no has sol·licitat la verificació d'aquesta adreça electrònica, pots ignorar aquest missatge.", - "emails.verification.thanks": "Gràcies", + "emails.verification.thanks": "Gràcies,", "emails.verification.signature": "Equip {{project}}", "emails.magicSession.subject": "Entrar", - "emails.magicSession.hello": "Hola", + "emails.magicSession.hello": "Hola,", "emails.magicSession.body": "Accedeix a aquest enllaç per a entrar.", "emails.magicSession.footer": "Si no has sol·licitat entrar amb aquesta adreça electrònica, pots ignorar aquest missatge.", - "emails.magicSession.thanks": "Gràcies", + "emails.magicSession.thanks": "Gràcies,", "emails.magicSession.signature": "Equip {{project}}", "emails.recovery.subject": "Reinicialitzar contrasenya", - "emails.recovery.hello": "Hola {{user}}", + "emails.recovery.hello": "Hola {{user}},", "emails.recovery.body": "Accedeix a aquest enllaç per a reinicialitzar la teva contrasenya de {{project}}.", "emails.recovery.footer": "Si no has sol·licitat reinicialitzar la teva contrasenya, pots ignorar aquest missatge.", - "emails.recovery.thanks": "Gràcies", + "emails.recovery.thanks": "Gràcies,", "emails.recovery.signature": "Equip {{project}}", "emails.invitation.subject": "Invitació a l'equip %s a s%", - "emails.invitation.hello": "Hola", + "emails.invitation.hello": "Hola,", "emails.invitation.body": "Aquest correu se t'ha enviat perquè {{owner}} vol convidar-te a formar part de l'equip {{team}} al {{project}}.", "emails.invitation.footer": "Si no és del teu interès, pots ignorar aquest missatge.", - "emails.invitation.thanks": "Gràcies", + "emails.invitation.thanks": "Gràcies,", "emails.invitation.signature": "Equip {{project}}", "locale.country.unknown": "Desconegut", "countries.af": "Afganistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "La frase de seguretat d'aquest correu electrònic és {{phrase}}. Podeu confiar en aquest correu electrònic si aquesta frase coincideix amb la frase mostrada durant l'inici de sessió.", "emails.otpSession.thanks": "Gràcies,", "emails.otpSession.signature": "equip {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/cs.json b/app/config/locale/translations/cs.json index 4e3c533c77..c67e9299da 100644 --- a/app/config/locale/translations/cs.json +++ b/app/config/locale/translations/cs.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s tým", "emails.verification.subject": "", - "emails.verification.hello": "", + "emails.verification.hello": ",", "emails.verification.body": "", "emails.verification.footer": "", - "emails.verification.thanks": "", + "emails.verification.thanks": ",", "emails.verification.signature": "", "emails.magicSession.subject": "", - "emails.magicSession.hello": "", + "emails.magicSession.hello": ",", "emails.magicSession.body": "", "emails.magicSession.footer": "", - "emails.magicSession.thanks": "", + "emails.magicSession.thanks": ",", "emails.magicSession.signature": "", "emails.recovery.subject": "", - "emails.recovery.hello": "", + "emails.recovery.hello": ",", "emails.recovery.body": "", "emails.recovery.footer": "", - "emails.recovery.thanks": "", + "emails.recovery.thanks": ",", "emails.recovery.signature": "", "emails.invitation.subject": "", - "emails.invitation.hello": "", + "emails.invitation.hello": ",", "emails.invitation.body": "", "emails.invitation.footer": "", - "emails.invitation.thanks": "", + "emails.invitation.thanks": ",", "emails.invitation.signature": "", "locale.country.unknown": "Neznámý", "countries.af": "Afghánistán", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Bezpečnostní fráze pro tento e-mail je {{phrase}}. Tomuto e-mailu můžete důvěřovat, pokud se tato fráze shoduje s frází zobrazenou při přihlášení.", "emails.otpSession.thanks": "Děkuji,", "emails.otpSession.signature": "tým {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/da.json b/app/config/locale/translations/da.json index d50d9c46c6..9cec74dbed 100644 --- a/app/config/locale/translations/da.json +++ b/app/config/locale/translations/da.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s Team", "emails.verification.subject": "Konto Verifikation", - "emails.verification.hello": "Hej {{user}}", + "emails.verification.hello": "Hej {{user}},", "emails.verification.body": "Følg dette link, for at verificere din email adresse.", "emails.verification.footer": "Hvis du ikke har bedt om at verificere denne adresse, ignorer venligst denne besked.", - "emails.verification.thanks": "Tak", + "emails.verification.thanks": "Tak,", "emails.verification.signature": "{{project}} team", "emails.magicSession.subject": "Login", - "emails.magicSession.hello": "Hej", + "emails.magicSession.hello": "Hej,", "emails.magicSession.body": "Følg dette link for at logge ind.", "emails.magicSession.footer": "Hvis du ikke har bedt om at logge ind med denne email, ignorer venligst denne besked.", - "emails.magicSession.thanks": "Tak", + "emails.magicSession.thanks": "Tak,", "emails.magicSession.signature": "{{project}} team", "emails.recovery.subject": "Nulstil Password", - "emails.recovery.hello": "Hej {{user}}", + "emails.recovery.hello": "Hej {{user}},", "emails.recovery.body": "Følg dette link for at nulstille koden til {{project}}.", "emails.recovery.footer": "Hvis du ikke har bedt om at nulstille dit password, ignorer venligst denne besked.", - "emails.recovery.thanks": "Tak", + "emails.recovery.thanks": "Tak,", "emails.recovery.signature": "{{project}} team", "emails.invitation.subject": "Invitation til %s Team på %s", - "emails.invitation.hello": "Hej", + "emails.invitation.hello": "Hej,", "emails.invitation.body": "Denne mail blev sendt til dig, fordi {{owner}} vil invitere dig til at blive medlem af {{team}} teamet på {{project}}.", "emails.invitation.footer": "Hvis du ikke er interesseret, ignorer venligst denne besked.", - "emails.invitation.thanks": "Tak", + "emails.invitation.thanks": "Tak,", "emails.invitation.signature": "{{project}} team", "locale.country.unknown": "Ukendt", "countries.af": "Afghanistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Sikkerhedsfrasen for denne e-mail er {{phrase}}. Du kan stole på denne e-mail, hvis denne frase matcher frasen vist under login.", "emails.otpSession.thanks": "Tak,", "emails.otpSession.signature": "{{project}} team" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/de.json b/app/config/locale/translations/de.json index 2279fcf4c0..38b1e46870 100644 --- a/app/config/locale/translations/de.json +++ b/app/config/locale/translations/de.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s Team", "emails.verification.subject": "Kontoverifizierung", - "emails.verification.hello": "Hey {{user}}", + "emails.verification.hello": "Hey {{user}},", "emails.verification.body": "Folge diesem Link, um deine E-Mail-Adresse zu bestätigen.", "emails.verification.footer": "Solltest du keine Verifizierung dieser E-Mail-Adresse angefordert haben, kannst du diese Nachricht ignorieren.", - "emails.verification.thanks": "Danke", + "emails.verification.thanks": "Danke,", "emails.verification.signature": "{{project}}-Team", "emails.magicSession.subject": "Login", - "emails.magicSession.hello": "Hey", + "emails.magicSession.hello": "Hey,", "emails.magicSession.body": "Folge diesem Link, um dich einzuloggen.", "emails.magicSession.footer": "Solltest du keinen Login für diese E-Mail-Adresse angefordert haben, kannst du diese Nachricht ignorieren.", - "emails.magicSession.thanks": "Danke", + "emails.magicSession.thanks": "Danke,", "emails.magicSession.signature": "{{project}}-Team", "emails.recovery.subject": "Kennwort zurücksetzen", - "emails.recovery.hello": "Hallo {{user}}", + "emails.recovery.hello": "Hallo {{user}},", "emails.recovery.body": "Folge diesem Link, um dein {{project}}-Kennwort zurückzusetzen.", "emails.recovery.footer": "Solltest du keine Kennwort-Zurücksetzung angefordert haben, kannst du diese Nachricht ignorieren.", - "emails.recovery.thanks": "Danke", + "emails.recovery.thanks": "Danke,", "emails.recovery.signature": "{{project}}-Team", "emails.invitation.subject": "Einladung zum %s-Team auf %s", - "emails.invitation.hello": "Hello", + "emails.invitation.hello": "Hello,", "emails.invitation.body": "Du erhälst diese E-Mail, weil {{owner}} dich in das Team {{team}} auf {{project}} eingeladen hat.", "emails.invitation.footer": "Wenn du nicht interessiert bist, kannst du diese Nachricht ignorieren.", - "emails.invitation.thanks": "Danke", + "emails.invitation.thanks": "Danke,", "emails.invitation.signature": "{{project}}-Team", "locale.country.unknown": "Unbekannt", "countries.af": "Afghanistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Die Sicherheitsphrase für diese E-Mail lautet {{phrase}}. Sie können dieser E-Mail vertrauen, wenn diese Phrase mit der Phrase übereinstimmt, die beim Anmelden angezeigt wird.", "emails.otpSession.thanks": "Danke,", "emails.otpSession.signature": "{{project}} Team" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/el.json b/app/config/locale/translations/el.json index 16c165ea39..1ef9cd30df 100644 --- a/app/config/locale/translations/el.json +++ b/app/config/locale/translations/el.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "Ομάδα %s", "emails.verification.subject": "Επαλήθευση Λογαριασμού", - "emails.verification.hello": "Γεια σου {{user}}", + "emails.verification.hello": "Γεια σου {{user}},", "emails.verification.body": "Ακολουθήστε αυτό το link για να επαληθεύσετε τη δ/νση του email σας", "emails.verification.footer": "Εάν δεν ζητήσατε επαλήθευση αυτής της δ/νσης email, μπορείτε να αγνοήσετε αυτό το μήνυμα", - "emails.verification.thanks": "Ευχαριστούμε", + "emails.verification.thanks": "Ευχαριστούμε,", "emails.verification.signature": "Η ομάδα του {{project}}", "emails.magicSession.subject": "Είσοδος", - "emails.magicSession.hello": "Γεια σου", + "emails.magicSession.hello": "Γεια σου,", "emails.magicSession.body": "Ακολουθήστε αυτό το link για να συνδεθείτε", "emails.magicSession.footer": "Εάν δεν ζητήσατε να συνδεθείτε χρησιμοποιώντας αυτό το email, μπορείτε να αγνοήσετε αυτό το μήνυμα.", - "emails.magicSession.thanks": "Ευχαριστούμε", + "emails.magicSession.thanks": "Ευχαριστούμε,", "emails.magicSession.signature": "Η ομάδα του {{project}}", "emails.recovery.subject": "Αλλαγή κωδικού πρόσβασης", - "emails.recovery.hello": "Γεια σου {{user}}", + "emails.recovery.hello": "Γεια σου {{user}},", "emails.recovery.body": "Ακολουθήστε αυτό το link για να αλλάξετε τον {{project}} κωδικό σας", "emails.recovery.footer": "Εάν δεν ζητήσατε αλλαγή του κωδικού σας πρόσβασης, μπορείτε να αγνοήσετε αυτό το μήνυμα", - "emails.recovery.thanks": "Ευχαριστούμε", + "emails.recovery.thanks": "Ευχαριστούμε,", "emails.recovery.signature": "Η ομάδα του {{project}}", "emails.invitation.subject": "Πρόσκληση στην %s Ομάδα στον %s", - "emails.invitation.hello": "Γεια σου", + "emails.invitation.hello": "Γεια σου,", "emails.invitation.body": "Αυτό το email στάλθηκε επειδή ο/η {{owner}} θέλει να σας προσκαλέσει να γίνετε μέλος της ομάδας {{team}} του {{project}}.", "emails.invitation.footer": "Εάν δεν ενδιαφέρεστε, μπορείτε να αγνοήσετε αυτό το μήνυμα.", - "emails.invitation.thanks": "Ευχαριστούμε", + "emails.invitation.thanks": "Ευχαριστούμε,", "emails.invitation.signature": "Η ομάδα του {{project}}", "locale.country.unknown": "Άγνωστο", "countries.af": "Αφγανιστάν", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Η φράση ασφαλείας για αυτό το email είναι {{phrase}}. Μπορείτε να εμπιστευτείτε αυτό το email αν αυτή η φράση ταιριάζει με τη φράση που εμφανίστηκε κατά την είσοδο.", "emails.otpSession.thanks": "Ευχαριστώ,", "emails.otpSession.signature": "ομάδα {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/en.json b/app/config/locale/translations/en.json index 860671946f..d9dfddb017 100644 --- a/app/config/locale/translations/en.json +++ b/app/config/locale/translations/en.json @@ -4,13 +4,13 @@ "settings.direction": "ltr", "emails.sender": "%s Team", "emails.verification.subject": "Account Verification", - "emails.verification.hello": "Hello {{user}}", + "emails.verification.hello": "Hello {{user}},", "emails.verification.body": "Follow this link to verify your email address to your {{b}}{{project}}{{/b}} account.", "emails.verification.footer": "If you didn’t ask to verify this address, you can ignore this message.", - "emails.verification.thanks": "Thanks", + "emails.verification.thanks": "Thanks,", "emails.verification.signature": "{{project}} team", "emails.magicSession.subject": "{{project}} Login", - "emails.magicSession.hello": "Hello {{user}}", + "emails.magicSession.hello": "Hello {{user}},", "emails.magicSession.optionButton": "Click the button below to securely sign in to your {{b}}{{project}}{{/b}} account. This link will expire in 1 hour.", "emails.magicSession.buttonText": "Sign in to {{project}}", "emails.magicSession.optionUrl": "If you are unable to sign in using the button above, please visit the following link:", @@ -19,12 +19,12 @@ "emails.magicSession.thanks": "Thanks,", "emails.magicSession.signature": "{{project}} team", "emails.sessionAlert.subject": "Security alert: new session on your {{project}} account", - "emails.sessionAlert.hello":"Hello {{user}}", + "emails.sessionAlert.hello": "Hello {{user}},", "emails.sessionAlert.body": "A new session has been created on your {{b}}{{project}}{{/b}} account, {{b}}on {{date}}, {{year}} at {{time}} UTC{{/b}}.\nHere are the details of the new session: ", "emails.sessionAlert.listDevice": "Device: {{b}}{{device}}{{/b}}", "emails.sessionAlert.listIpAddress": "IP Address: {{b}}{{ipAddress}}{{/b}}", "emails.sessionAlert.listCountry": "Country: {{b}}{{country}}{{/b}}", - "emails.sessionAlert.footer": "If this was you, there's nothing more you need to do.\nIf you didn't initiate this session or suspect any unauthorized activity, please secure your account.", + "emails.sessionAlert.footer": "If this was you, there's nothing more you need to do.\nIf you didn't initiate this session or suspect any unauthorized activity, please secure your account.", "emails.sessionAlert.thanks": "Thanks,", "emails.sessionAlert.signature": "{{project}} team", "emails.otpSession.subject": "OTP for {{project}} Login", @@ -35,28 +35,28 @@ "emails.otpSession.thanks": "Thanks,", "emails.otpSession.signature": "{{project}} team", "emails.mfaChallenge.subject": "Verification Code for {{project}}", - "emails.mfaChallenge.hello": "Hello {{user}}", + "emails.mfaChallenge.hello": "Hello {{user}},", "emails.mfaChallenge.description": "Enter the following verification code to verify your email and activate two-step verification in {{b}}{{project}}{{/b}}. This code will expire in 15 minutes.", "emails.mfaChallenge.clientInfo": "This verification code was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the verification code, you can safely ignore this email.", "emails.mfaChallenge.thanks": "Thanks,", "emails.mfaChallenge.signature": "{{project}} team", "emails.recovery.subject": "Password Reset", - "emails.recovery.hello": "Hello {{user}}", + "emails.recovery.hello": "Hello {{user}},", "emails.recovery.body": "Follow this link to reset your {{b}}{{project}}{{/b}} password.", "emails.recovery.footer": "If you didn't ask to reset your password, you can ignore this message.", - "emails.recovery.thanks": "Thanks", + "emails.recovery.thanks": "Thanks,", "emails.recovery.signature": "{{project}} team", "emails.invitation.subject": "Invitation to %s Team at %s", - "emails.invitation.hello": "Hello {{user}}", + "emails.invitation.hello": "Hello {{user}},", "emails.invitation.body": "This mail was sent to you because {{b}}{{owner}}{{/b}} wanted to invite you to become a member of the {{b}}{{team}}{{/b}} team at {{b}}{{project}}{{/b}}.", "emails.invitation.footer": "If you are not interested, you can ignore this message.", - "emails.invitation.thanks": "Thanks", + "emails.invitation.thanks": "Thanks,", "emails.invitation.signature": "{{project}} team", "emails.certificate.subject": "Certificate failure for %s", - "emails.certificate.hello": "Hello", + "emails.certificate.hello": "Hello,", "emails.certificate.body": "Certificate for your domain '{{domain}}' could not be generated. This is attempt no. {{attempt}}, and the failure was caused by: {{error}}", "emails.certificate.footer": "Your previous certificate will be valid for 30 days since the first failure. We highly recommend investigating this case, otherwise your domain will end up without a valid SSL communication.", - "emails.certificate.thanks": "Thanks", + "emails.certificate.thanks": "Thanks,", "emails.certificate.signature": "{{project}} team", "sms.verification.body": "{{secret}}", "locale.country.unknown": "Unknown", diff --git a/app/config/locale/translations/eo.json b/app/config/locale/translations/eo.json index 406bd4f52c..ba80bc602d 100644 --- a/app/config/locale/translations/eo.json +++ b/app/config/locale/translations/eo.json @@ -3,28 +3,28 @@ "settings.direction": "ltr", "emails.sender": "Teamo %s", "emails.verification.subject": "Konta Konfirmo", - "emails.verification.hello": "Saluton {{user}}", + "emails.verification.hello": "Saluton {{user}},", "emails.verification.body": "Alklaku ĉi tiun ligon por kontroli vian retpoŝtan adreson.", "emails.verification.footer": "Se vi ne petis ĉi tiun konfirmon de ĉi tiu retpoŝto, vi povas ignori ĉi tiun mesaĝon.", - "emails.verification.thanks": "Dankegon.", + "emails.verification.thanks": "Dankegon.,", "emails.verification.signature": "Teamo {{project}}", "emails.magicSession.subject": "Login", - "emails.magicSession.hello": "Saluton", + "emails.magicSession.hello": "Saluton,", "emails.magicSession.body": "Alklaku ĉi tiun ligon por eniri.", "emails.magicSession.footer": "Se vi ne petis ĉi tiun konfirmon de ĉi tiu retpoŝto, vi povas ignori ĉi tiun mesaĝon.", - "emails.magicSession.thanks": "Dankegon", + "emails.magicSession.thanks": "Dankegon,", "emails.magicSession.signature": "Teamo {{project}}", "emails.recovery.subject": "Parsvorta Restarigo", - "emails.recovery.hello": "Saluton {{user}}", + "emails.recovery.hello": "Saluton {{user}},", "emails.recovery.body": "Alklaku ĉi tiun ligon por reagordi vian pasvorton. {{project}}", "emails.recovery.footer": "Se vi ne petis reagordi vian pasvorton, vi povas ignori ĉi tiun mesaĝon.", - "emails.recovery.thanks": "Dankegon", + "emails.recovery.thanks": "Dankegon,", "emails.recovery.signature": "Teamo {{project}}", "emails.invitation.subject": "Invito al la Teamo %s em %s", - "emails.invitation.hello": "Dankegon", + "emails.invitation.hello": "Dankegon,", "emails.invitation.body": "Ĉi tiu retpoŝto estis sendita ĉar la {{owner}} volas inviti vin fariĝi membro de la Teamo {{team}} en {{project}}.", "emails.invitation.footer": "Se vi ne interesiĝas, vi povas ignori ĉi tiun mesaĝon.", - "emails.invitation.thanks": "Dankegon", + "emails.invitation.thanks": "Dankegon,", "emails.invitation.signature": "Teamo {{project}}", "locale.country.unknown": "Unknown", "countries.af": "Afghanistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Sekureca frazo por ĉi tiu retpoŝto estas {{phrase}}. Vi povas fidi ĉi tiun retpoŝton se tiu ĉi frazo kongruas kun la frazo montrita dum ensaluto.", "emails.otpSession.thanks": "Dankon,", "emails.otpSession.signature": "teamo de {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/es.json b/app/config/locale/translations/es.json index 0e02f816fe..ff98fd28c7 100644 --- a/app/config/locale/translations/es.json +++ b/app/config/locale/translations/es.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "El equipo de %s", "emails.verification.subject": "Verificación de cuenta", - "emails.verification.hello": "Hola, {{name}}.", + "emails.verification.hello": "Hola, {{name}}.,", "emails.verification.body": "Haz clic en este enlace para verificar tu correo:", "emails.verification.footer": "Si no has solicitado verificar este correo, puedes ignorar este mensaje.", - "emails.verification.thanks": "Gracias.", + "emails.verification.thanks": "Gracias.,", "emails.verification.signature": "El equipo de {{project}}.", "emails.magicSession.subject": "Inicio de sesión", - "emails.magicSession.hello": "Hola", + "emails.magicSession.hello": "Hola,", "emails.magicSession.body": "Haz clic en este enlace para iniciar sesión:", "emails.magicSession.footer": "Si no has solicitado iniciar sesión usando este correo, puedes ignorar este mensaje.", - "emails.magicSession.thanks": "Gracias.", + "emails.magicSession.thanks": "Gracias.,", "emails.magicSession.signature": "El equipo de {{project}}", "emails.recovery.subject": "Restablecer contraseña", - "emails.recovery.hello": "Hola, {{name}}.", + "emails.recovery.hello": "Hola, {{name}}.,", "emails.recovery.body": "Haz clic en este enlace para restablecer la contraseña de {{project}}:", "emails.recovery.footer": "Si no has solicitado restablecer la contraseña, puedes ignorar este mensaje.", - "emails.recovery.thanks": "Gracias.", + "emails.recovery.thanks": "Gracias.,", "emails.recovery.signature": "El equipo de {{project}}", "emails.invitation.subject": "Invitación al equipo %s en %s", - "emails.invitation.hello": "Hola", + "emails.invitation.hello": "Hola,", "emails.invitation.body": "Este correo ha sido enviado a petición de {{owner}} quién quiere invitarte a formar parte del equipo {{team}} en {{project}}.", "emails.invitation.footer": "Si no estás interesado, puedes ignorar este mensaje.", - "emails.invitation.thanks": "Gracias.", + "emails.invitation.thanks": "Gracias.,", "emails.invitation.signature": "El equipo de {{project}}", "locale.country.unknown": "Desconocido", "countries.af": "Afganistán", @@ -239,10 +239,10 @@ "emails.magicSession.securityPhrase": "La frase de seguridad para este correo electrónico es {{phrase}}. Puedes confiar en este correo electrónico si esta frase coincide con la frase que se muestra durante el inicio de sesión.", "emails.magicSession.optionUrl": "Si no puedes iniciar sesión utilizando el botón anterior, visita el siguiente enlace:", "emails.otpSession.subject": "Inicio de sesión en {{project}}", - "emails.otpSession.hello": "Hola", + "emails.otpSession.hello": "Hola,", "emails.otpSession.description": "Ingrese el siguiente código de verificación cuando se le solicite para iniciar sesión de forma segura en su cuenta de {{project}}. Expirará en 15 minutos.", "emails.otpSession.clientInfo": "Este inicio de sesión fue solicitado usando {{agentClient}} en {{agentDevice}} {{agentOs}}. Si no solicitaste el inicio de sesión, puedes ignorar este correo electrónico de forma segura.", "emails.otpSession.securityPhrase": "La frase de seguridad para este correo electrónico es {{phrase}}. Puedes confiar en este correo si esta frase coincide con la frase mostrada durante el inicio de sesión.", - "emails.otpSession.thanks": "Gracias.", + "emails.otpSession.thanks": "Gracias.,", "emails.otpSession.signature": "El equipo de {{project}}" } diff --git a/app/config/locale/translations/fa.json b/app/config/locale/translations/fa.json index 9620b2c3f0..f826a75118 100644 --- a/app/config/locale/translations/fa.json +++ b/app/config/locale/translations/fa.json @@ -4,28 +4,28 @@ "settings.direction": "rtl", "emails.sender": "تیم %s", "emails.verification.subject": "تأیید حساب", - "emails.verification.hello": "سلام {{user}}", + "emails.verification.hello": "سلام {{user}}،", "emails.verification.body": "برای تأیید ایمیل‌تان پیوند زیر را دنبال کنید.", "emails.verification.footer": "اگر شما درخواست تأیید حساب نداده‌اید، می‌توانید این پیام را نادیده بگیرید.", - "emails.verification.thanks": "سپاس فراوان", + "emails.verification.thanks": "سپاس فراوان،", "emails.verification.signature": "تیم {{user}}", "emails.magicSession.subject": "ورود به حساب کاربری", "emails.magicSession.hello": "سلام،", "emails.magicSession.body": "برای ورود به حساب‌تان پیوند زیر را دنبال کنید.", "emails.magicSession.footer": "اگر شما درخواست ورود به حساب کاربری با استفاده از این ایمیل را نداد‌ه‌اید، می‌توانید این پیام را نادیده بگیرید.", - "emails.magicSession.thanks": "سپاس فراوان", + "emails.magicSession.thanks": "سپاس فراوان،", "emails.magicSession.signature": "تیم {{user}}", "emails.recovery.subject": "بازیابی گذرواژه", - "emails.recovery.hello": "سلام {{user}}", + "emails.recovery.hello": "سلام {{user}}،", "emails.recovery.body": "برای بازیابی گذرواژه‌تان پیوند زیر را دنبال کنید.", "emails.recovery.footer": "اگر شما درخواست بازیابی گذرواژه نداده‌اید، می‌توانید این پیام را نادیده بگیرید.", - "emails.recovery.thanks": "سپاس فراوان", + "emails.recovery.thanks": "سپاس فراوان،", "emails.recovery.signature": "تیم {{user}}", "emails.invitation.subject": "دعوت به تیم %s در %s", - "emails.invitation.hello": "سلام", + "emails.invitation.hello": "سلام،", "emails.invitation.body": "این ایمیل برای شما فرستاده شده‌است زیرا {{owner}} می‌خواهد شما را به تیم {{team}} در پروژه‌ی {{project}} بیفزاید.", "emails.invitation.footer": "اگر علاقه ندارید، می‌توانید این پیام را نادیده بگیرید.", - "emails.invitation.thanks": "سپاس فراوان", + "emails.invitation.thanks": "سپاس فراوان،", "emails.invitation.signature": "تیم {{user}}", "locale.country.unknown": "ناشناخته", "countries.af": "افغانستان", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "عبارت امنیتی برای این ایمیل {{phrase}} است. اگر این عبارت با عبارت نشان داده شده هنگام ورود به سیستم مطابقت داشته باشد، می‌توانید به این ایمیل اعتماد کنید.", "emails.otpSession.thanks": "متشکرم،", "emails.otpSession.signature": "تیم {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/fi.json b/app/config/locale/translations/fi.json index d86810d925..ca61a95653 100644 --- a/app/config/locale/translations/fi.json +++ b/app/config/locale/translations/fi.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s Tiimi", "emails.verification.subject": "", - "emails.verification.hello": "", + "emails.verification.hello": ",", "emails.verification.body": "", "emails.verification.footer": "", - "emails.verification.thanks": "", + "emails.verification.thanks": ",", "emails.verification.signature": "", "emails.magicSession.subject": "", - "emails.magicSession.hello": "", + "emails.magicSession.hello": ",", "emails.magicSession.body": "", "emails.magicSession.footer": "", - "emails.magicSession.thanks": "", + "emails.magicSession.thanks": ",", "emails.magicSession.signature": "", "emails.recovery.subject": "", - "emails.recovery.hello": "", + "emails.recovery.hello": ",", "emails.recovery.body": "", "emails.recovery.footer": "", - "emails.recovery.thanks": "", + "emails.recovery.thanks": ",", "emails.recovery.signature": "", "emails.invitation.subject": "", - "emails.invitation.hello": "", + "emails.invitation.hello": ",", "emails.invitation.body": "", "emails.invitation.footer": "", - "emails.invitation.thanks": "", + "emails.invitation.thanks": ",", "emails.invitation.signature": "", "locale.country.unknown": "Unknown", "countries.af": "Afganistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Tämän sähköpostin turvalause on {{phrase}}. Voit luottaa tähän sähköpostiin, jos tämä lause vastaa kirjautumisen yhteydessä näytettyä lausetta.", "emails.otpSession.thanks": "Kiitos,", "emails.otpSession.signature": "{{project}} -tiimi" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/fo.json b/app/config/locale/translations/fo.json index 3853dbab9f..a982fd0590 100644 --- a/app/config/locale/translations/fo.json +++ b/app/config/locale/translations/fo.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s Lið", "emails.verification.subject": "", - "emails.verification.hello": "", + "emails.verification.hello": ",", "emails.verification.body": "", "emails.verification.footer": "", - "emails.verification.thanks": "", + "emails.verification.thanks": ",", "emails.verification.signature": "", "emails.magicSession.subject": "", - "emails.magicSession.hello": "", + "emails.magicSession.hello": ",", "emails.magicSession.body": "", "emails.magicSession.footer": "", - "emails.magicSession.thanks": "", + "emails.magicSession.thanks": ",", "emails.magicSession.signature": "", "emails.recovery.subject": "", - "emails.recovery.hello": "", + "emails.recovery.hello": ",", "emails.recovery.body": "", "emails.recovery.footer": "", - "emails.recovery.thanks": "", + "emails.recovery.thanks": ",", "emails.recovery.signature": "", "emails.invitation.subject": "", - "emails.invitation.hello": "", + "emails.invitation.hello": ",", "emails.invitation.body": "", "emails.invitation.footer": "", - "emails.invitation.thanks": "", + "emails.invitation.thanks": ",", "emails.invitation.signature": "", "locale.country.unknown": "Ókjent", "countries.af": "Afghanistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Trygdarorðið fyri hesa teldupostin er {{phrase}}. Tú kanst líta á hesa teldupostin um hetta orðið passar við orðið víst tá tú ritaði inn.", "emails.otpSession.thanks": "Takk,", "emails.otpSession.signature": "{{project}} lið" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/fr.json b/app/config/locale/translations/fr.json index d73f4e7c81..1b60cb1910 100644 --- a/app/config/locale/translations/fr.json +++ b/app/config/locale/translations/fr.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "Équipe %s", "emails.verification.subject": "Vérification du compte", - "emails.verification.hello": "Bonjour {{user}}", + "emails.verification.hello": "Bonjour {{user}},", "emails.verification.body": "Suivez ce lien pour vérifier votre adresse e-mail.", "emails.verification.footer": "Si vous n'avez pas demandé à vérifier cette adresse, vous pouvez ignorer ce message.", - "emails.verification.thanks": "Merci", + "emails.verification.thanks": "Merci,", "emails.verification.signature": "Équipe {{project}}", "emails.magicSession.subject": "Connexion", - "emails.magicSession.hello": "Bonjour", + "emails.magicSession.hello": "Bonjour,", "emails.magicSession.body": "Suivez ce lien pour vous connecter.", "emails.magicSession.footer": "Si vous n'avez pas demandé à vous connecter en utilisant cet e-mail, vous pouvez ignorer ce message.", - "emails.magicSession.thanks": "Merci", + "emails.magicSession.thanks": "Merci,", "emails.magicSession.signature": "L'équipe {{project}}", "emails.recovery.subject": "Réinitialisation du mot de passe", - "emails.recovery.hello": "Bonjour {{user}}", + "emails.recovery.hello": "Bonjour {{user}},", "emails.recovery.body": "Suivez ce lien pour réinitialiser votre mot de passe pour {{project}}.", "emails.recovery.footer": "Si vous n'avez pas demandé à réinitialiser votre mot de passe, vous pouvez ignorer ce message.", - "emails.recovery.thanks": "Merci", + "emails.recovery.thanks": "Merci,", "emails.recovery.signature": "L'équipe {{project}}", "emails.invitation.subject": "Invitation à l'équipe %s de %s", - "emails.invitation.hello": "Bonjour", + "emails.invitation.hello": "Bonjour,", "emails.invitation.body": "Cet e-mail vous a été envoyé parce que {{owner}} souhaite vous inviter à devenir membre de l'équipe {{team}} pour {{project}}.", "emails.invitation.footer": "Si vous n'êtes pas intéressé, vous pouvez ignorer ce message.", - "emails.invitation.thanks": "Merci", + "emails.invitation.thanks": "Merci,", "emails.invitation.signature": "L'équipe {{project}}", "locale.country.unknown": "Inconnu", "countries.af": "Afghanistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "La phrase de sécurité pour cet e-mail est {{phrase}}. Vous pouvez faire confiance à cet e-mail si cette phrase correspond à celle affichée lors de la connexion.", "emails.otpSession.thanks": "Merci,", "emails.otpSession.signature": "équipe {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/ga.json b/app/config/locale/translations/ga.json index b7a641ac01..3ed68ad8c3 100644 --- a/app/config/locale/translations/ga.json +++ b/app/config/locale/translations/ga.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s Foireann", "emails.verification.subject": "Fíoraithe cuntais", - "emails.verification.hello": "Haigh {{user}}", + "emails.verification.hello": "Haigh {{user}},", "emails.verification.body": "Lean an nasc seo chun do ríomhphost a fhíorú.", "emails.verification.footer": "Mura ndearna tú iarratas an seoladh seo a fhíoru, déan neamhaird den teachtaireacht seo.", - "emails.verification.thanks": "Go raibh maith agat", + "emails.verification.thanks": "Go raibh maith agat,", "emails.verification.signature": "{{project}} foireann", "emails.magicSession.subject": "Logáil isteach", - "emails.magicSession.hello": "Haigh", + "emails.magicSession.hello": "Haigh,", "emails.magicSession.body": "Lean an nasc seo chun logáil isteach.", "emails.magicSession.footer": "Mura ndearna tú iarratas logáil isteach leis an ríomhphost seo, déan neamhaird den teachtaireacht seo.", - "emails.magicSession.thanks": "Go raibh maith agat", + "emails.magicSession.thanks": "Go raibh maith agat,", "emails.magicSession.signature": "{{project}} foireann", "emails.recovery.subject": "Athshocrú pasfhocail", - "emails.recovery.hello": "Haigh {{user}}", + "emails.recovery.hello": "Haigh {{user}},", "emails.recovery.body": "Lean an nasc seo chun do pasfhocal {{project}} a athshocrú.", "emails.recovery.footer": "Mura ndearna tú iarratas do pasfhocal a athshocrú, déan neamhaird den teachtaireacht seo.", - "emails.recovery.thanks": "Go raibh maith agat", + "emails.recovery.thanks": "Go raibh maith agat,", "emails.recovery.signature": "{{project}} foireann", "emails.invitation.subject": "Cuireadh do %s foireann ag %s", - "emails.invitation.hello": "Haigh", + "emails.invitation.hello": "Haigh,", "emails.invitation.body": "Seoladh an ríomhphost seo chugat mar ba mhaith le {{owner}} cuireadh a thabhairt duit bheith mar bhall den fhoireann {{team}} ag obair ar {{project}}.", "emails.invitation.footer": "Is cuma leat? Déan neamhaird den teachtaireacht seo.", - "emails.invitation.thanks": "Go raibh maith agat", + "emails.invitation.thanks": "Go raibh maith agat,", "emails.invitation.signature": "{{project}} foireann", "locale.country.unknown": "Neamhaithnid", "countries.af": "An Afganastáin", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Frása slándála don ríomhphost seo ná {{phrase}}. Is féidir muinín a bheith agat as an ríomhphost seo má mheaitseálann an frása seo leis an bhfrása a taispeántar le linn síniú isteach.", "emails.otpSession.thanks": "Go raibh maith agat,", "emails.otpSession.signature": "foireann {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/gu.json b/app/config/locale/translations/gu.json index 8c41c76c54..54378caa9e 100644 --- a/app/config/locale/translations/gu.json +++ b/app/config/locale/translations/gu.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s ટીમ", "emails.verification.subject": "ખાતાની ચકાસણી", - "emails.verification.hello": "નમસ્કાર {{user}}", + "emails.verification.hello": "નમસ્કાર {{user}},", "emails.verification.body": "તમારું ઇમેઇલ સરનામું ચકાસવા માટે આ લિંકને અનુસરો.", "emails.verification.footer": "જો તમે આ સરનામાંની ચકાસણી કરવાનું ન કહ્યું હોય, તો તમે આ સંદેશને અવગણી શકો છો.", - "emails.verification.thanks": "આભાર", + "emails.verification.thanks": "આભાર,", "emails.verification.signature": "{{project}} ટીમ", "emails.magicSession.subject": "પ્રવેશ કરો", - "emails.magicSession.hello": "નમસ્કાર", + "emails.magicSession.hello": "નમસ્કાર,", "emails.magicSession.body": "પ્રવેશ કરવા માટે આ લિંકને અનુસરો.", "emails.magicSession.footer": "જો તમે આ ઇમેઇલનો ઉપયોગ કરીને પ્રવેશ કરવાનું ન કહ્યું હોય, તો તમે આ સંદેશને અવગણી શકો છો.", - "emails.magicSession.thanks": "આભાર", + "emails.magicSession.thanks": "આભાર,", "emails.magicSession.signature": "{{project}} ટીમ", "emails.recovery.subject": "પાસવર્ડ ફરીથી સેટ કરો", - "emails.recovery.hello": "નમસ્કાર {{user}}", + "emails.recovery.hello": "નમસ્કાર {{user}},", "emails.recovery.body": "તમારો {{project}} પાસવર્ડ ફરીથી સેટ કરવા માટે આ લિંકને અનુસરો.", "emails.recovery.footer": "જો તમે તમારો પાસવર્ડ ફરીથી સેટ કરવાનું ન કહ્યું હોય, તો તમે આ સંદેશને અવગણી શકો છો.", - "emails.recovery.thanks": "આભાર", + "emails.recovery.thanks": "આભાર,", "emails.recovery.signature": "{{project}} ટીમ", "emails.invitation.subject": "%s ટીમને %s પર આમંત્રણ", - "emails.invitation.hello": "નમસ્કાર", + "emails.invitation.hello": "નમસ્કાર,", "emails.invitation.body": "આ મેઇલ તમને મોકલવામાં આવ્યો હતો કારણ કે {{owner}} તમને {{project}} માં {{team}} ટીમના સભ્ય બનવા માટે આમંત્રિત કરવા માંગતા હતો.", "emails.invitation.footer": "જો તમને રસ નથી, તો તમે આ સંદેશને અવગણી શકો છો.", - "emails.invitation.thanks": "આભાર", + "emails.invitation.thanks": "આભાર,", "emails.invitation.signature": "{{project}} ટીમ", "locale.country.unknown": "અજાણ", "countries.af": "અફઘાનિસ્તાન", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "આ ઇમેઇલ માટેનો સુરક્ષા શબ્દ {{phrase}} છે. જો આ શબ્દ સાઇન ઇન વખતે દર્શાવેલા શબ્દ સાથે મેળ ખાતો હોય તો તમે આ ઇમેઇલ પર વિશ્વાસ કરી શકો છો.", "emails.otpSession.thanks": "આભાર,", "emails.otpSession.signature": "{{project}} ટીમ" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/he.json b/app/config/locale/translations/he.json index 81705d3492..b3d4dea2a8 100644 --- a/app/config/locale/translations/he.json +++ b/app/config/locale/translations/he.json @@ -4,28 +4,28 @@ "settings.direction": "rtl", "emails.sender": "צוות %s", "emails.verification.subject": "אימות חשבון", - "emails.verification.hello": "שלום {{user}}", + "emails.verification.hello": "שלום {{user}},", "emails.verification.body": "לחץ על קישור זה כדי לאמת את כתובת הדוא\"ל שלך.", "emails.verification.footer": "אם לא ביקשת לאמת כתובת זו, תוכל להתעלם מהודעה זו.", - "emails.verification.thanks": "תודה", + "emails.verification.thanks": "תודה,", "emails.verification.signature": "צוות {{project}}", "emails.magicSession.subject": "כניסה למערכת", - "emails.magicSession.hello": "שלום", + "emails.magicSession.hello": "שלום,", "emails.magicSession.body": "לחץ על קישור זה כדי להיכנס.", "emails.magicSession.footer": "אם לא ביקשת להיכנס באמצעות דוא\"ל זה, תוכל להתעלם מהודעה זו.", - "emails.magicSession.thanks": "תודה", + "emails.magicSession.thanks": "תודה,", "emails.magicSession.signature": "צוות {{project}}", "emails.recovery.subject": "איפוס סיסמא", - "emails.recovery.hello": "שלום {{user}}", + "emails.recovery.hello": "שלום {{user}},", "emails.recovery.body": "עקוב אחר קישור זה כדי לאפס את סיסמתך ב-{{project}}.", "emails.recovery.footer": "אם לא ביקשת לאפס את הסיסמה, תוכל להתעלם מהודעה זו.", - "emails.recovery.thanks": "תודה", + "emails.recovery.thanks": "תודה,", "emails.recovery.signature": "צוות {{project}}", "emails.invitation.subject": "הזמנה לצוות %s ב- %s", - "emails.invitation.hello": "שלום", + "emails.invitation.hello": "שלום,", "emails.invitation.body": "דואר זה נשלח אליך מכיוון ש {{owner}} רצה להזמין אותך להיות חבר בצוות {{team}} ב-{{project}}.", "emails.invitation.footer": "אם אינך מעוניין, תוכל להתעלם מהודעה זו.", - "emails.invitation.thanks": "תודה", + "emails.invitation.thanks": "תודה,", "emails.invitation.signature": "צוות {{project}}", "locale.country.unknown": "לא ידוע", "countries.af": "אפגניסטן", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "משפט האבטחה למייל זה הוא {{phrase}}. אתה יכול לסמוך על המייל הזה אם המשפט הזה תואם את המשפט שהוצג במהלך הכניסה למערכת.", "emails.otpSession.thanks": "תודה,", "emails.otpSession.signature": "צוות {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/hi.json b/app/config/locale/translations/hi.json index d764205ad6..1c4d531d60 100644 --- a/app/config/locale/translations/hi.json +++ b/app/config/locale/translations/hi.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s टीम", "emails.verification.subject": "अकाउंट वेरिफिकेशन ", - "emails.verification.hello": "नमस्ते {{user}}", + "emails.verification.hello": "नमस्ते {{user}},", "emails.verification.body": "इस लिंक के माध्यम से अपने ईमेल को सत्यापित कीजिये।", "emails.verification.footer": "यदि आप इस पते को सत्यापित नहीं करना चाहते हैं, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।", - "emails.verification.thanks": "धन्यवाद", + "emails.verification.thanks": "धन्यवाद,", "emails.verification.signature": "{{project}} टीम", "emails.magicSession.subject": "लॉग इन", - "emails.magicSession.hello": "नमस्ते", + "emails.magicSession.hello": "नमस्ते,", "emails.magicSession.body": "इस लिंक के माध्यम से लॉग-इन करें।", "emails.magicSession.footer": "यदि आप इस ईमेल द्वारा लॉगिन नहीं करना चाहते हैं, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।", - "emails.magicSession.thanks": "धन्यवाद", + "emails.magicSession.thanks": "धन्यवाद,", "emails.magicSession.signature": "{{project}} टीम", "emails.recovery.subject": "पासवर्ड रीसेट", - "emails.recovery.hello": "नमस्ते {{user}}", + "emails.recovery.hello": "नमस्ते {{user}},", "emails.recovery.body": "इस लिंक के माध्यम से अपना {{project}} पासवर्ड रीसेट करें।", "emails.recovery.footer": "यदि आप अपना पासवर्ड रीसेट नहीं करना चाहते हैं, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।", - "emails.recovery.thanks": "धन्यवाद", + "emails.recovery.thanks": "धन्यवाद,", "emails.recovery.signature": "{{project}} टीम", "emails.invitation.subject": "%s टीम का यहाँ %s पर आमंत्रण", - "emails.invitation.hello": "नमस्ते", + "emails.invitation.hello": "नमस्ते,", "emails.invitation.body": "यह मेल आपको इसलिए भेजा गया है क्योंकि {{owner}} आपको {{team}} टीम का सदस्य बनाना चाहते है, जो {{project}} से जुड़ा हुआ है।", "emails.invitation.footer": "यदि आप इसमें रूचि नहीं रखते, तो आप इस संदेश को नज़रअंदाज़ कर सकते हैं।", - "emails.invitation.thanks": "धन्यवाद", + "emails.invitation.thanks": "धन्यवाद,", "emails.invitation.signature": "{{project}} टीम", "locale.country.unknown": "अज्ञात", "countries.af": "अफ़ग़ानिस्तान", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "इस ईमेल के लिए सुरक्षा वाक्यांश {{phrase}} है। अगर यह वाक्यांश साइन इन के दौरान दिखाए गए वाक्यांश से मेल खाता है, तो आप इस ईमेल पर भरोसा कर सकते हैं।", "emails.otpSession.thanks": "धन्यवाद,", "emails.otpSession.signature": "{{project}} टीम" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/hr.json b/app/config/locale/translations/hr.json index 8e537f12ec..e5bf4719a9 100644 --- a/app/config/locale/translations/hr.json +++ b/app/config/locale/translations/hr.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s Tim", "emails.verification.subject": "Verifikacija računa", - "emails.verification.hello": "Pozdrav {{user}}", + "emails.verification.hello": "Pozdrav {{user}},", "emails.verification.body": "Slijedite ovu poveznicu da biste potvrdili svoju adresu e-pošte.", "emails.verification.footer": "Ukoliko niste zatražili potvrdu ove adrese, možete zanemariti ovu poruku.", - "emails.verification.thanks": "Hvala", + "emails.verification.thanks": "Hvala,", "emails.verification.signature": "{{project}} tim", "emails.magicSession.subject": "Prijavite se", - "emails.magicSession.hello": "Pozdrav", + "emails.magicSession.hello": "Pozdrav,", "emails.magicSession.body": "Slijedite ovu poveznicu za prijavu.", "emails.magicSession.footer": "Ako niste zatražili prijavu putem ove e-pošte, možete zanemariti ovu poruku.", - "emails.magicSession.thanks": "Hvala", + "emails.magicSession.thanks": "Hvala,", "emails.magicSession.signature": "{{project}} tim", "emails.recovery.subject": "Ponovno postavljanje lozinke", - "emails.recovery.hello": "Pozdrav {{user}}", + "emails.recovery.hello": "Pozdrav {{user}},", "emails.recovery.body": "Slijedite ovu poveznicu za ponovno postavljanje {{project}} lozinke.", "emails.recovery.footer": "Ako niste zatražili ponovno postavljanje Vaše lozinke, možete zanemariti ovu poruku.", - "emails.recovery.thanks": "Hvala", + "emails.recovery.thanks": "Hvala,", "emails.recovery.signature": "{{project}} tim", "emails.invitation.subject": "Pozivnica za %s tim na %s", - "emails.invitation.hello": "Pozdrav", + "emails.invitation.hello": "Pozdrav,", "emails.invitation.body": "Ova poruka Vam je poslana jer Vas je {{owner}} htio pozvati da postanete član {{team}} tima na {{project}}.", "emails.invitation.footer": "Ukoliko niste zainteresirani, možete zanemariti ovu poruku.", - "emails.invitation.thanks": "Hvala", + "emails.invitation.thanks": "Hvala,", "emails.invitation.signature": "{{project}} tim", "locale.country.unknown": "Nepoznato", "countries.af": "Afganistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Sigurnosna fraza za ovaj e-mail je {{phrase}}. Možete vjerovati ovom e-mailu ako se ova fraza podudara s frazom prikazanom prilikom prijave.", "emails.otpSession.thanks": "Hvala,", "emails.otpSession.signature": "tim {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/hu.json b/app/config/locale/translations/hu.json index fded03aee1..589cb61859 100644 --- a/app/config/locale/translations/hu.json +++ b/app/config/locale/translations/hu.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s Csapat", "emails.verification.subject": "Fiók Megerősítése", - "emails.verification.hello": "Szia {{user}}", + "emails.verification.hello": "Szia {{user}},", "emails.verification.body": "Kattints a linkre, hogy megerősítsd az email címedet.", "emails.verification.footer": "Ha nem te kérted a címed megerősítését, akkor nyugodtan hagyd figyelmen kívül ezt az üzenetet.", - "emails.verification.thanks": "Köszönettel", + "emails.verification.thanks": "Köszönettel,", "emails.verification.signature": "a {{project}} csapat", "emails.magicSession.subject": "Bejelentkezés", - "emails.magicSession.hello": "Szia", + "emails.magicSession.hello": "Szia,", "emails.magicSession.body": "Kattints a linkre a bejelentkezéshez.", "emails.magicSession.footer": "Ha nem te szerettél volna bejelentkezni ezzel az email címmel, akkor nyugodtan hagyd figyelmen kívül ezt az üzenetet.", - "emails.magicSession.thanks": "Köszönettel", + "emails.magicSession.thanks": "Köszönettel,", "emails.magicSession.signature": "a {{project}} csapat", "emails.recovery.subject": "Jelszó Visszaállítása", - "emails.recovery.hello": "Hahó, {{user}}", + "emails.recovery.hello": "Hahó, {{user}},", "emails.recovery.body": "Kattints a linkre a {{project}} jelszavad visszaállításához.", "emails.recovery.footer": "Ha nem te kezdeményezted a jelszavad visszaállítását, akkor nyugodtan hagyd figyelmen kívül ezt az üzenetet.", - "emails.recovery.thanks": "Köszönettel", + "emails.recovery.thanks": "Köszönettel,", "emails.recovery.signature": "a {{project}} csapat", "emails.invitation.subject": "Meghívó a(z) %s csapatba, a(z) %s projektbe", - "emails.invitation.hello": "Szia", + "emails.invitation.hello": "Szia,", "emails.invitation.body": "Ezt a levelet azért kaptad, mert {{owner}} meghívott, hogy légy a {{team}} csapat tagja a {{project}} projektben.", "emails.invitation.footer": "Ha nem érdekel a lehetőség, nyugodtan hagyd figyelmen kívül ezt az üzenetet.", - "emails.invitation.thanks": "Köszönettel", + "emails.invitation.thanks": "Köszönettel,", "emails.invitation.signature": "a {{project}} csapat", "locale.country.unknown": "Ismeretlen", "countries.af": "Afganisztán", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Az e-mailhez tartozó biztonsági kód: {{phrase}}. Ennek az e-mailnek megbízhat, ha a megjelenített kód megegyezik a bejelentkezéskor megjelenő kóddal.", "emails.otpSession.thanks": "Köszönöm,", "emails.otpSession.signature": "{{project}} csapat" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/hy.json b/app/config/locale/translations/hy.json index 2c4e72ebe3..c845526607 100644 --- a/app/config/locale/translations/hy.json +++ b/app/config/locale/translations/hy.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "Թիմ %s", "emails.verification.subject": "", - "emails.verification.hello": "", + "emails.verification.hello": ",", "emails.verification.body": "", "emails.verification.footer": "", - "emails.verification.thanks": "", + "emails.verification.thanks": ",", "emails.verification.signature": "", "emails.magicSession.subject": "", - "emails.magicSession.hello": "", + "emails.magicSession.hello": ",", "emails.magicSession.body": "", "emails.magicSession.footer": "", - "emails.magicSession.thanks": "", + "emails.magicSession.thanks": ",", "emails.magicSession.signature": "", "emails.recovery.subject": "", - "emails.recovery.hello": "", + "emails.recovery.hello": ",", "emails.recovery.body": "", "emails.recovery.footer": "", - "emails.recovery.thanks": "", + "emails.recovery.thanks": ",", "emails.recovery.signature": "", "emails.invitation.subject": "", - "emails.invitation.hello": "", + "emails.invitation.hello": ",", "emails.invitation.body": "", "emails.invitation.footer": "", - "emails.invitation.thanks": "", + "emails.invitation.thanks": ",", "emails.invitation.signature": "", "locale.country.unknown": "Անհայտ", "countries.af": "Աֆղանստան", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Այս էլ.փոստի անվտանգության արտահայտությունը {{phrase}} է: Կարող եք վստահել այս էլ.փոստին, եթե այս արտահայտությունը համընկնում է մուտք գործելու պահին տրված արտահայտության հետ։", "emails.otpSession.thanks": "Շնորհակալ եմ,", "emails.otpSession.signature": "{{project}} թիմ" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/id.json b/app/config/locale/translations/id.json index 90b096e39e..c28b15f15d 100644 --- a/app/config/locale/translations/id.json +++ b/app/config/locale/translations/id.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "Tim %s", "emails.verification.subject": "Verifikasi Akun", - "emails.verification.hello": "Hai {{user}}", + "emails.verification.hello": "Hai {{user}},", "emails.verification.body": "Ikuti tautan ini untuk memverifikasi alamat email Anda.", "emails.verification.footer": "Jika Anda tidak meminta untuk memverifikasi alamat email ini, Anda dapat mengabaikan pesan ini.", - "emails.verification.thanks": "Terima kasih", + "emails.verification.thanks": "Terima kasih,", "emails.verification.signature": "Tim {{project}}", "emails.magicSession.subject": "Masuk", - "emails.magicSession.hello": "Hai", + "emails.magicSession.hello": "Hai,", "emails.magicSession.body": "Ikuti tautan ini untuk masuk.", "emails.magicSession.footer": "Jika Anda tidak meminta untuk masuk menggunakan email ini, Anda dapat mengabaikan pesan ini.", - "emails.magicSession.thanks": "Terima kasih", + "emails.magicSession.thanks": "Terima kasih,", "emails.magicSession.signature": "Tim {{project}}", "emails.recovery.subject": "Atur Ulang Kata Sandi", - "emails.recovery.hello": "Halo {{user}}", + "emails.recovery.hello": "Halo {{user}},", "emails.recovery.body": "Ikuti tautan ini untuk menyetel ulang kata sandi {{project}} Anda.", "emails.recovery.footer": "Jika Anda tidak meminta untuk menyetel ulang kata sandi, Anda dapat mengabaikan pesan ini.", - "emails.recovery.thanks": "Terima kasih", + "emails.recovery.thanks": "Terima kasih,", "emails.recovery.signature": "Tim {{project}}", "emails.invitation.subject": "Undangan ke Tim %s di %s", - "emails.invitation.hello": "Halo", + "emails.invitation.hello": "Halo,", "emails.invitation.body": "Email ini dikirimkan kepada Anda karena {{owner}} ingin mengundang Anda untuk menjadi anggota tim {{team}} di {{project}}.", "emails.invitation.footer": "Jika Anda tidak tertarik, Anda dapat mengabaikan pesan ini.", - "emails.invitation.thanks": "Terima kasih", + "emails.invitation.thanks": "Terima kasih,", "emails.invitation.signature": "Tim {{project}}", "locale.country.unknown": "Tidak diketahui", "countries.af": "Afganistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Frasa keamanan untuk email ini adalah {{phrase}}. Anda dapat mempercayai email ini jika frasa tersebut cocok dengan frasa yang ditampilkan saat masuk.", "emails.otpSession.thanks": "Terima kasih,", "emails.otpSession.signature": "tim {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/is.json b/app/config/locale/translations/is.json index 039d6849b7..5fede4dda0 100644 --- a/app/config/locale/translations/is.json +++ b/app/config/locale/translations/is.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s Teymi", "emails.verification.subject": "", - "emails.verification.hello": "", + "emails.verification.hello": ",", "emails.verification.body": "", "emails.verification.footer": "", - "emails.verification.thanks": "", + "emails.verification.thanks": ",", "emails.verification.signature": "", "emails.magicSession.subject": "", - "emails.magicSession.hello": "", + "emails.magicSession.hello": ",", "emails.magicSession.body": "", "emails.magicSession.footer": "", - "emails.magicSession.thanks": "", + "emails.magicSession.thanks": ",", "emails.magicSession.signature": "", "emails.recovery.subject": "", - "emails.recovery.hello": "", + "emails.recovery.hello": ",", "emails.recovery.body": "", "emails.recovery.footer": "", - "emails.recovery.thanks": "", + "emails.recovery.thanks": ",", "emails.recovery.signature": "", "emails.invitation.subject": "", - "emails.invitation.hello": "", + "emails.invitation.hello": ",", "emails.invitation.body": "", "emails.invitation.footer": "", - "emails.invitation.thanks": "", + "emails.invitation.thanks": ",", "emails.invitation.signature": "", "locale.country.unknown": "Óþekktur", "countries.af": "Afganistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Öryggissetning fyrir þetta tölvupóst er {{phrase}}. Þú getur treyst þessum tölvupósti ef þessi setning passar við setninguna sem birtist við innskráningu.", "emails.otpSession.thanks": "Takk,", "emails.otpSession.signature": "{{project}} liðið" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/it.json b/app/config/locale/translations/it.json index d0716dd1e2..8d45de9903 100644 --- a/app/config/locale/translations/it.json +++ b/app/config/locale/translations/it.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "Team %s", "emails.verification.subject": "Verifica account", - "emails.verification.hello": "Ciao {{user}}", + "emails.verification.hello": "Ciao {{user}},", "emails.verification.body": "Clicca questo link per verificare il tuo indirizzo email.", "emails.verification.footer": "Se non hai richiesto la verifica dell’indirizzo email, puoi ignorare questo messaggio.", - "emails.verification.thanks": "Grazie", + "emails.verification.thanks": "Grazie,", "emails.verification.signature": "Il team {{project}}", "emails.magicSession.subject": "Login", - "emails.magicSession.hello": "Ciao", + "emails.magicSession.hello": "Ciao,", "emails.magicSession.body": "Clicca questo link per accedere.", "emails.magicSession.footer": "Se non hai richiesto di effettuare l’accesso, puoi ignorare questo messaggio.", - "emails.magicSession.thanks": "Grazie", + "emails.magicSession.thanks": "Grazie,", "emails.magicSession.signature": "Il team {{project}}", "emails.recovery.subject": "Reimpostazione password", - "emails.recovery.hello": "Ciao {{user}}", + "emails.recovery.hello": "Ciao {{user}},", "emails.recovery.body": "Clicca questo link per reimpostare la tua password di {{project}}.", "emails.recovery.footer": "Se non hai richiesto la reimpostazione della password, puoi ignorare questo messaggio.", - "emails.recovery.thanks": "Grazie", + "emails.recovery.thanks": "Grazie,", "emails.recovery.signature": "Il team {{project}}", "emails.invitation.subject": "Invito al Team %s per %s", - "emails.invitation.hello": "Ciao", + "emails.invitation.hello": "Ciao,", "emails.invitation.body": "Hai ricevuto questa email perché {{owner}} ti ha invitato a diventare un membro del team {{team}} di {{project}}.", "emails.invitation.footer": "Ignora questo messaggio se non sei interessatə.", - "emails.invitation.thanks": "Grazie", + "emails.invitation.thanks": "Grazie,", "emails.invitation.signature": "Il team {{project}}", "locale.country.unknown": "Sconosciuto", "countries.af": "Afghanistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "La frase di sicurezza per questa email è {{phrase}}. Puoi fidarti di questa email se questa frase corrisponde alla frase mostrata durante l'accesso.", "emails.otpSession.thanks": "Grazie,", "emails.otpSession.signature": "team {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/ja.json b/app/config/locale/translations/ja.json index 6482488cf5..76d9a0cb1f 100644 --- a/app/config/locale/translations/ja.json +++ b/app/config/locale/translations/ja.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s チーム", "emails.verification.subject": "アカウント認証", - "emails.verification.hello": "こんにちは{{user}}さん", + "emails.verification.hello": "こんにちは{{user}}さん、", "emails.verification.body": "メールアドレスを有効化するためには下記リンクをクリックして下さい。", "emails.verification.footer": "このメールに心当たりが無い場合は破棄をお願いいたします。", - "emails.verification.thanks": "ご利用いただきありがとうございます。", + "emails.verification.thanks": "ご利用いただきありがとうございます。、", "emails.verification.signature": "{{project}}チーム", "emails.magicSession.subject": "ログイン", - "emails.magicSession.hello": "こんにちは", + "emails.magicSession.hello": "こんにちは、", "emails.magicSession.body": "ログインするためには下記リンクをクリックしてください。", "emails.magicSession.footer": "このメールに心当たりが無い場合は破棄をお願いいたします。", - "emails.magicSession.thanks": "ご利用いただきありがとうございます。", + "emails.magicSession.thanks": "ご利用いただきありがとうございます。、", "emails.magicSession.signature": "{{project}}チーム", "emails.recovery.subject": "パスワードリセット", - "emails.recovery.hello": "こんにちは{{user}}さん", + "emails.recovery.hello": "こんにちは{{user}}さん、", "emails.recovery.body": "パスワードをリセットするためには下記リンクをクリックしてください。", "emails.recovery.footer": "このメールに心当たりが無い場合は破棄をお願いいたします。", - "emails.recovery.thanks": "ご利用いただきありがとうございます。", + "emails.recovery.thanks": "ご利用いただきありがとうございます。、", "emails.recovery.signature": "{{project}}チーム", "emails.invitation.subject": "%sチームへの招待が%sから来ました。", - "emails.invitation.hello": "こんにちは", + "emails.invitation.hello": "こんにちは、", "emails.invitation.body": "{{owner}}さんが{{project}}の{{team}}チームにあなたを招待しています。", "emails.invitation.footer": "このメールに心当たりが無い場合は破棄をお願いいたします。", - "emails.invitation.thanks": "ご利用いただきありがとうございます。", + "emails.invitation.thanks": "ご利用いただきありがとうございます。、", "emails.invitation.signature": "{{project}}チーム", "locale.country.unknown": "不明", "countries.af": "アフガニスタン", @@ -239,10 +239,10 @@ "emails.magicSession.securityPhrase": "このメールのセキュリティフレーズは{{phrase}}です。サインイン時に表示されたフレーズと一致する場合、このメールは信頼できます。", "emails.magicSession.optionUrl": "上記のボタンを使用してサインインすることができない場合は、次のリンクにアクセスしてください:", "emails.otpSession.subject": "プロジェクト ログイン", - "emails.otpSession.hello": "こんにちは。", + "emails.otpSession.hello": "こんにちは。、", "emails.otpSession.description": "以下の確認コードを入力して、{{project}}アカウントに安全にサインインしてください。このコードは15分後に失効します。", "emails.otpSession.clientInfo": "このサインインは、{{agentClient}} を使い {{agentDevice}} {{agentOs}} で要求されました。サインインを要求していない場合、このメールを無視しても安全です。", "emails.otpSession.securityPhrase": "このメールのセキュリティフレーズは{{phrase}}です。サインイン時に表示されたフレーズと一致する場合、このメールを信頼できます。", - "emails.otpSession.thanks": "ありがとうございます。", + "emails.otpSession.thanks": "ありがとうございます。、", "emails.otpSession.signature": "{{project}} チーム" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/jv.json b/app/config/locale/translations/jv.json index 7f91a8dcef..889e968b4d 100644 --- a/app/config/locale/translations/jv.json +++ b/app/config/locale/translations/jv.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "Tim %s", "emails.verification.subject": "Verifikasi Akun", - "emails.verification.hello": "Hai {{user}}", + "emails.verification.hello": "Hai {{user}},", "emails.verification.body": "Klik link iki kanggo verifikasi alamat email sampeyan.", "emails.verification.footer": "Yen sampeyan ora njaluk verifikasi alamat iki, sampeyan iso nglirwakake pesen iki.", - "emails.verification.thanks": "Matur nuwun", + "emails.verification.thanks": "Matur nuwun,", "emails.verification.signature": "Tim {{project}}", "emails.magicSession.subject": "Masuk", - "emails.magicSession.hello": "Hai", + "emails.magicSession.hello": "Hai,", "emails.magicSession.body": "Klik link iki kanggo masuk.", "emails.magicSession.footer": "Yen sampeyan ora njaluk masuk nggunakake alamat email iki, sampeyan iso nglirwakake pesen iki.", - "emails.magicSession.thanks": "Matur nuwun", + "emails.magicSession.thanks": "Matur nuwun,", "emails.magicSession.signature": "Tim {{project}}", "emails.recovery.subject": "Setel ulang sandi", - "emails.recovery.hello": "Halo {{user}}", + "emails.recovery.hello": "Halo {{user}},", "emails.recovery.body": "Klik link iki kanggo setel ulang sandi {{project}}.", "emails.recovery.footer": "Yen sampeyan ora njaluk setel ulang sandi, sampeyan iso nglirwakake pesen iki.", - "emails.recovery.thanks": "Matur nuwun", + "emails.recovery.thanks": "Matur nuwun,", "emails.recovery.signature": "Tim {{project}}", "emails.invitation.subject": "Undangan ke Tim %s di %s", - "emails.invitation.hello": "Halo", + "emails.invitation.hello": "Halo,", "emails.invitation.body": "Email iki dikirim menyang sampeyan amarga {{owner}} pengin ngajak sampeyan dadi anggota tim {{team}} di {{project}}.", "emails.invitation.footer": "Yen sampeyan ora tertarik, sampeyan iso nglirwakake pesen iki.", - "emails.invitation.thanks": "Matur nuwun", + "emails.invitation.thanks": "Matur nuwun,", "emails.invitation.signature": "Tim {{project}}", "locale.country.unknown": "Ora dingerteni", "countries.af": "Afghanistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Tembung keamanan kanggo email iki yaiku {{phrase}}. Sampeyan bisa percaya email iki menawa tembung kasebut cocog karo tembung sing ditampilake nalika mlebu.", "emails.otpSession.thanks": "Matur nuwun,", "emails.otpSession.signature": "tim {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/km.json b/app/config/locale/translations/km.json index 72c362a77c..301796f69e 100644 --- a/app/config/locale/translations/km.json +++ b/app/config/locale/translations/km.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "ក្រុម %s", "emails.verification.subject": "", - "emails.verification.hello": "", + "emails.verification.hello": ",", "emails.verification.body": "", "emails.verification.footer": "", - "emails.verification.thanks": "", + "emails.verification.thanks": ",", "emails.verification.signature": "", "emails.magicSession.subject": "", - "emails.magicSession.hello": "", + "emails.magicSession.hello": ",", "emails.magicSession.body": "", "emails.magicSession.footer": "", - "emails.magicSession.thanks": "", + "emails.magicSession.thanks": ",", "emails.magicSession.signature": "", "emails.recovery.subject": "", - "emails.recovery.hello": "", + "emails.recovery.hello": ",", "emails.recovery.body": "", "emails.recovery.footer": "", - "emails.recovery.thanks": "", + "emails.recovery.thanks": ",", "emails.recovery.signature": "", "emails.invitation.subject": "", - "emails.invitation.hello": "", + "emails.invitation.hello": ",", "emails.invitation.body": "", "emails.invitation.footer": "", - "emails.invitation.thanks": "", + "emails.invitation.thanks": ",", "emails.invitation.signature": "", "locale.country.unknown": "មិនស្គាល់", "countries.af": "អាហ្វហ្គានីស្ថាន", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "ឃ្លាសម្រាប់សុវត្ថិភាពអ៊ីមែលនេះគឺ {{phrase}}។ អ្នកអាចទុកចិត្តនូវអ៊ីមែលនេះប្រសិនបើឃ្លានេះត្រូវគ្នាជាមួយឃ្លាដែលបានបង្ហាញពេលចូលគណនី។", "emails.otpSession.thanks": "អរគុណ,", "emails.otpSession.signature": "ក្រុម {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/kn.json b/app/config/locale/translations/kn.json index 429958fc1a..ba57c21155 100644 --- a/app/config/locale/translations/kn.json +++ b/app/config/locale/translations/kn.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s ತಂಡ", "emails.verification.subject": "ಖಾತೆ ಪರಿಶೀಲನೆ", - "emails.verification.hello": "ನಮಸ್ಕಾರ {{user}}", + "emails.verification.hello": "ನಮಸ್ಕಾರ {{user}},", "emails.verification.body": "ನಿಮ್ಮ ಇಮೇಲ್ ವಿಳಾಸ ಪರಿಶೀಲನೆಗೆ ಈ ಲಿಂಕನ್ನು ಅನುಸರಿಸಿ", "emails.verification.footer": "ನೀವು ಇಮೇಲ್ ವಿಳಾಸ ಪರಿಶೀಲನೆಗೆ ಕೇಳದಿದ್ದರೆ, ಈ ಸಂದೇಶವನ್ನು ನಿರ್ಲಕ್ಷಿಸಿ", - "emails.verification.thanks": "ಧನ್ಯವಾದಗಳು", + "emails.verification.thanks": "ಧನ್ಯವಾದಗಳು,", "emails.verification.signature": "{{project}} ತಂಡ", "emails.magicSession.subject": "ಲಾಗಿನ್", - "emails.magicSession.hello": "ನಮಸ್ಕಾರ", + "emails.magicSession.hello": "ನಮಸ್ಕಾರ,", "emails.magicSession.body": "ಲಾಗಿನ್ ಮಾಡಲಿಕ್ಕೆ ಈ ಲಿಂಕನ್ನು ಅನುಸರಿಸಿ", "emails.magicSession.footer": "ನೀವು ಈ ಇಮೇಲನಿಂದ ಲಾಗಿನ್ ಮಾಡಲು ಕೇಳದಿದ್ದರೆ, ಈ ಸಂದೇಶವನ್ನು ನಿರ್ಲಕ್ಷಿಸಿ", - "emails.magicSession.thanks": "ಧನ್ಯವಾದಗಳು", + "emails.magicSession.thanks": "ಧನ್ಯವಾದಗಳು,", "emails.magicSession.signature": "{{project}} ತಂಡ", "emails.recovery.subject": "ಗುಪ್ತಪದ ಮರುಹೊಂದಿಸಿ", - "emails.recovery.hello": "ನಮಸ್ಕಾರ {{user}}", + "emails.recovery.hello": "ನಮಸ್ಕಾರ {{user}},", "emails.recovery.body": "ನಿಮ್ಮ {{project}} ಗುಪ್ತಪದವನ್ನು ಮರುಹೊಂದಿಸಲು ಈ ಲಿಂಕನ್ನು ಅನುಸರಿಸಿ", "emails.recovery.footer": "ನೀವು ಗುಪ್ತಪದವನ್ನು ಮರುಹೊಂದಿಸಲು ಕೇಳದಿದ್ದರೆ, ಈ ಸಂದೇಶವನ್ನು ನಿರ್ಲಕ್ಷಿಸಿ", - "emails.recovery.thanks": "ಧನ್ಯವಾದಗಳು", + "emails.recovery.thanks": "ಧನ್ಯವಾದಗಳು,", "emails.recovery.signature": "{{project}} ತಂಡ", "emails.invitation.subject": "%s ತಂಡಕ್ಕೆ %s ರಲ್ಲಿ ಆಹ್ವಾನ", - "emails.invitation.hello": "ನಮಸ್ಕಾರ", + "emails.invitation.hello": "ನಮಸ್ಕಾರ,", "emails.invitation.body": "ಈ ಇಮೇಲ್ ನಿಮಗೆ ಬಂದಿದೆ ಏಕೆಂದರೆ {{owner}} ನಿಮ್ಮನ್ನು {{team}} ತಂಡದ {{project}}ರಲ್ಲಿ ಸದಸ್ಯ ಆಗಲಿಕ್ಕೆ ಆಹ್ವಾನಿಸಿದ್ದಾರೆ", "emails.invitation.footer": "ನಿಮಗೆ ಆಸಕ್ತಿಯಿಲ್ಲದಿದ್ದರೆ, ಈ ಸಂದೇಶವನ್ನು ನಿರ್ಲಕ್ಷಿಸಿ", - "emails.invitation.thanks": "ಧನ್ಯವಾದಗಳು", + "emails.invitation.thanks": "ಧನ್ಯವಾದಗಳು,", "emails.invitation.signature": "{{project}} ತಂಡ", "locale.country.unknown": "Unknown", "countries.af": "ಅಫ್ಘಾನಿಸ್ತಾನ", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "ಈ ಇಮೇಲ್‌ಗೆ ಭದ್ರತಾ ಪದವು {{phrase}}. ನೀವು ಸೈನ್ ಇನ್ ಮಾಡುವಾಗ ತೋರಿಸಿದ ಪದವು ಇದರೊಂದಿಗೆ ಹೊಂದಿಕೊಂಡರೆ ಈ ಇಮೇಲನ್ನು ನೀವು ನಂಬಬಹುದು.", "emails.otpSession.thanks": "ಧನ್ಯವಾದಗಳು,", "emails.otpSession.signature": "ಪ್ರಾಜೆಕ್ಟ್ ತಂಡ" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/ko.json b/app/config/locale/translations/ko.json index 372b7f1664..c33c961130 100644 --- a/app/config/locale/translations/ko.json +++ b/app/config/locale/translations/ko.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s 팀", "emails.verification.subject": "계정 인증", - "emails.verification.hello": "안녕하세요 {{user}}님", + "emails.verification.hello": "안녕하세요 {{user}}님、", "emails.verification.body": "이메일 인증을 위해 링크를 클릭하여주세요.", "emails.verification.footer": "이메일 인증을 부탁하지 않으셨다면 이 메시지를 무시하여주세요.", - "emails.verification.thanks": "감사합니다", + "emails.verification.thanks": "감사합니다、", "emails.verification.signature": "{{project}} 팀", "emails.magicSession.subject": "로그인", - "emails.magicSession.hello": "안녕하세요", + "emails.magicSession.hello": "안녕하세요、", "emails.magicSession.body": "로그인 하시려면 링크를 클릭하여주세요.", "emails.magicSession.footer": "이 이메일 계정으로 로그인 신청을 하지 않으셨다면 이 메세지를 무시하여주세요.", - "emails.magicSession.thanks": "감사합니다", + "emails.magicSession.thanks": "감사합니다、", "emails.magicSession.signature": "{{project}} 팀", "emails.recovery.subject": "비밀번호 재설정", - "emails.recovery.hello": "안녕하세요 {{user}}님", + "emails.recovery.hello": "안녕하세요 {{user}}님、", "emails.recovery.body": "{{project}}의 비밀번호 재설정을 위해 링크를 클릭하여주세요.", "emails.recovery.footer": "비밀번호 재설정 신청을 하지 않으셨다면 이 메세지를 무시하여주세요.", - "emails.recovery.thanks": "감사합니다", + "emails.recovery.thanks": "감사합니다、", "emails.recovery.signature": "{{project}} 팀", "emails.invitation.subject": "초대장 %s 팀 - %s", - "emails.invitation.hello": "안녕하세요", + "emails.invitation.hello": "안녕하세요、", "emails.invitation.body": "{{owner}}님이 귀하를 {{project}}의 {{team}} 팀으로 초대합니다.", "emails.invitation.footer": "팀에 합류할 의사가 없으시면 이 메세지를 무시하여주세요.", - "emails.invitation.thanks": "감사합니다", + "emails.invitation.thanks": "감사합니다、", "emails.invitation.signature": "{{project}} 팀", "locale.country.unknown": "알려지지 않은", "countries.af": "아프가니스탄", @@ -239,10 +239,10 @@ "emails.magicSession.securityPhrase": "이 이메일의 보안 구절은 {{phrase}}입니다. 로그인할 때 표시되는 구절과 일치한다면 이 이메일을 신뢰할 수 있습니다.", "emails.magicSession.optionUrl": "위의 버튼을 사용하여 로그인할 수 없다면, 다음 링크를 방문해 주세요:", "emails.otpSession.subject": "{{project}} 로그인", - "emails.otpSession.hello": "안녕하세요,", + "emails.otpSession.hello": "안녕하세요、", "emails.otpSession.description": "다음 인증 코드를 입력하면 보안을 유지하며 {{project}} 계정에 안전하게 로그인할 수 있습니다. 15분 후에 만료됩니다.", "emails.otpSession.clientInfo": "이 로그인은 {{agentClient}}을 사용하여 {{agentDevice}} {{agentOs}}에서 요청되었습니다. 로그인을 요청하지 않았다면, 이 이메일을 안심하고 무시하셔도 됩니다.", "emails.otpSession.securityPhrase": "이 이메일의 보안 구절은 {{phrase}}입니다. 로그인하는 동안 표시된 구절과 이 구절이 일치하면 이 이메일을 신뢰할 수 있습니다.", - "emails.otpSession.thanks": "감사합니다,", + "emails.otpSession.thanks": "감사합니다、", "emails.otpSession.signature": "{{project}} 팀" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/la.json b/app/config/locale/translations/la.json index 9ccbaba86e..bebef26854 100644 --- a/app/config/locale/translations/la.json +++ b/app/config/locale/translations/la.json @@ -4,28 +4,28 @@ "settings.direction": "ltr*", "emails.sender": "%s team", "emails.verification.subject": "Ratio comprobatio", - "emails.verification.hello": "Salve ibi {{user}}", + "emails.verification.hello": "Salve ibi {{user}},", "emails.verification.body": "Sequere hanc nexum ut quin inscriptionem tuum.", "emails.verification.footer": "Si verificationem huius inscriptionis non postulasti, nuntium hunc ignorare potes.", - "emails.verification.thanks": "Gratias", + "emails.verification.thanks": "Gratias,", "emails.verification.signature": "{{project}} Team", "emails.magicSession.subject": "Log in", - "emails.magicSession.hello": "Salve ibi", + "emails.magicSession.hello": "Salve ibi,", "emails.magicSession.body": "Hanc nexum cum login", "emails.magicSession.footer": "Si verificationem huius inscriptionis non postulasti, nuntium hunc ignorare potes.", - "emails.magicSession.thanks": "Gratias", + "emails.magicSession.thanks": "Gratias,", "emails.magicSession.signature": "{{project}} team", "emails.recovery.subject": "Recuperet password", - "emails.recovery.hello": "Salve ibi {{user}}", + "emails.recovery.hello": "Salve ibi {{user}},", "emails.recovery.body": "Sequere hanc conjunctionem ut recipias project password {{project}}", "emails.recovery.footer": "Si tesseram tuam recuperare non petis, nuntium hunc ignorare potes", - "emails.recovery.thanks": "Gratias", + "emails.recovery.thanks": "Gratias,", "emails.recovery.signature": "{{project}} team", "emails.invitation.subject": "Invitatio pro %s in quadrigis %s", - "emails.invitation.hello": "Salve ibi", + "emails.invitation.hello": "Salve ibi,", "emails.invitation.body": "Haec inscriptio ad te missa est quia dominus incepto {{owner}} te invitare vult ut membrum {{team}} quadrigis fias ad {{project}}", "emails.invitation.footer": "Si non quaero, potes hunc nuntium ignorare", - "emails.invitation.thanks": "Gratias", + "emails.invitation.thanks": "Gratias,", "emails.invitation.signature": "{{project}} team", "locale.country.unknown": "Ignotum", "countries.af": "Afghanistan", @@ -245,10 +245,10 @@ "emails.otpSession.thanks": "Gratias,", "emails.otpSession.signature": "{{project}} team -> {{project}} grex", "emails.certificate.subject": "Defectio testimonii pro %s", - "emails.certificate.hello": "Salve", + "emails.certificate.hello": "Salve,", "emails.certificate.body": "Certificatum pro dominio tuo '{{domain}}' generari non potuit. Hoc conatus num. {{attempt}} est, et defectus causatus est ab: {{error}}", "emails.certificate.footer": "Praeclarum tuum testificationem valet ad XXX dies a primo defectu. Magnopere suademus ut hoc casum investiges, alioquin dominium tuum sine valida SSL communicatione erit.", - "emails.certificate.thanks": "Gratias", + "emails.certificate.thanks": "Gratias,", "emails.certificate.signature": "team {{project}}", "sms.verification.body": "{{secret}}" } diff --git a/app/config/locale/translations/lb.json b/app/config/locale/translations/lb.json index 3f590d5323..91b52e4a18 100644 --- a/app/config/locale/translations/lb.json +++ b/app/config/locale/translations/lb.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s Team", "emails.verification.subject": "Kont Verifikatioun", - "emails.verification.hello": "Hey {{user}}", + "emails.verification.hello": "Hey {{user}},", "emails.verification.body": "Follegt dëse Link fir Är E -Mail Adress z'iwwerpréiwen.", "emails.verification.footer": "Wann Dir net gefrot hutt dës Adress z'iwwerpréiwen, kënnt Dir dëse Message ignoréieren.", - "emails.verification.thanks": "Merci", + "emails.verification.thanks": "Merci,", "emails.verification.signature": "{{project}} équipe", "emails.magicSession.subject": "Login", - "emails.magicSession.hello": "Hey", + "emails.magicSession.hello": "Hey,", "emails.magicSession.body": "Follegt dëse Link fir umellen.", "emails.magicSession.footer": "Wann Dir net gefrot hutt Iech mat dëser E -Mail anzemelden, kënnt Dir dëse Message ignoréieren.", - "emails.magicSession.thanks": "Merci", + "emails.magicSession.thanks": "Merci,", "emails.magicSession.signature": "{{project}} équipe", "emails.recovery.subject": "Password Reset", - "emails.recovery.hello": "Hello {{user}}", + "emails.recovery.hello": "Hello {{user}},", "emails.recovery.body": "Follegt dëse Link fir Äert {{project}} Passwuert zréckzesetzen.", "emails.recovery.footer": "Wann Dir net gefrot hutt Äert Passwuert zréckzesetzen, kënnt Dir dëse Message ignoréieren.", - "emails.recovery.thanks": "Merci", + "emails.recovery.thanks": "Merci,", "emails.recovery.signature": "{{project}} équipe", "emails.invitation.subject": "Invitatioun un %s équipe bei %s", - "emails.invitation.hello": "Hallo", + "emails.invitation.hello": "Hallo,", "emails.invitation.body": "Dës E -Mail gouf un Iech geschéckt well {{owner}} Iech invitéiere wëllt fir Member vum {{team}} Team bei {{project}} ze ginn.", "emails.invitation.footer": "Wann Dir net interesséiert sidd, kënnt Dir dëse Message ignoréieren.", - "emails.invitation.thanks": "Merci", + "emails.invitation.thanks": "Merci,", "emails.invitation.signature": "{{project}} équipe", "locale.country.unknown": "Onbekannt", "countries.af": "Afghanistan", @@ -244,4 +244,4 @@ "emails.otpSession.securityPhrase": "D'Sécherheetsausso fir dësen E-Mail ass {{phrase}}. Dir kënnt dësem E-Mail vertrauen, wann dës Ausso mat der Ausso iwwereneestëmmt, déi beim Umellen gewise ginn ass.", "emails.otpSession.thanks": "Merci,", "emails.otpSession.signature": "{{project}} Equipe" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/lt.json b/app/config/locale/translations/lt.json index f72250c98e..94c874ce82 100644 --- a/app/config/locale/translations/lt.json +++ b/app/config/locale/translations/lt.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s komanda", "emails.verification.subject": "Paskyros Patvirtinimas", - "emails.verification.hello": "Labas {{user}}", + "emails.verification.hello": "Labas {{user}},", "emails.verification.body": "Spauskite šią nuorodą, kad patvirtintumėte savo el. paštą.", "emails.verification.footer": "Jei neprašėte patvirtinti šio el. pašto, galite ignoruoti šį pranešimą.", - "emails.verification.thanks": "Ačiū", + "emails.verification.thanks": "Ačiū,", "emails.verification.signature": "{{project}} komanda", "emails.magicSession.subject": "Prisijungti", - "emails.magicSession.hello": "Labas", + "emails.magicSession.hello": "Labas,", "emails.magicSession.body": "Spauskite šią nuorodą, kad prisijungtumėte.", "emails.magicSession.footer": "Jei neprašėte prisijungti naudojantis šiuo el. paštu, galite ignoruoti šį pranešimą.", - "emails.magicSession.thanks": "Ačiū", + "emails.magicSession.thanks": "Ačiū,", "emails.magicSession.signature": "{{project}} komanda", "emails.recovery.subject": "Slaptažodžio Atkūrimas", - "emails.recovery.hello": "Labas {{user}}", + "emails.recovery.hello": "Labas {{user}},", "emails.recovery.body": "Spauskite šią nuorodą, kad atkurtumėte projekto {{project}} slaptažodį.", "emails.recovery.footer": "Jei neprašėte atkurti savo slaptažodzio, galite ignoruoti šį pranešimą.", - "emails.recovery.thanks": "Ačiū", + "emails.recovery.thanks": "Ačiū,", "emails.recovery.signature": "{{project}} komanda", "emails.invitation.subject": "Pakvietimas į %s komandą %s projekte", - "emails.invitation.hello": "Labas", + "emails.invitation.hello": "Labas,", "emails.invitation.body": "Šis el. laiškas buvo atsiųstas jums, nes {{owner}} norėjo jus pakviesti tapti projekto {{project}} dalimi {{team}} komandoje.", "emails.invitation.footer": "Jei jūsų tai nedomina, galite ignoruoti šį pranešimą.", - "emails.invitation.thanks": "Ačiū", + "emails.invitation.thanks": "Ačiū,", "emails.invitation.signature": "{{project}} komanda", "locale.country.unknown": "Nežinoma", "countries.af": "Afganistanas", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Šio el. laiško saugumo frazė yra {{phrase}}. Galite pasitikėti šiuo el. laišku, jei ši frazė atitinka frazę, rodytą prisijungimo metu.", "emails.otpSession.thanks": "Ačiū,", "emails.otpSession.signature": "{{project}} komanda" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/lv.json b/app/config/locale/translations/lv.json index 66604a0e3e..b4a396367c 100644 --- a/app/config/locale/translations/lv.json +++ b/app/config/locale/translations/lv.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s komanda", "emails.verification.subject": "Konta verifikācija", - "emails.verification.hello": "Sveicināti, {{user}}", + "emails.verification.hello": "Sveicināti, {{user}},", "emails.verification.body": "Sekojiet saitei, lai apstiprinātu savu e-pasta adresi.", "emails.verification.footer": "Ja Jūs nepieprasījāt šīs adreses apstiprinājumu, lūdzu, ignorējiet šo ziņu.", - "emails.verification.thanks": "Paldies", + "emails.verification.thanks": "Paldies,", "emails.verification.signature": "{{project}} komanda", "emails.magicSession.subject": "Ieiet", - "emails.magicSession.hello": "Sveicināti", + "emails.magicSession.hello": "Sveicināti,", "emails.magicSession.body": "Sekojiet saitei, lai ieietu.", "emails.magicSession.footer": "Ja Jūs nepieprasījāt ieiet ar šo e-pasta adresi, lūdzu, ignorējiet šo ziņu.", - "emails.magicSession.thanks": "Paldies", + "emails.magicSession.thanks": "Paldies,", "emails.magicSession.signature": "{{project}} komanda", "emails.recovery.subject": "Paroles atjaunināšana", - "emails.recovery.hello": "Labdien, {{user}}", + "emails.recovery.hello": "Labdien, {{user}},", "emails.recovery.body": "Sekojiet saitei, lai atjauninātu {{project}} paroli.", "emails.recovery.footer": "Ja Jūs nepieprasījāt paroles atjaunināšanu, lūdzu, ignorējiet šo ziņu.", - "emails.recovery.thanks": "Paldies", + "emails.recovery.thanks": "Paldies,", "emails.recovery.signature": "{{project}} komanda", "emails.invitation.subject": "Ielūgums piebiedroties %s komandai %s projektā.", - "emails.invitation.hello": "Labdien", + "emails.invitation.hello": "Labdien,", "emails.invitation.body": "Šis e-pasts tika nosūtīts Jums, jo {{owner}} vēlējās Jūs ielūgt kļūt par {{team}} komandas biedru {{project}} projektā.", "emails.invitation.footer": "Ja Jūs neesat ieinteresēts, lūdzu, ignorējiet šo ziņu.", - "emails.invitation.thanks": "Paldies", + "emails.invitation.thanks": "Paldies,", "emails.invitation.signature": "{{project}} komanda", "locale.country.unknown": "Nav zināms", "countries.af": "Afganistāna", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Drošības frāze šim e-pastam ir {{phrase}}. Šim e-pastam var uzticēties, ja šī frāze sakrīt ar frāzi, kas parādīta pieslēdzoties.", "emails.otpSession.thanks": "Paldies,", "emails.otpSession.signature": "{{project}} komanda" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/ml.json b/app/config/locale/translations/ml.json index 60e84e2a92..1b57d87865 100644 --- a/app/config/locale/translations/ml.json +++ b/app/config/locale/translations/ml.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s ടീം", "emails.verification.subject": "അക്കൗണ്ട് സ്ഥിരീകരണം", - "emails.verification.hello": "നമസ്കാരം {{user}}", + "emails.verification.hello": "നമസ്കാരം {{user}},", "emails.verification.body": "നിങ്ങളുടെ ഇമെയിൽ വിലാസം സ്ഥിരീകരിക്കുന്നതിനായി ഈ ലിങ്ക് പിന്തുടരുക.", "emails.verification.footer": "ഈ വിലാസം സ്ഥിരീകരിക്കാന്‍ നിങ്ങൾ ആവശ്യപ്പെട്ടില്ലെങ്കിൽ, നിങ്ങൾക്ക് ഈ സന്ദേശം അവഗണിക്കാവുന്നതാണ്.", - "emails.verification.thanks": "നന്ദി", + "emails.verification.thanks": "നന്ദി,", "emails.verification.signature": "{{project}} ടീം", "emails.magicSession.subject": "ലോഗിൻ", - "emails.magicSession.hello": "നമസ്കാരം", + "emails.magicSession.hello": "നമസ്കാരം,", "emails.magicSession.body": "ലോഗിൻ ചെയ്യുന്നതിനായി ഈ ലിങ്ക് പിന്തുടരുക.", "emails.magicSession.footer": "ഈ ഇമെയിൽ ഉപയോഗിച്ച് ലോഗിൻ ചെയ്യാൻ നിങ്ങൾ ആവശ്യപ്പെട്ടില്ലെങ്കിൽ, ഈ സന്ദേശം അവഗണിക്കാവുന്നതാണ്.", - "emails.magicSession.thanks": "നന്ദി", + "emails.magicSession.thanks": "നന്ദി,", "emails.magicSession.signature": "{{project}} ടീം", "emails.recovery.subject": "രഹസ്യവാക്ക് പുനക്രമീകരണം", - "emails.recovery.hello": "നമസ്കാരം {{user}}", + "emails.recovery.hello": "നമസ്കാരം {{user}},", "emails.recovery.body": "നിങ്ങളുടെ {{Project}} രഹസ്യവാക്ക് പുനക്രമീകരിക്കുന്നതിന് ഈ ലിങ്ക് പിന്തുടരുക.", "emails.recovery.footer": "നിങ്ങളുടെ രഹസ്യവാക്ക് പുനക്രമീകരിക്കാന്‍ നിങ്ങൾ ആവശ്യപ്പെട്ടില്ലെങ്കിൽ, ഈ സന്ദേശം അവഗണിക്കാവുന്നതാണ്.", - "emails.recovery.thanks": "നന്ദി", + "emails.recovery.thanks": "നന്ദി,", "emails.recovery.signature": "{{project}} ടീം", "emails.invitation.subject": "%s -ലെ %s ടീമിലേക്കുള്ള ക്ഷണം", - "emails.invitation.hello": "നമസ്കാരം", + "emails.invitation.hello": "നമസ്കാരം,", "emails.invitation.body": "നിങ്ങളെ {{project}} -ലെ {{team}} ടീമിലെ അംഗമാകുവാന്‍ ക്ഷണിക്കാൻ {{owner}} ആഗ്രഹിക്കുന്നതിനാലാണ് ഈ മെയിൽ നിങ്ങൾക്ക് അയക്കുന്നത്.", "emails.invitation.footer": "നിങ്ങൾക്ക് താൽപ്പര്യമില്ലെങ്കിൽ, ഈ സന്ദേശം അവഗണിക്കാവുന്നതാണ്.", - "emails.invitation.thanks": "നന്ദി", + "emails.invitation.thanks": "നന്ദി,", "emails.invitation.signature": "{{project}} ടീം", "locale.country.unknown": "Unknown", "countries.af": "അഫ്ഗാനിസ്ഥാൻ", @@ -245,10 +245,10 @@ "emails.otpSession.thanks": "നന്ദി,", "emails.otpSession.signature": "പ്രോജക്ട് ടീം", "emails.certificate.subject": "%s ന് സർട്ടിഫിക്കറ്റ് പരാജയപ്പെട്ടു", - "emails.certificate.hello": "ഹലോ", + "emails.certificate.hello": "ഹലോ,", "emails.certificate.body": "നിങ്ങളുടെ ഡൊമൈൻ '{{domain}}'നു വേണ്ടിയുള്ള സർട്ടിഫിക്കറ്റ് ഉണ്ടാക്കാനായില്ല. ഇത് ശ്രമം നമ്പർ {{attempt}} ആണ്, പരാജയപ്പെട്ടത് ഇതു മൂലമാണ്: {{error}}", "emails.certificate.footer": "നിങ്ങളുടെ മുൻപത്തെ സർട്ടിഫിക്കറ്റ് ആദ്യ പരാജയത്തിനു ശേഷം 30 ദിവസം വരെ സാധുവായിരിക്കും. ഈ കേസ് അന്വേഷിച്ചു നോക്കുന്നത് ഞങ്ങൾ ശക്തമായി ശുപാർശ ചെയ്യുന്നു, അല്ലെങ്കിൽ നിങ്ങളുടെ ഡൊമെയ്‌ൻ സാധുവായ SSL കമ്മ്യൂണിക്കേഷനില്ലാത്ത ഒരു അവസ്ഥയിലാകും.", - "emails.certificate.thanks": "നന്ദി", + "emails.certificate.thanks": "നന്ദി,", "emails.certificate.signature": "{{project}} ടീം", "sms.verification.body": "{{secret}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/mr.json b/app/config/locale/translations/mr.json index 4edd6bf617..6550d1c1ba 100644 --- a/app/config/locale/translations/mr.json +++ b/app/config/locale/translations/mr.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s टीम", "emails.verification.subject": "खाते सत्यापन", - "emails.verification.hello": "नमस्कार {{user}}", + "emails.verification.hello": "नमस्कार {{user}},", "emails.verification.body": "आपला ईमेल पत्ता सत्यापित करण्यासाठी या दुव्याचे अनुसरण करा.", "emails.verification.footer": "आपण या पत्त्याची पडताळणी करण्यास सांगितले नसल्यास, आपण या संदेशाकडे दुर्लक्ष करू शकता.", - "emails.verification.thanks": "धन्यवाद", + "emails.verification.thanks": "धन्यवाद,", "emails.verification.signature": "{{project}} संघ", "emails.magicSession.subject": "लॉगिन करा", - "emails.magicSession.hello": "नमस्कार ", + "emails.magicSession.hello": "नमस्कार ,", "emails.magicSession.body": "लॉगिन करण्यासाठी या लिंकचे अनुसरण करा.", "emails.magicSession.footer": "आपण या ईमेलचा वापर करून लॉगिन करण्यास सांगितले नसल्यास, आपण या संदेशाकडे दुर्लक्ष करू शकता.", - "emails.magicSession.thanks": "धन्यवाद", + "emails.magicSession.thanks": "धन्यवाद,", "emails.magicSession.signature": "{{project}} संघ", "emails.recovery.subject": "पासवर्ड रीसेट", - "emails.recovery.hello": "नमस्कार {{user}}", + "emails.recovery.hello": "नमस्कार {{user}},", "emails.recovery.body": "आपला {{project}}चे पासवर्ड रीसेट करण्यासाठी या लिंकचे अनुसरण करा", "emails.recovery.footer": "आपण आपला पासवर्ड रीसेट करण्यास सांगितले नसल्यास, आपण या संदेशाकडे दुर्लक्ष करू शकता.", - "emails.recovery.thanks": "धन्यवाद", + "emails.recovery.thanks": "धन्यवाद,", "emails.recovery.signature": "{{project}} संघ", "emails.invitation.subject": "%s संघ %s येथे सामील होण्यासाठी आमंत्रण", - "emails.invitation.hello": "नमस्कार", + "emails.invitation.hello": "नमस्कार,", "emails.invitation.body": "हा मेल तुम्हाला पाठवला होता कारण {{owner}} तुम्हाला {{project}} येथे {{team}} टीमचे सदस्य होण्यासाठी आमंत्रित करू इच्छित होते.", "emails.invitation.footer": "आपल्याला स्वारस्य नसल्यास, आपण या संदेशाकडे दुर्लक्ष करू शकता.", - "emails.invitation.thanks": "धन्यवाद", + "emails.invitation.thanks": "धन्यवाद,", "emails.invitation.signature": "{{project}} संघ", "locale.country.unknown": "अज्ञात", "countries.af": "अफगानिस्तान", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "या ईमेलसाठीचे सुरक्षा वाक्यांश {{phrase}} आहे. जर हे वाक्यांश साइन इन करताना दाखवल्या गेलेल्या वाक्यांशाशी जुळत असेल तर आपण या ईमेलवर विश्वास ठेवू शकता.", "emails.otpSession.thanks": "धन्यवाद,", "emails.otpSession.signature": "{{project}} संघ" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/ms.json b/app/config/locale/translations/ms.json index ded7587d34..a02c36b075 100644 --- a/app/config/locale/translations/ms.json +++ b/app/config/locale/translations/ms.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s Team", "emails.verification.subject": "Pengesahan Akaun", - "emails.verification.hello": "Hey {{user}}", + "emails.verification.hello": "Hey {{user}},", "emails.verification.body": "Tekan pautan ini untuk mengesahkan alamat email anda.", "emails.verification.footer": "Sekiranya anda tidak membuat permintaan untuk mengesahkan email ini, sila abaikan mesej ini.", - "emails.verification.thanks": "Terima kasih", + "emails.verification.thanks": "Terima kasih,", "emails.verification.signature": "{{project}} team", "emails.magicSession.subject": "Log masuk", - "emails.magicSession.hello": "Hey", + "emails.magicSession.hello": "Hey,", "emails.magicSession.body": "Tekan pautan ini untuk log masuk.", "emails.magicSession.footer": "Sekiranya anda tidak membuat permintaan untuk log masuk menggunakan email ini, sila abaikan mesej ini.", - "emails.magicSession.thanks": "Terima kasih", + "emails.magicSession.thanks": "Terima kasih,", "emails.magicSession.signature": "{{project}} team", "emails.recovery.subject": "Menetap semula Kata Laluan", - "emails.recovery.hello": "Hello {{user}}", + "emails.recovery.hello": "Hello {{user}},", "emails.recovery.body": "Tekan pautan ini untuk menetapkan semula kata laluan {{project}}.", "emails.recovery.footer": "Sekiranya anda tidak membuat permintaan menetap semula kata laluan, sila abaikan mesej ini.", - "emails.recovery.thanks": "Terima kasih", + "emails.recovery.thanks": "Terima kasih,", "emails.recovery.signature": "{{project}} team", "emails.invitation.subject": "Jemputan ke pasukan %s di %s", - "emails.invitation.hello": "Hello", + "emails.invitation.hello": "Hello,", "emails.invitation.body": "Anda menerima mel ini kerana {{owner}} ingin menjemput anda untuk menjadi ahli pasukan {{team}} di {{project}}.", "emails.invitation.footer": "Sekiranya anda tidak berminat, sila abaikan mesej ini.", - "emails.invitation.thanks": "Terima kasih", + "emails.invitation.thanks": "Terima kasih,", "emails.invitation.signature": "{{project}} team", "locale.country.unknown": "Tidak Diketahui", "countries.af": "Afghanistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Frasa keselamatan untuk email ini adalah {{phrase}}. Anda boleh mempercayai email ini jika frasa ini sepadan dengan frasa yang ditunjukkan semasa log masuk.", "emails.otpSession.thanks": "Terima kasih,", "emails.otpSession.signature": "pasukan {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/nb.json b/app/config/locale/translations/nb.json index f8680e8993..daf18abc1c 100644 --- a/app/config/locale/translations/nb.json +++ b/app/config/locale/translations/nb.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s Team", "emails.verification.subject": "Kontobekreftelse", - "emails.verification.hello": "Hei {{user}}", + "emails.verification.hello": "Hei {{user}},", "emails.verification.body": "Følg denne lenken for å bekrefte din e-postadresse.", "emails.verification.footer": "Dersom du ikke ba om å bekrefte e-postadressen, kan du se bort fra denne meldingen.", - "emails.verification.thanks": "Takk", + "emails.verification.thanks": "Takk,", "emails.verification.signature": "{{project}} team", "emails.magicSession.subject": "Pålogging", - "emails.magicSession.hello": "Hei", + "emails.magicSession.hello": "Hei,", "emails.magicSession.body": "Følg denne lenken for å logge på.", "emails.magicSession.footer": "Dersom du ikke ba om å logge på med denne e-postadressen, kan du se bort fra denne meldingen.", - "emails.magicSession.thanks": "Takk", + "emails.magicSession.thanks": "Takk,", "emails.magicSession.signature": "{{project}} team", "emails.recovery.subject": "Nullstille passord", - "emails.recovery.hello": "Hei {{user}}", + "emails.recovery.hello": "Hei {{user}},", "emails.recovery.body": "Følg denne lenken for å nullstille ditt {{project}} passord.", "emails.recovery.footer": "Dersom du ikke ba om å nullstille passordet ditt, kan du se bort fra denne meldingen.", - "emails.recovery.thanks": "Takk", + "emails.recovery.thanks": "Takk,", "emails.recovery.signature": "{{project}} team", "emails.invitation.subject": "Invitasjon til %s Team ved %s", - "emails.invitation.hello": "Hei", + "emails.invitation.hello": "Hei,", "emails.invitation.body": "Denne meldingen ble sendt til deg fordi {{owner}} ønsket å invitere deg til å bli medlem av {{team}} team ved {{project}}.", "emails.invitation.footer": "Dersom du ikke er interessert, kan du se bort fra denne meldingen.", - "emails.invitation.thanks": "Takk", + "emails.invitation.thanks": "Takk,", "emails.invitation.signature": "{{project}} team", "locale.country.unknown": "Ukjent", "countries.af": "Afghanistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Sikkerhetsfrasen for denne e-posten er {{phrase}}. Du kan stole på denne e-posten hvis denne frasen stemmer overens med frasen som ble vist under pålogging.", "emails.otpSession.thanks": "Takk,", "emails.otpSession.signature": "{{project}} team" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/ne.json b/app/config/locale/translations/ne.json index f31609eafc..4f05a9b5ba 100644 --- a/app/config/locale/translations/ne.json +++ b/app/config/locale/translations/ne.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s समूह", "emails.verification.subject": "खाता प्रमाणिकरण", - "emails.verification.hello": "नमस्ते {{user}}", + "emails.verification.hello": "नमस्ते {{user}},", "emails.verification.body": "इमेल ठेगाना प्रमाणित गर्नको लागी यो लिंकमा जानुहोस।", "emails.verification.footer": "यदि तपाइँले आफ्नो खाता प्रमाणित गर्न सोध्नु भएको छैन भने तपाइँले यो सन्देश लाई बेवास्ता गर्न सक्नुहुन्छ।", - "emails.verification.thanks": "धन्यवाद", + "emails.verification.thanks": "धन्यवाद,", "emails.verification.signature": "{{project}} समूह", "emails.magicSession.subject": "लगइन", - "emails.magicSession.hello": "नमस्ते", + "emails.magicSession.hello": "नमस्ते,", "emails.magicSession.body": "लगइन गर्नको लागी यो लिंकमा जानुहोस।", "emails.magicSession.footer": "यदि तपाइँले यो इमेल प्रयोग गरेर लगइन गर्न सोध्नु भएको छैन भने तपाइँले यो सन्देश लाई बेवास्ता गर्न सक्नुहुन्छ।", - "emails.magicSession.thanks": "धन्यवाद", + "emails.magicSession.thanks": "धन्यवाद,", "emails.magicSession.signature": "{{project}} समूह", "emails.recovery.subject": "पासवर्ड रिसेट", - "emails.recovery.hello": "नमस्ते {{user}}", + "emails.recovery.hello": "नमस्ते {{user}},", "emails.recovery.body": "{{project}}को पासवर्ड रिसेट गर्नको लागी यो लिंकमा जानुहोस।", "emails.recovery.footer": "यदि तपाइँले आफ्नो पासवर्ड रिसेट गर्न सोध्नु भएको छैन भने तपाइँले यो सन्देश लाई बेवास्ता गर्न सक्नुहुन्छ।", - "emails.recovery.thanks": "धन्यवाद", + "emails.recovery.thanks": "धन्यवाद,", "emails.recovery.signature": "{{project}} समूह", "emails.invitation.subject": "%s समूहको लागि %s मा निमन्त्रणा", - "emails.invitation.hello": "नमस्ते", + "emails.invitation.hello": "नमस्ते,", "emails.invitation.body": "{{owner}}ले तपाइँलाई {{project}}मा {{team}}को सदस्य बन्न आमन्त्रित गर्न चाहनु भएको छ। त्येसैले तपाइँलाई यो सन्देश पठाइएको हो।", "emails.invitation.footer": "यदि तपाइँ इच्छुक हुनुहुन्न भने, तपाइँले यो सन्देशलाई बेवास्ता गर्न सक्नुहुन्छ।", - "emails.invitation.thanks": "धन्यवाद", + "emails.invitation.thanks": "धन्यवाद,", "emails.invitation.signature": "{{project}} समूह", "locale.country.unknown": "अज्ञात", "countries.af": "अफगानिस्तान", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "यस ईमेलको लागि सुरक्षा वाक्य {{phrase}} हो। यदि यो वाक्य साइन इन गर्दा देखाइएको वाक्यसँग मेल खान्छ भने तपाईँले यो ईमेलमा विश्वास गर्न सक्नुहुन्छ।", "emails.otpSession.thanks": "धन्यवाद,", "emails.otpSession.signature": "{{project}} टिम" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/nl.json b/app/config/locale/translations/nl.json index 55e48fa753..cae82a9a37 100644 --- a/app/config/locale/translations/nl.json +++ b/app/config/locale/translations/nl.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s Team", "emails.verification.subject": "Account Verificatie", - "emails.verification.hello": "Hoi {{user}}", + "emails.verification.hello": "Hoi {{user}},", "emails.verification.body": "Volg deze link om uw e-mail te verifieren", "emails.verification.footer": "Als u geen aanvraag voor verificatie heeft gemaakt, kan u deze mail negeren", - "emails.verification.thanks": "Bedankt", + "emails.verification.thanks": "Bedankt,", "emails.verification.signature": "{{project}} team", "emails.magicSession.subject": "Login", - "emails.magicSession.hello": "Hoi", + "emails.magicSession.hello": "Hoi,", "emails.magicSession.body": "Volg deze link om in te loggen", "emails.magicSession.footer": "Als u geen aanvraag heeft gemaakt om met deze mail in te loggen, kan u deze mail negeren", - "emails.magicSession.thanks": "Bedankt", + "emails.magicSession.thanks": "Bedankt,", "emails.magicSession.signature": "{{project}} team", "emails.recovery.subject": "Wachtwoord Herinstellen", - "emails.recovery.hello": "Hallo {{user}}", + "emails.recovery.hello": "Hallo {{user}},", "emails.recovery.body": "Volg deze link om het wachtwoord van uw project {{project}} te wijzigen", "emails.recovery.footer": "Als u geen aanvraag heeft gemaakt om uw wachtwoord te wijzigen, kan u deze mail negeren", - "emails.recovery.thanks": "Bedankt", + "emails.recovery.thanks": "Bedankt,", "emails.recovery.signature": "{{project}} team", "emails.invitation.subject": "Uitnodiging van %s Team uit %s", "emails.invitation.hello": "Hallo,", "emails.invitation.body": "U ontvangt deze mail want u was uitgenodig door {{owner}} om lid van het {{team}} team te worden in {{project}} ", "emails.invitation.footer": "Als u niet geintereseerd bent, kan u deze mail negeren.", - "emails.invitation.thanks": "Bedankt", + "emails.invitation.thanks": "Bedankt,", "emails.invitation.signature": "{{project}} team", "locale.country.unknown": "Onbekend", "countries.af": "Afghanistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "De beveiligingszin voor deze e-mail is {{phrase}}. U kunt deze e-mail vertrouwen als deze zin overeenkomt met de zin die wordt getoond tijdens het inloggen.", "emails.otpSession.thanks": "Bedankt,", "emails.otpSession.signature": "{{project}} team" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/nn.json b/app/config/locale/translations/nn.json index 6ab037466f..44be0f9845 100644 --- a/app/config/locale/translations/nn.json +++ b/app/config/locale/translations/nn.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s Team", "emails.verification.subject": "Kontostadfesting", - "emails.verification.hello": "Hallo {{user}}", + "emails.verification.hello": "Hallo {{user}},", "emails.verification.body": "Følg denne lenkja for å bekrefta din e-postadresse.", "emails.verification.footer": "Om du ikkje bad om å bekrefta e-postadressa, kan du ignorera denne meldinga.", - "emails.verification.thanks": "Takk", + "emails.verification.thanks": "Takk,", "emails.verification.signature": "{{project}} team", "emails.magicSession.subject": "Pålogging", - "emails.magicSession.hello": "Hei", + "emails.magicSession.hello": "Hei,", "emails.magicSession.body": "Følg denne lenkja for å logge på.", "emails.magicSession.footer": "Om du ikkje ba om å logge på med denne e-postadressa, kan du ignorera denne meldinga.", - "emails.magicSession.thanks": "Takk", + "emails.magicSession.thanks": "Takk,", "emails.magicSession.signature": "{{project}} team", "emails.recovery.subject": "Nullstilla passord", - "emails.recovery.hello": "Hallo {{user}}", + "emails.recovery.hello": "Hallo {{user}},", "emails.recovery.body": "Følg denne lenkja for å nullstilla ditt {{project}} passord.", "emails.recovery.footer": "Om du ikkje ba om å nullstilla passordet ditt, kan du ignorera denne meldinga.", - "emails.recovery.thanks": "Takk", + "emails.recovery.thanks": "Takk,", "emails.recovery.signature": "{{project}} team", "emails.invitation.subject": "Innbyding til %s Team ved %s", - "emails.invitation.hello": "Hallo", + "emails.invitation.hello": "Hallo,", "emails.invitation.body": "Denne meldinga ble sendt til deg fordi {{owner}} ynskja å invitera deg til å bli medlem av {{team}} team i {{project}}.", "emails.invitation.footer": "Om du ikkje er interessert, kan du ignorera denne meldinga.", - "emails.invitation.thanks": "Takk", + "emails.invitation.thanks": "Takk,", "emails.invitation.signature": "{{project}} team", "locale.country.unknown": "Ukjend", "countries.af": "Afghanistan", @@ -245,10 +245,10 @@ "emails.otpSession.thanks": "Takk,", "emails.otpSession.signature": "{{project}}-laget", "emails.certificate.subject": "Sertifikatfeil for %s", - "emails.certificate.hello": "Hei", + "emails.certificate.hello": "Hei,", "emails.certificate.body": "Sertifikatet for domenet ditt '{{domain}}' kunne ikkje opprettast. Dette er forsøk nr. {{attempt}}, og feilen blei forårsaka av: {{error}}", "emails.certificate.footer": "Førre sertifikatet ditt vil vere gyldig i 30 dagar sidan den første feilen. Vi rår sterkt til at du undersøkjer denne saka, elles vil domenet ditt ende opp utan gyldig SSL-kommunikasjon.", - "emails.certificate.thanks": "Takk", + "emails.certificate.thanks": "Takk,", "emails.certificate.signature": "{{project}} team", "sms.verification.body": "{{secret}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/or.json b/app/config/locale/translations/or.json index 08b150fbb7..efd516f23a 100644 --- a/app/config/locale/translations/or.json +++ b/app/config/locale/translations/or.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s ଦଳ", "emails.verification.subject": "ଖାତା ଯାଞ୍ଚ", - "emails.verification.hello": "ନମସ୍କାର {{user}}", + "emails.verification.hello": "ନମସ୍କାର {{user}},", "emails.verification.body": "ଆପଣଙ୍କର ଇମେଲ୍ ଠିକଣା ଯାଞ୍ଚ କରିବାକୁ ଏହି ଲିଙ୍କ୍ ଅନୁସରଣ କରନ୍ତୁ |", "emails.verification.footer": "ଯଦି ଆପଣ ଏହି ଠିକଣା ଯାଞ୍ଚ କରିବାକୁ କହି ନାହାଁନ୍ତି, ତେବେ ଆପଣ ଏହି ସନ୍ଦେଶକୁ ଉପେକ୍ଷା କରିପାରିବେ |", - "emails.verification.thanks": "ଧନ୍ୟବାଦ", + "emails.verification.thanks": "ଧନ୍ୟବାଦ,", "emails.verification.signature": "{{project}} ଦଳ", "emails.magicSession.subject": "ଲଗଇନ୍ କରନ୍ତୁ", - "emails.magicSession.hello": "ନମସ୍କାର", + "emails.magicSession.hello": "ନମସ୍କାର,", "emails.magicSession.body": "ଲଗଇନ୍ କରିବାକୁ ଏହି ଲିଙ୍କ୍ ଅନୁସରଣ କରନ୍ତୁ |", "emails.magicSession.footer": "ଯଦି ଆପଣ ଏହି ଇମେଲ୍ ବ୍ୟବହାର କରି ଲଗଇନ୍ କରିବାକୁ କହି ନାହାଁନ୍ତି, ତେବେ ଆପଣ ଏହି ସନ୍ଦେଶକୁ ଉପେକ୍ଷା କରିପାରିବେ |", - "emails.magicSession.thanks": "ଧନ୍ୟବାଦ", + "emails.magicSession.thanks": "ଧନ୍ୟବାଦ,", "emails.magicSession.signature": "{{project}} ଦଳ", "emails.recovery.subject": "ପାସୱାର୍ଡ ପୁନଃ ସେଟ୍ କରନ୍ତୁ |", - "emails.recovery.hello": "ନମସ୍କାର {{user}}", + "emails.recovery.hello": "ନମସ୍କାର {{user}},", "emails.recovery.body": "ଆପଣଙ୍କର {{project}} ପାସୱାର୍ଡ ପୁନଃ ସେଟ୍ କରିବାକୁ ଏହି ଲିଙ୍କକୁ ଅନୁସରଣ କରନ୍ତୁ |", "emails.recovery.footer": "ଯଦି ଆପଣ ଆପଣଙ୍କର ପାସୱାର୍ଡ ପୁନଃ ସେଟ୍ କରିବାକୁ କହି ନାହାଁନ୍ତି, ତେବେ ଆପଣ ଏହି ସନ୍ଦେଶକୁ ଉପେକ୍ଷା କରିପାରିବେ |", - "emails.recovery.thanks": "ଧନ୍ୟବାଦ", + "emails.recovery.thanks": "ଧନ୍ୟବାଦ,", "emails.recovery.signature": "{{project}} ଦଳ", "emails.invitation.subject": "%s ରେ %s ଦଳକୁ ନିମନ୍ତ୍ରଣ |", - "emails.invitation.hello": "ନମସ୍କାର", + "emails.invitation.hello": "ନମସ୍କାର,", "emails.invitation.body": "ଏହି ମେଲ୍ ଆପଣଙ୍କୁ ପଠାଯାଇଥିଲା କାରଣ {{owner}} ଆପଣଙ୍କୁ {{project} ରେ {{team}} ଦଳର ସଦସ୍ୟ ହେବାକୁ ଆମନ୍ତ୍ରଣ କରିବାକୁ ଚାହୁଁଥିଲେ |", "emails.invitation.footer": "ଯଦି ଆପଣ ଆଗ୍ରହୀ ନୁହଁନ୍ତି, ଆପଣ ଏହି ସନ୍ଦେଶକୁ ଅଣଦେଖା କରିପାରିବେ |", - "emails.invitation.thanks": "ଧନ୍ୟବାଦ", + "emails.invitation.thanks": "ଧନ୍ୟବାଦ,", "emails.invitation.signature": "{{project}} ଦଳ", "locale.country.unknown": "ଅଜ୍ଞାତ", "countries.af": "ଆଫଗାନିସ୍ତାନ", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "ଏହି ଇମେଲର ସୁରକ୍ଷା ବାକ୍ୟାଂଶ ହେଉଛି {{phrase}}। ସାଇନ୍ ଇନ୍ କରିବା ସମୟରେ ଦେଖାଯାଇଥିବା ବାକ୍ୟାଂଶ ସହ ଏହା ମେଳେ ଯଦି, ଆପଣ ଏହି ଇମେଲକୁ ଆସ୍ଥା କରି ପାରିବେ।", "emails.otpSession.thanks": "ଧନ୍ୟବାଦ,", "emails.otpSession.signature": "ପ୍ରକଳ୍ପ ଟିମ୍ବ୍" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/pa.json b/app/config/locale/translations/pa.json index 76efde346b..de71be9f49 100644 --- a/app/config/locale/translations/pa.json +++ b/app/config/locale/translations/pa.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s ਟੀਮ", "emails.verification.subject": "", - "emails.verification.hello": "", + "emails.verification.hello": ",", "emails.verification.body": "", "emails.verification.footer": "", - "emails.verification.thanks": "", + "emails.verification.thanks": ",", "emails.verification.signature": "", "emails.magicSession.subject": "", - "emails.magicSession.hello": "", + "emails.magicSession.hello": ",", "emails.magicSession.body": "", "emails.magicSession.footer": "", - "emails.magicSession.thanks": "", + "emails.magicSession.thanks": ",", "emails.magicSession.signature": "", "emails.recovery.subject": "", - "emails.recovery.hello": "", + "emails.recovery.hello": ",", "emails.recovery.body": "", "emails.recovery.footer": "", - "emails.recovery.thanks": "", + "emails.recovery.thanks": ",", "emails.recovery.signature": "", "emails.invitation.subject": "", - "emails.invitation.hello": "", + "emails.invitation.hello": ",", "emails.invitation.body": "", "emails.invitation.footer": "", - "emails.invitation.thanks": "", + "emails.invitation.thanks": ",", "emails.invitation.signature": "", "locale.country.unknown": "ਅਣਜਾਣ", "countries.af": "ਅਫਗਾਨਿਸਤਾਨ", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "ਇਸ ਈਮੇਲ ਲਈ ਸੁਰੱਖਿਆ ਵਾਕ ਹੈ {{phrase}}। ਜੇ ਇਹ ਵਾਕ ਸਾਈਨ ਇਨ ਕਰਨ ਸਮੇਂ ਦਿਖਾਈ ਦੇਣ ਵਾਲੇ ਵਾਕ ਨਾਲ ਮੇਲ ਖਾਂਦਾ ਹੈ ਤਾਂ ਤੁਸੀਂ ਇਸ ਈਮੇਲ 'ਤੇ ਭਰੋਸਾ ਕਰ ਸਕਦੇ ਹੋ।", "emails.otpSession.thanks": "ਧੰਨਵਾਦ,", "emails.otpSession.signature": "{{project}} ਟੀਮ" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/pl.json b/app/config/locale/translations/pl.json index e596d2c04b..ee5811fb59 100644 --- a/app/config/locale/translations/pl.json +++ b/app/config/locale/translations/pl.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "Zespół %s", "emails.verification.subject": "Weryfikacja konta", - "emails.verification.hello": "Cześć {{user}}", + "emails.verification.hello": "Cześć {{user}},", "emails.verification.body": "Przejdź do tego linku, aby zweryfikować swój adres e-mail.", "emails.verification.footer": "Jeśli to nie Ty prosiłeś o zweryfikowanie tego adresu, zignoruj tę wiadomość.", - "emails.verification.thanks": "Dziękujemy", + "emails.verification.thanks": "Dziękujemy,", "emails.verification.signature": "Zespół {{project}}", "emails.magicSession.subject": "Logowanie", - "emails.magicSession.hello": "Cześć", + "emails.magicSession.hello": "Cześć,", "emails.magicSession.body": "Kliknij w ten link, aby zalogować się.", "emails.magicSession.footer": "Jeśli to nie Ty prosiłeś o logowanie przy użyciu tego adresu e-mail, zignoruj tę wiadomość.", - "emails.magicSession.thanks": "Dziękujemy", + "emails.magicSession.thanks": "Dziękujemy,", "emails.magicSession.signature": "Zespół {{project}}", "emails.recovery.subject": "Resetowanie hasła", - "emails.recovery.hello": "Cześć {{user}}", + "emails.recovery.hello": "Cześć {{user}},", "emails.recovery.body": "Przejdź do tego linku, aby zresetować hasło dla {{project}}.", "emails.recovery.footer": "Jeśli to nie Ty prosiłeś o zresetowanie swojego hasła, zignoruj tę wiadomość.", - "emails.recovery.thanks": "Dziękujemy", + "emails.recovery.thanks": "Dziękujemy,", "emails.recovery.signature": "Zespół {{project}}", "emails.invitation.subject": "Zaproszenie do zespołu %s w %s", - "emails.invitation.hello": "Cześć", + "emails.invitation.hello": "Cześć,", "emails.invitation.body": "Otrzymujesz tę wiadomość, ponieważ {{owner}} zaprasza Cię do grona członków zespołu {{team}} w projekcie {{project}}.", "emails.invitation.footer": "Jeśli nie jesteś zainteresowany, zignoruj tę wiadomość.", - "emails.invitation.thanks": "Dziękujemy", + "emails.invitation.thanks": "Dziękujemy,", "emails.invitation.signature": "Zespół {{project}}", "locale.country.unknown": "Nieznany", "countries.af": "Afganistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Hasłem zabezpieczającym dla tego e-maila jest {{phrase}}. Możesz zaufać temu e-mailowi, jeśli hasło zgadza się z hasłem wyświetlonym podczas logowania.", "emails.otpSession.thanks": "Dzięki,", "emails.otpSession.signature": "team {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/pt-br.json b/app/config/locale/translations/pt-br.json index b2c4931011..a53ca79813 100644 --- a/app/config/locale/translations/pt-br.json +++ b/app/config/locale/translations/pt-br.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "Time %s", "emails.verification.subject": "Verificação da Conta", - "emails.verification.hello": "Olá {{user}}", + "emails.verification.hello": "Olá {{user}},", "emails.verification.body": "Clique neste link para verificar o seu endereço de e-mail.", "emails.verification.footer": "Se você não solicitou a verificação deste e-mail, ignore essa mensagem.", - "emails.verification.thanks": "Muito obrigado", + "emails.verification.thanks": "Muito obrigado,", "emails.verification.signature": "Time {{project}}", "emails.magicSession.subject": "Login", - "emails.magicSession.hello": "Olá", + "emails.magicSession.hello": "Olá,", "emails.magicSession.body": "Clique neste link para entrar.", "emails.magicSession.footer": "Se você não solicitou conectar-se com este e-mail, ignore essa mensagem.", - "emails.magicSession.thanks": "Muito obrigado", + "emails.magicSession.thanks": "Muito obrigado,", "emails.magicSession.signature": "Time {{project}}", "emails.recovery.subject": "Redefinição de senha", - "emails.recovery.hello": "Olá {{user}}", + "emails.recovery.hello": "Olá {{user}},", "emails.recovery.body": "Clique neste link para redefinir sua senha do {{project}}.", "emails.recovery.footer": "Se você não solicitou a redefinição da sua senha, você pode ignorar essa mensagem.", - "emails.recovery.thanks": "Muito obrigado", + "emails.recovery.thanks": "Muito obrigado,", "emails.recovery.signature": "Time {{project}}", "emails.invitation.subject": "Convite para o Time %s em %s", - "emails.invitation.hello": "Olá", + "emails.invitation.hello": "Olá,", "emails.invitation.body": "Este e-mail foi enviado porque {{owner}} deseja convidar você a se tornar membro do Time {{team}} em {{project}}.", "emails.invitation.footer": "Caso não tenha interesse, ignore essa mensagem.", - "emails.invitation.thanks": "Muito obrigado", + "emails.invitation.thanks": "Muito obrigado,", "emails.invitation.signature": "Time {{project}}", "locale.country.unknown": "Desconhecido", "countries.af": "Afeganistão", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "A frase de segurança para este e-mail é {{phrase}}. Você pode confiar neste e-mail se esta frase corresponder à frase mostrada durante o login.", "emails.otpSession.thanks": "Obrigado,", "emails.otpSession.signature": "equipe {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/pt-pt.json b/app/config/locale/translations/pt-pt.json index 2dab03c9dd..d85dca9300 100644 --- a/app/config/locale/translations/pt-pt.json +++ b/app/config/locale/translations/pt-pt.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "Equipa %s", "emails.verification.subject": "Verificação de contas", - "emails.verification.hello": "Hey {{user}}", + "emails.verification.hello": "Hey {{user}},", "emails.verification.body": "Siga esta ligação para verificar o seu endereço de correio electrónico.", "emails.verification.footer": "Se não pediu para verificar este endereço, pode ignorar esta mensagem.", - "emails.verification.thanks": "Obrigado", + "emails.verification.thanks": "Obrigado,", "emails.verification.signature": "Equipa {{project}}", "emails.magicSession.subject": "Login", - "emails.magicSession.hello": "Olá ", + "emails.magicSession.hello": "Olá ,", "emails.magicSession.body": "Siga esta ligação para iniciar sessão.", "emails.magicSession.footer": "Se não pediu para entrar usando este e-mail, pode ignorar esta mensagem.", - "emails.magicSession.thanks": "Obrigado", + "emails.magicSession.thanks": "Obrigado,", "emails.magicSession.signature": "Equipa {{project}}", "emails.recovery.subject": "Redefinição de senha", - "emails.recovery.hello": "Olá {{user}}", + "emails.recovery.hello": "Olá {{user}},", "emails.recovery.body": "Utilize este link para redefinir a palavra-passe do seu projecto {{project}}", "emails.recovery.footer": "Se não pediu para redefinir a sua palavra-passe, pode ignorar esta mensagem.", - "emails.recovery.thanks": "Obrigado", + "emails.recovery.thanks": "Obrigado,", "emails.recovery.signature": "Equipa {{project}}", "emails.invitation.subject": "Convite à equipa de %s às %s", - "emails.invitation.hello": "Olá", + "emails.invitation.hello": "Olá,", "emails.invitation.body": "Este correio foi-lhe enviado porque {{owner}} queria convidá-lo a tornar-se membro da equipa {{team}} da {{project}}.", "emails.invitation.footer": "Se não estiver interessado, pode ignorar esta mensagem.", - "emails.invitation.thanks": "Obrigado", + "emails.invitation.thanks": "Obrigado,", "emails.invitation.signature": "Equipa {{project}}", "locale.country.unknown": "Desconhecido", "countries.af": "Afeganistão", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "A frase de segurança para este email é {{phrase}}. Você pode confiar neste email se essa frase corresponder à frase mostrada durante o login.", "emails.otpSession.thanks": "Obrigado,", "emails.otpSession.signature": "equipe {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/ro.json b/app/config/locale/translations/ro.json index 45fb71d190..04cb22dd6b 100644 --- a/app/config/locale/translations/ro.json +++ b/app/config/locale/translations/ro.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s Echipa", "emails.verification.subject": "Verificare cont", - "emails.verification.hello": "Bună ziua, {{user}}", + "emails.verification.hello": "Bună ziua, {{user}},", "emails.verification.body": "Click pe acest link pentru a valida adresa de email.", "emails.verification.footer": "Dacă nu ai cerut validarea adresei de email, poți ignora acest mesaj.", - "emails.verification.thanks": "Mulțumim", + "emails.verification.thanks": "Mulțumim,", "emails.verification.signature": "Echipa {{project}}", "emails.magicSession.subject": "Login", - "emails.magicSession.hello": "Bună ziua", + "emails.magicSession.hello": "Bună ziua,", "emails.magicSession.body": "Urmează acest link pentru logare.", "emails.magicSession.footer": "Dacă nu ai incercat să te loghezi folosing această adresa de email, poți ignora acest mesaj.", - "emails.magicSession.thanks": "Mulțumim", + "emails.magicSession.thanks": "Mulțumim,", "emails.magicSession.signature": "Echipa {{project}}", "emails.recovery.subject": "Resetare parolă", - "emails.recovery.hello": "Bună ziua, {{user}}", + "emails.recovery.hello": "Bună ziua, {{user}},", "emails.recovery.body": "Click aici pentru a reseta parola pentru {{project}}", "emails.recovery.footer": "Dacă nu ai cerut să îți schimbi parola, ignoră acest mesaj.", - "emails.recovery.thanks": "Mulțumim", + "emails.recovery.thanks": "Mulțumim,", "emails.recovery.signature": "Echipa {{project}}", "emails.invitation.subject": "Invitatie catre %s Echipa la %s", - "emails.invitation.hello": "Bună ziua", + "emails.invitation.hello": "Bună ziua,", "emails.invitation.body": "Acest email a fost trimis pentru că {{owner}} a vrut ca tu să devii membru al echipei {{team}} la {{project}}.", "emails.invitation.footer": "Dacă nu esti interesat, poți ignora acest email.", - "emails.invitation.thanks": "Mulțumim", + "emails.invitation.thanks": "Mulțumim,", "emails.invitation.signature": "Echipa {{project}}", "locale.country.unknown": "Necunoscut", "countries.af": "Afghanistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Fraza de securitate pentru acest e-mail este {{phrase}}. Puteți avea încredere în acest e-mail dacă fraza se potrivește cu cea afișată în timpul autentificării.", "emails.otpSession.thanks": "Mulțumesc,", "emails.otpSession.signature": "echipa {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/ru.json b/app/config/locale/translations/ru.json index a1d740bea2..029aa06ee7 100644 --- a/app/config/locale/translations/ru.json +++ b/app/config/locale/translations/ru.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "Команда %s", "emails.verification.subject": "Верификация аккаунта", - "emails.verification.hello": "Здравствуйте, {{user}}", + "emails.verification.hello": "Здравствуйте, {{user}},", "emails.verification.body": "Перейдите по ссылке, чтобы подтвердить свой адрес электронной почты.", "emails.verification.footer": "Если вы не запрашивали подтверждение этого адреса, проигнорируйте это сообщение.", - "emails.verification.thanks": "Спасибо", + "emails.verification.thanks": "Спасибо,", "emails.verification.signature": "команда {{project}}", "emails.magicSession.subject": "Логин", - "emails.magicSession.hello": "Здравствуйте", + "emails.magicSession.hello": "Здравствуйте,", "emails.magicSession.body": "Перейдите по ссылке, чтобы войти.", "emails.magicSession.footer": "Если вы не просили войти, используя этот адрес электронной почты, проигнорируйте это сообщение.", - "emails.magicSession.thanks": "Спасибо", + "emails.magicSession.thanks": "Спасибо,", "emails.magicSession.signature": "команда {{project}}", "emails.recovery.subject": "Сброс пароля", - "emails.recovery.hello": "Здравствуйте, {{user}}", + "emails.recovery.hello": "Здравствуйте, {{user}},", "emails.recovery.body": "Перейдите по этой ссылке для того чтобы сбросить свой пароль для проекта {{project}}", "emails.recovery.footer": "Если вы не запрашивали сброс пароля, проигнорируйте это сообщение.", - "emails.recovery.thanks": "Спасибо", + "emails.recovery.thanks": "Спасибо,", "emails.recovery.signature": "команда {{project}}", "emails.invitation.subject": "Приглашение в команду %s по проекту %s", - "emails.invitation.hello": "Здравствуйте", + "emails.invitation.hello": "Здравствуйте,", "emails.invitation.body": "Это письмо отправлено вам, потому что {{owner}} приглашает стать членом команды {{team}} в проекте {{project}}.", "emails.invitation.footer": "Если вы не заинтересованы, проигнорируйте это сообщение.", - "emails.invitation.thanks": "Спасибо", + "emails.invitation.thanks": "Спасибо,", "emails.invitation.signature": "команда {{project}}", "locale.country.unknown": "Неизвестно", "countries.af": "Афганистан", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Фраза безопасности для этого письма {{phrase}}. Вы можете доверять этому письму, если эта фраза совпадает с фразой, показанной во время входа в систему.", "emails.otpSession.thanks": "Спасибо,", "emails.otpSession.signature": "команда {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/sa.json b/app/config/locale/translations/sa.json index b82a3ed9ba..7aa8c90d77 100644 --- a/app/config/locale/translations/sa.json +++ b/app/config/locale/translations/sa.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s गणः", "emails.verification.subject": "पञ्जिकानिर्णायनम्‌", - "emails.verification.hello": "अयि {{user}}", + "emails.verification.hello": "अयि {{user}},", "emails.verification.body": "ई-पत्रनिर्णायनार्थमिदं संयोगसूत्रमनुसरतु।", "emails.verification.footer": "यदि अस्य संकेतस्य निर्णायनं नेष्यते तर्हि वात्र्तामिमामुपेक्षताम्‌।", - "emails.verification.thanks": "धन्यवादः", + "emails.verification.thanks": "धन्यवादः,", "emails.verification.signature": "{{project}} गणः", "emails.magicSession.subject": "संप्रवेशः", - "emails.magicSession.hello": "अयि", + "emails.magicSession.hello": "अयि,", "emails.magicSession.body": "संप्रवेशार्थमिदं संयोगसूत्रमनुसरतु।", "emails.magicSession.footer": "अनेन ई-पत्रण यदि संप्रवेशो नेष्यते तर्हि वात्र्तामिमामुपेक्षताम्‌।", - "emails.magicSession.thanks": "धन्यवादः", + "emails.magicSession.thanks": "धन्यवादः,", "emails.magicSession.signature": "{{project}} गणः", "emails.recovery.subject": "कूटशब्दपुनयाेजनम्‌", - "emails.recovery.hello": "अयि भो {{user}}", + "emails.recovery.hello": "अयि भो {{user}},", "emails.recovery.body": "{{project}} कूटशब्दपुनयाेजनाय संयोगमेनमनुसरतु।", "emails.recovery.footer": "यदि कूटशब्दस्य पुनयाेजनं नेष्यते तर्हि वात्र्तामिमामुपेक्षताम्‌।", - "emails.recovery.thanks": "धन्यवादः", + "emails.recovery.thanks": "धन्यवादः,", "emails.recovery.signature": "{{project}} गणः", "emails.invitation.subject": "गणस्य आमन्त्रणम्‌ %s इति %s", - "emails.invitation.hello": "अयि भो", + "emails.invitation.hello": "अयि भो,", "emails.invitation.body": "{{owner}} {{team}} गणे {{project}} मध्ये भवद्योगदानमच्छितीति हेतोः पत्रमदिं भवत्सकाशं प्रेषतिम्।", "emails.invitation.footer": "यदि भवदनिच्छा तर्हि वात्र्तामिमामुपेक्षताम्‌।", - "emails.invitation.thanks": "धन्यवादः", + "emails.invitation.thanks": "धन्यवादः,", "emails.invitation.signature": "{{project}} गणः", "locale.country.unknown": "अज्ञातम्‌ ", "countries.af": "आफगानिस्थानम्‌", @@ -239,10 +239,10 @@ "emails.magicSession.securityPhrase": "العبارة الأمنية لهذا البريد الإلكتروني هي {{phrase}}. يمكنك الوثوق بهذا البريد الإلكتروني إذا كانت هذه العبارة متطابقة مع العبارة المعروضة أثناء تسجيل الدخول.", "emails.magicSession.optionUrl": "إذا لم تتمكن من تسجيل الدخول باستخدام الزر أعلاه، يرجى زيارة الرابط التالي:", "emails.otpSession.subject": "प्रवेशनम्", - "emails.otpSession.hello": "नमस्ते।", + "emails.otpSession.hello": "नमस्ते।,", "emails.otpSession.description": "प्रविष्ट कुरु अनुसृत विश्वासनीयकोडम् यदा पृच्छ्यसे भवतः {{project}} खातायां सुरक्षितरूपेण प्रवेशे। एषः पन्द्रह मिनितेषु समाप्तिं गच्छति।", "emails.otpSession.clientInfo": "एष प्रवेशनं प्रार्थितं {{agentClient}} नाम प्रतिनिधौ {{agentDevice}} {{agentOs}} इत्यस्मिन्। यदि त्वमेव प्रवेशनं न प्रार्थितवानसि, तर्हि त्वमनेन ईपत्रेण उपेक्षितुं शक्नोसि।", "emails.otpSession.securityPhrase": "अस्य ईमेलस्य सुरक्षा वाक्यं {{phrase}} अस्ति। यदि अयं वाक्यः प्रवेशकाले दृष्टवाक्येन साम्यं याति तर्हि अस्माकं ईमेलं विश्वसनीयम् अस्ति।", - "emails.otpSession.thanks": "धन्यवादाः", + "emails.otpSession.thanks": "धन्यवादाः,", "emails.otpSession.signature": "कार्यक्रमस्य समूहः" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/sd.json b/app/config/locale/translations/sd.json index 69cad52549..3f1f7678db 100644 --- a/app/config/locale/translations/sd.json +++ b/app/config/locale/translations/sd.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s ٽيم", "emails.verification.subject": " اڪائونٽ جي تصديق", - "emails.verification.hello": "سلام {{user}}", + "emails.verification.hello": "سلام {{user}},", "emails.verification.body": "پنھنجي اي ميل ايڊريس جي تصديق ڪرڻ لاءِ ھن لنڪ تي عمل ڪريو.", "emails.verification.footer": "جيڪڏھن توھان نه پ askيا ھئا ھن ايڊريس جي تصديق ڪرڻ لاءِ ، توھان نظر انداز ڪري سگھوٿا ھن پيغام کي.", - "emails.verification.thanks": "مهرباني", + "emails.verification.thanks": "مهرباني,", "emails.verification.signature": "{{project}} ٽيم", "emails.magicSession.subject": "لاگ ان", - "emails.magicSession.hello": "هي ،", + "emails.magicSession.hello": "هي ,", "emails.magicSession.body": "لاگ ان ٿيڻ لاءِ ھن لنڪ تي عمل ڪريو.", "emails.magicSession.footer": "جيڪڏھن توھان نه پ پيا ھي لاگ ان استعمال ڪندي اي ميل ، توھان نظر انداز ڪري سگھوٿا ھن پيغام کي.", - "emails.magicSession.thanks": "مهرباني", + "emails.magicSession.thanks": "مهرباني,", "emails.magicSession.signature": "{{project}} ٽيم", "emails.recovery.subject": "پاسورڊ ري سيٽ", - "emails.recovery.hello": "هيلو {{user}}", + "emails.recovery.hello": "هيلو {{user}},", "emails.recovery.body": "ھن لنڪ تي عمل ڪريو پنھنجو {{project}} پاسورڊ ري سيٽ ڪرڻ لاءِ.", "emails.recovery.footer": "جيڪڏھن توھان نه پ پيو ھو پنھنجي پاسورڊ کي ري سيٽ ڪرڻ لاءِ ، توھان نظر انداز ڪري سگھوٿا ھن پيغام کي.", - "emails.recovery.thanks": "مهرباني", + "emails.recovery.thanks": "مهرباني,", "emails.recovery.signature": "{{project}} ٽيم", "emails.invitation.subject": "%s ٽيم %s تيجي دعوت", - "emails.invitation.hello": "هيلو", + "emails.invitation.hello": "هيلو,", "emails.invitation.body": "ھي اي ميل توھان ڏانھن موڪليو ويو آھي {اڪاڻ ته {{owner}} توھان کي دعوت ڏيڻ چاھي ٿو ته توھان {{team}} ٽيم جو ميمبر بڻجي {{project}} تي.", "emails.invitation.footer": "جيڪڏھن توھان دلچسپي نٿا رکو ، توھان نظر انداز ڪري سگھوٿا ھن پيغام کي.", - "emails.invitation.thanks": "مهرباني", + "emails.invitation.thanks": "مهرباني,", "emails.invitation.signature": "{{project}} ٽيم", "locale.country.unknown": "نامعلوم", "countries.af": "افغانستان", @@ -238,17 +238,17 @@ "emails.magicSession.clientInfo": "هي سائن ان درخواست ورتو {{agentClient}} استعمال ڪري ٿو {{agentDevice}} {{agentOs}}. جيڪڏهن توهان سائن ان جي درخواست ڪئي نه ورتي، ته توهان هن اي ميل کي محفوظ طور تي نظر انداز ڪري سگهو ٿا.", "emails.magicSession.securityPhrase": "اس ای میل جو سیکيورٽي جملو {{phrase}} آهي. جيڪڏهن هن جملو توهان جي سائن ان وقتي ڏيکاري واري جملي سان ميل آهي ته توهان اس ای میل تي اعتماد ڪري سگھو ٿا.", "emails.otpSession.subject": "پروجيڪٽ جي لاگ ان", - "emails.otpSession.hello": "ہيلو،", + "emails.otpSession.hello": "ہيلو,", "emails.otpSession.description": "جڏهن توهان کي محفوظ طريقي سان اپني {{project}} اڪائونٽ ۾ سائن ان ڪرڻ لاءِ ڪہي ويندي، ته هيٺيان دنل ويريفڪيشن ڪوڊ ڏيو. هي 15 منٽن ۾ ختم ٿي ويندي.", "emails.otpSession.clientInfo": "هي سائن ان توهان جو درخواست گهريو ويو آهي {{agentClient}} جي واپار ۾ {{agentDevice}} {{agentOs}} تي. جيڪڏهن توهان سائن ان جي درخواست ڪئي نه آهي، ته توهان هن ايميل کي نظر انداز ڪري سگهو ٿا.", "emails.otpSession.securityPhrase": "هن ای میل لاءِ سیکيورٽي جملو {{phrase}} آھي. توهان هن ای میل تي اعتماد ڪري سگهو ٿا جيڪڏهن هن جملو لاڳو ٿيندڙ جملي سان ميل کاندي.", - "emails.otpSession.thanks": "مهرباني", + "emails.otpSession.thanks": "مهرباني,", "emails.otpSession.signature": "پروجيڪٽ جي ٽيم", "emails.certificate.subject": "%s لاءِ سند جو ناکامی", - "emails.certificate.hello": "هيلو", + "emails.certificate.hello": "هيلو,", "emails.certificate.body": "توهان جي ڊومين '{{domain}}' لاءِ سرٽيفڪيٽ ٺاهڻ جو نه ٿي سگهيو. هي ڪوشش نمبر {{attempt}} آهي، ۽ ناڪامي جو سبب ٿيو: {{error}}", "emails.certificate.footer": "توهان جو اڳيون سرٽيفڪيٽ اولهو فئيلر جي ݙينهن کان ٣٠ ݙينهن لاءِ ماني ويندو. اسان ان جي چھان بني جي بھرپور خواهش ڪنداسين، نہ ته توهان جو ݙومين بغير ڪوري SSL ڪميونڪيشن آڻي ويندي.", - "emails.certificate.thanks": "شُكريا", + "emails.certificate.thanks": "شُكريا,", "emails.certificate.signature": "ٽيم", "sms.verification.body": "{{secret}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/si.json b/app/config/locale/translations/si.json index f1c4b7c86b..536e8d3604 100644 --- a/app/config/locale/translations/si.json +++ b/app/config/locale/translations/si.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s කණ්ඩායම", "emails.verification.subject": "ගිණුම් සත්‍යාපනය", - "emails.verification.hello": "හේයි {{user}}", + "emails.verification.hello": "හේයි {{user}},", "emails.verification.body": "ඔබගේ විද්‍යුත් තැපැල් ලිපිනය සත්‍යාපනය කිරීමට මෙම සම්බන්ධකය අනුගමනය කරන්න.", "emails.verification.footer": "මෙම ලිපිනය සත්‍යාපනය කරන ලෙස ඔබ ඉල්ලුවේ නැත්නම්, ඔබට මෙම පණිවිඩය නොසලකා හැරිය හැක.", - "emails.verification.thanks": "ස්තුතියි", + "emails.verification.thanks": "ස්තුතියි,", "emails.verification.signature": "{{project}} කණ්ඩායම", "emails.magicSession.subject": "ප්‍රවේශ වන්න", - "emails.magicSession.hello": "හේයි", + "emails.magicSession.hello": "හේයි,", "emails.magicSession.body": "ප්‍රවේශ වීමට මෙම සම්බන්ධකය අනුගමනය කරන්න.", "emails.magicSession.footer": "මෙම විද්‍යුත් තැපෑල භාවිතයෙන් ප්‍රවේශ වීමට ඔබ ඉල්ලුවේ නැත්නම්, ඔබට මෙම පණිවිඩය නොසලකා හැරිය හැක.", - "emails.magicSession.thanks": "ස්තුතියි", + "emails.magicSession.thanks": "ස්තුතියි,", "emails.magicSession.signature": "{{project}} කණ්ඩායම", "emails.recovery.subject": "මුරපද යළි පිහිටුවීම", - "emails.recovery.hello": "ආයුබෝවන් {{user}}", + "emails.recovery.hello": "ආයුබෝවන් {{user}},", "emails.recovery.body": "ඔබගේ {{project}} මුරපදය නැවත සැකසීමට මෙම සම්බන්ධකය අනුගමනය කරන්න.", "emails.recovery.footer": "ඔබගේ මුරපදය නැවත සකසන ලෙස ඔබ ඉල්ලුවේ නැත්නම්, ඔබට මෙම පණිවිඩය නොසලකා හැරිය හැක.", - "emails.recovery.thanks": "ස්තුතියි", + "emails.recovery.thanks": "ස්තුතියි,", "emails.recovery.signature": "{{project}} කණ්ඩායම", "emails.invitation.subject": "%s කණ්ඩායමට ආරාධනා %s හි", - "emails.invitation.hello": "ආයුබෝවන්", + "emails.invitation.hello": "ආයුබෝවන්,", "emails.invitation.body": "මෙම තැපැල් ඔබට එව්වේ, {{owner}} හට {{project}} හි {{team}} කණ්ඩායමේ සාමාජිකයෙකු වීමට ඔබට ආරාධනා කිරීමට අවශ්‍ය වූ බැවිනි.", "emails.invitation.footer": "ඔබ උනන්දුවක් නොදක්වන්නේ නම්, ඔබට මෙම පණිවිඩය නොසලකා හැරිය හැක.", - "emails.invitation.thanks": "ස්තුතියි", + "emails.invitation.thanks": "ස්තුතියි,", "emails.invitation.signature": "{{project}} කණ්ඩායම", "locale.country.unknown": "නොදන්නා", "countries.af": "ඇෆ්ගනිස්ථානය", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "මෙම ඊමේල්ට සඳහා ආරක්ෂක පාඨය {{phrase}}. පුරන්න විට පෙන්වන පාඨයට මෙම පාඨය ගැලපෙනවා නම්, ඔබට මෙම ඊමේල් විශ්වාස කළ හැකිය.", "emails.otpSession.thanks": "ස්තුතියි,", "emails.otpSession.signature": "{{project}} කණ්ඩායම" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/sk.json b/app/config/locale/translations/sk.json index 457e756c9a..93c12c0881 100644 --- a/app/config/locale/translations/sk.json +++ b/app/config/locale/translations/sk.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s Tím", "emails.verification.subject": "Overenie účtu", - "emails.verification.hello": "Ahoj {{user}}", + "emails.verification.hello": "Ahoj {{user}},", "emails.verification.body": "Použi tento link pre overenie svojej emailovej adresy.", "emails.verification.footer": "Ak si nepožiadal o overenie tejto adresy, môžeš túto správu ignorovať.", - "emails.verification.thanks": "Ďakujeme.", + "emails.verification.thanks": "Ďakujeme.,", "emails.verification.signature": "{{project}} tím", "emails.magicSession.subject": "Prihlásenie", - "emails.magicSession.hello": "Ahoj", + "emails.magicSession.hello": "Ahoj,", "emails.magicSession.body": "Použi tento link pre prihlásenie.", "emails.magicSession.footer": "Ak si nepožiadal o prihlásenie cez email, túto správu môžeš ignorovať.", - "emails.magicSession.thanks": "Ďakujeme", + "emails.magicSession.thanks": "Ďakujeme,", "emails.magicSession.signature": "{{project}} tím", "emails.recovery.subject": "Obnovenie hesla", - "emails.recovery.hello": "Ahoj {{user}}", + "emails.recovery.hello": "Ahoj {{user}},", "emails.recovery.body": "Použi tento link pre obnovenie svojho {{project}} hesla.", "emails.recovery.footer": "Ak si nepožiadal o obnovu svojho hesla, túto správu môžeš ignorovať.", - "emails.recovery.thanks": "Ďakujeme", + "emails.recovery.thanks": "Ďakujeme,", "emails.recovery.signature": "{{project}} tím", "emails.invitation.subject": "Pozvánka do %s Tímu v %s", - "emails.invitation.hello": "Ahoj", + "emails.invitation.hello": "Ahoj,", "emails.invitation.body": "Tento email ti bol zaslaný, pretože {{owner}} ťa pozval, aby si sa stal členom {{team}} tímu v projekte {{project}}.", "emails.invitation.footer": "Ak nemáš záujem, môžeš túto správu ignorovať.", - "emails.invitation.thanks": "Ďakujeme", + "emails.invitation.thanks": "Ďakujeme,", "emails.invitation.signature": "{{project}} tím", "locale.country.unknown": "Neznámy", "countries.af": "Afganistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Bezpečnostná fráza pre tento e-mail je {{phrase}}. Tento e-mail môžete dôverovať, ak táto fráza zodpovedá fráze zobrazenej počas prihlasovania.", "emails.otpSession.thanks": "Ďakujem,", "emails.otpSession.signature": "tím {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/sl.json b/app/config/locale/translations/sl.json index ec7b4c1ebf..f7c4f41255 100644 --- a/app/config/locale/translations/sl.json +++ b/app/config/locale/translations/sl.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s Ekipa", "emails.verification.subject": "", - "emails.verification.hello": "", + "emails.verification.hello": ",", "emails.verification.body": "", "emails.verification.footer": "", - "emails.verification.thanks": "", + "emails.verification.thanks": ",", "emails.verification.signature": "", "emails.magicSession.subject": "", - "emails.magicSession.hello": "", + "emails.magicSession.hello": ",", "emails.magicSession.body": "", "emails.magicSession.footer": "", - "emails.magicSession.thanks": "", + "emails.magicSession.thanks": ",", "emails.magicSession.signature": "", "emails.recovery.subject": "", - "emails.recovery.hello": "", + "emails.recovery.hello": ",", "emails.recovery.body": "", "emails.recovery.footer": "", - "emails.recovery.thanks": "", + "emails.recovery.thanks": ",", "emails.recovery.signature": "", "emails.invitation.subject": "", - "emails.invitation.hello": "", + "emails.invitation.hello": ",", "emails.invitation.body": "", "emails.invitation.footer": "", - "emails.invitation.thanks": "", + "emails.invitation.thanks": ",", "emails.invitation.signature": "", "locale.country.unknown": "Neznano", "countries.af": "Afganistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Varnostni stavek za to e-pošto je {{phrase}}. Temu e-sporočilu lahko zaupate, če se ta stavek ujema s stavkom, ki je prikazan ob prijavi.", "emails.otpSession.thanks": "Hvala,", "emails.otpSession.signature": "ekipa {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/sn.json b/app/config/locale/translations/sn.json index d8b2f2c682..d17a98ff42 100644 --- a/app/config/locale/translations/sn.json +++ b/app/config/locale/translations/sn.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "Chikwata che%s", "emails.verification.subject": "Kuratidzi kuti ndiwe muridzi weakaundi", - "emails.verification.hello": "Hesi {{user}}", + "emails.verification.hello": "Hesi {{user}},", "emails.verification.body": "Tevedza chinongedzo ichi kuti uratidze kuti kero iyi ndeyako.", "emails.verification.footer": "Kana usina kukumbira kuti uratidze kuti kero iyi ndeyako, unogona kufuratira meseji iyi.", - "emails.verification.thanks": "Ndatenda", + "emails.verification.thanks": "Ndatenda,", "emails.verification.signature": "Chikwata che{{project}}", "emails.magicSession.subject": "Pinda", - "emails.magicSession.hello": "Hesi", + "emails.magicSession.hello": "Hesi,", "emails.magicSession.body": "Baya chinongedzo ichi kuti upinde muakaundi yako.", "emails.magicSession.footer": "Kana usina kukumbira kupinda muakaundi yako uchishandisa email iyi, unogona kufuratira meseji iyi.", - "emails.magicSession.thanks": "Ndatenda", + "emails.magicSession.thanks": "Ndatenda,", "emails.magicSession.signature": "Chikwata che{{project}}", "emails.recovery.subject": "Kuchinja pasiwedhi", - "emails.recovery.hello": "Mhoro {{user}}", + "emails.recovery.hello": "Mhoro {{user}},", "emails.recovery.body": "Baya chinongedzo ichi kuti uchinje pasiwedhi yako ye{{project}}.", "emails.recovery.footer": "Kana usina kukumbira kuchinja pasiwedhi yako, unogona kufuratira meseji iyi.", - "emails.recovery.thanks": "Ndatenda", + "emails.recovery.thanks": "Ndatenda,", "emails.recovery.signature": "Chikwata che{{project}}", "emails.invitation.subject": "Kukokwa kuchikwata che%s ku%s", - "emails.invitation.hello": "Mhoro", + "emails.invitation.hello": "Mhoro,", "emails.invitation.body": "Tsamba iyi yatumirwa kwauri nekuti {{owner}} anga achida kuti uve nhengo yechikwata che{{team}} pachirongwa che{{project}}.", "emails.invitation.footer": "Kana usiri kufarira kuve nhengo yechikwata ichi, unogona kufuratira meseji iyi.", - "emails.invitation.thanks": "Ndatenda", + "emails.invitation.thanks": "Ndatenda,", "emails.invitation.signature": "Chikwata che{{project}}", "locale.country.unknown": "Haizivikanwe", "countries.af": "Afuganisitani", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Chirevo chekuchengetedza cheemail iyi ndechekuti {{phrase}}. Unogona kuvimba neemail iyi kana chirevo ichi chichienderana nechirevo chakaratidzwa panguva yekupinda.", "emails.otpSession.thanks": "Ndatenda,", "emails.otpSession.signature": "chikwata {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/sq.json b/app/config/locale/translations/sq.json index 0fb066a8ea..85aa6637f6 100644 --- a/app/config/locale/translations/sq.json +++ b/app/config/locale/translations/sq.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "Grup %s", "emails.verification.subject": "", - "emails.verification.hello": "", + "emails.verification.hello": ",", "emails.verification.body": "", "emails.verification.footer": "", - "emails.verification.thanks": "", + "emails.verification.thanks": ",", "emails.verification.signature": "", "emails.magicSession.subject": "", - "emails.magicSession.hello": "", + "emails.magicSession.hello": ",", "emails.magicSession.body": "", "emails.magicSession.footer": "", - "emails.magicSession.thanks": "", + "emails.magicSession.thanks": ",", "emails.magicSession.signature": "", "emails.recovery.subject": "", - "emails.recovery.hello": "", + "emails.recovery.hello": ",", "emails.recovery.body": "", "emails.recovery.footer": "", - "emails.recovery.thanks": "", + "emails.recovery.thanks": ",", "emails.recovery.signature": "", "emails.invitation.subject": "", - "emails.invitation.hello": "", + "emails.invitation.hello": ",", "emails.invitation.body": "", "emails.invitation.footer": "", - "emails.invitation.thanks": "", + "emails.invitation.thanks": ",", "emails.invitation.signature": "", "locale.country.unknown": "I panjohur", "countries.af": "Afganistani", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Fjala e sigurisë për këtë email është {{phrase}}. Ju mund të besoni këtë email nëse kjo fjalë përputhet me fjalën që shfaqet gjatë kyçjes.", "emails.otpSession.thanks": "Faleminderit,", "emails.otpSession.signature": "ekipi i {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/sv.json b/app/config/locale/translations/sv.json index b838c05084..8997fd53f8 100644 --- a/app/config/locale/translations/sv.json +++ b/app/config/locale/translations/sv.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s-teamet", "emails.verification.subject": "Verifiera konto", - "emails.verification.hello": "Hej {{user}}", + "emails.verification.hello": "Hej {{user}},", "emails.verification.body": "Klicka på denna länk för att verifiera din email", "emails.verification.footer": "Om du inte bad om att verifiera den här e-postadressen kan du ignorera detta mail.", - "emails.verification.thanks": "Tack", + "emails.verification.thanks": "Tack,", "emails.verification.signature": "{{project}} teamet", "emails.magicSession.subject": "Logga in", - "emails.magicSession.hello": "Hej", + "emails.magicSession.hello": "Hej,", "emails.magicSession.body": "Klicka på denna länk för att logga in.", "emails.magicSession.footer": "Om du inte bad om att logga in med denna e-postadress kan du ignorera detta mail.", - "emails.magicSession.thanks": "Tack", + "emails.magicSession.thanks": "Tack,", "emails.magicSession.signature": "{{project}} teamet", "emails.recovery.subject": "Återställ lösenord", - "emails.recovery.hello": "Hej {{user}}", + "emails.recovery.hello": "Hej {{user}},", "emails.recovery.body": "Klicka på denna länk för att återställa lösenordet på {{project}}", "emails.recovery.footer": "Om du inte bad om att återställa ditt lösenord kan du ignorera detta mail.", - "emails.recovery.thanks": "Tack", + "emails.recovery.thanks": "Tack,", "emails.recovery.signature": "{{project}} teamet", "emails.invitation.subject": "Inbjudan till %s teamet på %s", - "emails.invitation.hello": "Hej", + "emails.invitation.hello": "Hej,", "emails.invitation.body": "Detta mail skickades till dig eftersom {{owner}} ville bjuda in dig att bli medlem i teamet {{team}} på {{project}}.", "emails.invitation.footer": "Om du inte är intresserad kan du ignorera detta mail.", - "emails.invitation.thanks": "Tack", + "emails.invitation.thanks": "Tack,", "emails.invitation.signature": "{{project}} teamet", "locale.country.unknown": "Okänt", "countries.af": "Afghanistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Säkerhetsfrasen för detta e-postmeddelande är {{phrase}}. Du kan lita på detta e-postmeddelande om frasen stämmer överens med frasen som visades vid inloggningen.", "emails.otpSession.thanks": "Tack,", "emails.otpSession.signature": "{{project}} team" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/ta.json b/app/config/locale/translations/ta.json index 659306c977..f0695867a9 100644 --- a/app/config/locale/translations/ta.json +++ b/app/config/locale/translations/ta.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s குழு", "emails.verification.subject": "கணக்கு சரிபார்ப்பு", - "emails.verification.hello": "ஏய் {{user}}", + "emails.verification.hello": "ஏய் {{user}},", "emails.verification.body": "உங்கள் மின்னஞ்சல் முகவரியைச் சரிபார்க்க இந்த இணைப்பைப் பின்தொடரவும்.", "emails.verification.footer": "இந்த முகவரியைச் சரிபார்க்கும்படி உங்களிடம் கேட்கப்படவில்லை என்றால், இந்தச் செய்தியை நீங்கள் புறக்கணிக்கலாம்.", - "emails.verification.thanks": "நன்றி", + "emails.verification.thanks": "நன்றி,", "emails.verification.signature": "{{project}} குழு ", "emails.magicSession.subject": "உள்நுழைய", - "emails.magicSession.hello": "ஏய்", + "emails.magicSession.hello": "ஏய்,", "emails.magicSession.body": "இந்த இணைப்பைப் பின்தொடரவும் உள்நுழைய", "emails.magicSession.footer": "இந்த மின்னஞ்சலைப் பயன்படுத்தி உள்நுழையுமாறு உங்களிடம் கேட்கப்படாவிட்டால், இந்தச் செய்தியைப் புறக்கணிக்கலாம்.", - "emails.magicSession.thanks": "நன்றி", + "emails.magicSession.thanks": "நன்றி,", "emails.magicSession.signature": "{{project}} குழு", "emails.recovery.subject": "கடவுச்சொல் மீட்டமைப்பு", - "emails.recovery.hello": "வணக்கம் {{user}}", + "emails.recovery.hello": "வணக்கம் {{user}},", "emails.recovery.body": "மீட்டமைக்க இந்த இணைப்பைப் பின்தொடரவும் {{project}} கடவுச்சொல்.", "emails.recovery.footer": "உங்கள் கடவுச்சொல்லை மீட்டமைக்கும்படி உங்களிடம் கேட்கப்படவில்லை என்றால், இந்தச் செய்தியை நீங்கள் புறக்கணிக்கலாம்.", - "emails.recovery.thanks": "நன்றி", + "emails.recovery.thanks": "நன்றி,", "emails.recovery.signature": "{{project}} குழு", "emails.invitation.subject": "அழைப்பிதழ் %s குழு %s ", - "emails.invitation.hello": "வணக்கம்", + "emails.invitation.hello": "வணக்கம்,", "emails.invitation.body": "{{project}} இல் {{team}} குழுவில் உறுப்பினராக உங்களை {{owner}} அழைக்க விரும்புவதால், இந்த அஞ்சல் உங்களுக்கு அனுப்பப்பட்டது.", "emails.invitation.footer": "உங்களுக்கு ஆர்வம் இல்லை என்றால், இந்த செய்தியை நீங்கள் புறக்கணிக்கலாம்.", - "emails.invitation.thanks": "நன்றி", + "emails.invitation.thanks": "நன்றி,", "emails.invitation.signature": "{{project}} குழு", "locale.country.unknown": "அறியவில்லை", "countries.af": "ஆப்கானித்தான்", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "இந்த மின்னஞ்சலுக்கான பாதுகாப்பு வாசகம் {{phrase}} ஆகும். இந்த வாசகம் உள்நுழையும் போது காட்டப்பட்ட வாசகத்துடன் பொருந்துமானால், இந்த மின்னஞ்சலை நம்பலாம்.", "emails.otpSession.thanks": "நன்றி,", "emails.otpSession.signature": "{{project}} குழு" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/te.json b/app/config/locale/translations/te.json index 019b4581ca..870b0b82a2 100644 --- a/app/config/locale/translations/te.json +++ b/app/config/locale/translations/te.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s జట్టు", "emails.verification.subject": "ఖాతా ధృవీకరణ", - "emails.verification.hello": "నమస్కారము {{user}}", + "emails.verification.hello": "నమస్కారము {{user}},", "emails.verification.body": "ఈ లింక్ ద్వారా ఇమెయిల్ ని ధృవీకరించండి", "emails.verification.footer": "మీరు ఈ చిరునామాను ధృవీకరించమని అడగనట్లయితే, మీరు ఈ సందేశాన్ని విస్మరించవచ్చు", - "emails.verification.thanks": "ధన్యవాదాలు", + "emails.verification.thanks": "ధన్యవాదాలు,", "emails.verification.signature": "{{project}} జట్", "emails.magicSession.subject": "లాగిన్", - "emails.magicSession.hello": "నమస్కారము", + "emails.magicSession.hello": "నమస్కారము,", "emails.magicSession.body": "లాగిన్ చేయడానికి ఈ లింక్ ని అనుసరించండి", "emails.magicSession.footer": "మీరు ఈ ఇమెయిల్ ని ఉపయోగించి లాగిన్ చేయమని అడగకపోతే, మీరు ఈ సందేశాన్ని విస్మరించవచ్చు", - "emails.magicSession.thanks": "ధన్యవాదాలు", + "emails.magicSession.thanks": "ధన్యవాదాలు,", "emails.magicSession.signature": "{{project}} జట్", "emails.recovery.subject": "పాస్వర్డ్ రీసెట్", - "emails.recovery.hello": "నమస్కారమ {{user}}", + "emails.recovery.hello": "నమస్కారమ {{user}},", "emails.recovery.body": "మీ {{project}} పాస్వర్డ్ ని రీసెట్ చేయడానికి ఈ లింక్ ని అనుసరించండి", "emails.recovery.footer": "మీరు మీ పాస్వర్డ్ ని రీసెట్ చేయమని అడగనట్లయితే, మీరు ఈ సందేశాన్ని విస్మరించవచ్చు", - "emails.recovery.thanks": "ధన్యవాదాల", + "emails.recovery.thanks": "ధన్యవాదాల,", "emails.recovery.signature": "{{project}} జట్", "emails.invitation.subject": "%s వద్ద %s బృందానికి ఆహ్వానం", - "emails.invitation.hello": "నమస్కారమ", + "emails.invitation.hello": "నమస్కారమ,", "emails.invitation.body": "{{owner}} మిమ్మల్ని {{project}} లో {{team}} బృందంలో సభ్యునిగా ఉండమని ఆహ్వానించాలనుకుంటున్నందున ఈ మెయిల్ మీకు పంపబడింది.", "emails.invitation.footer": "మీకు ఆసక్తి లేకుంటే, మీరు ఈ సందేశాన్ని విస్మరించవచ్చు.", - "emails.invitation.thanks": "ధన్యవాదాల", + "emails.invitation.thanks": "ధన్యవాదాల,", "emails.invitation.signature": "{{project}} జట్", "locale.country.unknown": "తెలియని", "countries.af": "ఆఫ్ఘనిస్తాన్", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "ఈ ఇమెయిల్‌కు భద్రతా పదం {{phrase}}. మీరు సైన్ ఇన్ సమయంలో చూపించబడిన పదంతో ఈ పదం సరిపోలుస్తుంటే ఈ ఇమెయిల్‌ను నమ్మవచ్చు.", "emails.otpSession.thanks": "ధన్యవాదాలు,", "emails.otpSession.signature": "ప్రాజెక్టు బృందం" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/th.json b/app/config/locale/translations/th.json index 97d224de1f..2ec02ee8e2 100644 --- a/app/config/locale/translations/th.json +++ b/app/config/locale/translations/th.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "ทีม %s", "emails.verification.subject": "การยืนยันบัญชีผู้ใช้", - "emails.verification.hello": "เรียนคุณ {{user}}", + "emails.verification.hello": "เรียนคุณ {{user}},", "emails.verification.body": "กดเข้าไปที่ลิงก์นี้เพื่อยืนยันอีเมลของท่าน", "emails.verification.footer": "หากท่านไม่ได้ต้องการที่จะยืนยันอีเมลนี้ ท่านสามารถเพิกเฉยข้อความนี้ได้", - "emails.verification.thanks": "ขอบคุณ", + "emails.verification.thanks": "ขอบคุณ,", "emails.verification.signature": "ทีม {{project}}", "emails.magicSession.subject": "เข้าสู่ระบบ", - "emails.magicSession.hello": "เรียนผู้ใช้งาน", + "emails.magicSession.hello": "เรียนผู้ใช้งาน,", "emails.magicSession.body": "กดเข้าไปที่ลิงก์นี้เพื่อเข้าสู่ระบบ", "emails.magicSession.footer": "หากท่านไม่ได้ต้องการที่จะเข้าสู่ระบบด้วยอีเมลนี้ ท่านสามารถเพิกเฉยข้อความนี้ได้", - "emails.magicSession.thanks": "ขอบคุณ", + "emails.magicSession.thanks": "ขอบคุณ,", "emails.magicSession.signature": "ทีม {{project}}", "emails.recovery.subject": "รีเซ็ตรหัสผ่าน", - "emails.recovery.hello": "เรียนคุณ {{user}}", + "emails.recovery.hello": "เรียนคุณ {{user}},", "emails.recovery.body": "กดเข้าไปที่ลิงก์นี้เพื่อรีเซ็ตรหัสผ่านสำหรับโปรเจกต์ {{project}} ของท่าน", "emails.recovery.footer": "หากท่านไม่ได้ต้องการที่จะรีเซ็ตรหัสผ่านของท่าน ท่านสามารถเพิกเฉยข้อความนี้ได้", - "emails.recovery.thanks": "ขอบคุณ", + "emails.recovery.thanks": "ขอบคุณ,", "emails.recovery.signature": "ทีม {{project}}", "emails.invitation.subject": "เรียนเชิญเข้าร่วม ทีม %s จากโปรเจกต์ %s", - "emails.invitation.hello": "สวัสดี", + "emails.invitation.hello": "สวัสดี,", "emails.invitation.body": "ท่านได้รับอีเมลฉบับนี้เนื่องจาก {{owner}} ต้องการที่จะเชิญชวนคุณเข้าร่วมเป็นส่วนหนึ่งของ ทีม {{team}} จากโปรเจกต์ {{project}}", "emails.invitation.footer": "หากท่านไม่ได้สนใจที่จะเข้าร่วม ท่านสามารถเพิกเฉยข้อความนี้ได้", - "emails.invitation.thanks": "ขอบคุณ", + "emails.invitation.thanks": "ขอบคุณ,", "emails.invitation.signature": "ทีม {{project}}", "locale.country.unknown": "ไม่ทราบ", "countries.af": "อัฟกานิสถาน", @@ -243,6 +243,6 @@ "emails.otpSession.description": "ป้อนรหัสยืนยันต่อไปนี้เมื่อได้รับการสั่งให้ทำเพื่อลงชื่อเข้าใช้บัญชี {{project}} ของคุณอย่างปลอดภัย รหัสนี้จะหมดอายุใน 15 นาที.", "emails.otpSession.clientInfo": "การลงชื่อเข้าใช้งานนี้ได้รับการทำผ่าน {{agentClient}} บน {{agentDevice}} {{agentOs}} หากคุณไม่ได้ทำการขอลงชื่อเข้าใช้นี้ คุณสามารถเพิกเฉยต่ออีเมลนี้ได้เลย", "emails.otpSession.securityPhrase": "วลีความปลอดภัยสำหรับอีเมลนี้คือ {{phrase}} คุณสามารถเชื่อถืออีเมลนี้ได้หากวลีนี้ตรงกับวลีที่แสดงขณะลงชื่อเข้าใช้งาน.", - "emails.otpSession.thanks": "ขอบคุณครับ/ค่ะ", + "emails.otpSession.thanks": "ขอบคุณครับ/ค่ะ,", "emails.otpSession.signature": "ทีม {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/tl.json b/app/config/locale/translations/tl.json index e6df9f8aef..6d0be01095 100644 --- a/app/config/locale/translations/tl.json +++ b/app/config/locale/translations/tl.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "Pangkat ng %s", "emails.verification.subject": "Pagpapatunay ng account", - "emails.verification.hello": "Kamusta {{user}}", + "emails.verification.hello": "Kamusta {{user}},", "emails.verification.body": "Sundin ang link na ito upang ma-verify ang iyong email address.", "emails.verification.footer": "Kung hindi mo hiningi na i-verify ang address na ito, maaari mong balewalain ang mensahe na ito.", - "emails.verification.thanks": "Salamat", + "emails.verification.thanks": "Salamat,", "emails.verification.signature": "Pangkat ng {{project}}", "emails.magicSession.subject": "Mag log in", - "emails.magicSession.hello": "Kamusta ", + "emails.magicSession.hello": "Kamusta ,", "emails.magicSession.body": "Sundin ang link na ito upang mag-login.", "emails.magicSession.footer": "Kung hindi mo hiningi na mag-login gamit ang email na ito, maaari mong balewalain ang mensahe na ito.", - "emails.magicSession.thanks": "Salamat", + "emails.magicSession.thanks": "Salamat,", "emails.magicSession.signature": "Pangkat ng {{project}}", "emails.recovery.subject": "I-reset ang password", - "emails.recovery.hello": "Kamusta {{user}}", + "emails.recovery.hello": "Kamusta {{user}},", "emails.recovery.body": "Sundin ang link na ito upang i-reset ang password ng iyong {{project}}.", "emails.recovery.footer": "Kung hindi mo hiningi na i-reset ang iyong password, maaari mong balewalain ang mensahe na ito.", - "emails.recovery.thanks": "Salamat", + "emails.recovery.thanks": "Salamat,", "emails.recovery.signature": "Pangkat ng {{project}}", "emails.invitation.subject": "Imbitasyon para sa Pangkat %s sa %s", - "emails.invitation.hello": "Kamusta", + "emails.invitation.hello": "Kamusta,", "emails.invitation.body": "Ipinadala sa iyo ang mail na ito dahil gusto kang imbitahan ni {{owner}} na maging miyembro ng Pangkat {{team}} sa ilalim ng proyektong {{project}}.", "emails.invitation.footer": "Kung ikaw ay hindi interesado, maaari mong balewalain ang mensaheng ito.", - "emails.invitation.thanks": "Salamat", + "emails.invitation.thanks": "Salamat,", "emails.invitation.signature": "Pangkat ng {{project}}", "locale.country.unknown": "Hindi kilala", "countries.af": "Apganistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Ang security phrase para sa email na ito ay {{phrase}}. Maaari mong pagkatiwalaan ang email na ito kung ang phrase na ito ay tugma sa phrase na ipinakita noong nag-sign in.", "emails.otpSession.thanks": "Salamat,", "emails.otpSession.signature": "team ng {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/tr.json b/app/config/locale/translations/tr.json index 808a20576c..115050c2e2 100644 --- a/app/config/locale/translations/tr.json +++ b/app/config/locale/translations/tr.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s Takımı", "emails.verification.subject": "Hesabını Doğrula", - "emails.verification.hello": "Merhaba {{user}}", + "emails.verification.hello": "Merhaba {{user}},", "emails.verification.body": "Eposta adresini doğrulamak için bu bağlantıyı kullanın.", "emails.verification.footer": "Eğer bu eposta adresini doğrulamak isteyen siz değilseniz devam etmeyin.", - "emails.verification.thanks": "Teşekkürler", + "emails.verification.thanks": "Teşekkürler,", "emails.verification.signature": "{{project}} takımı", "emails.magicSession.subject": "Giriş", - "emails.magicSession.hello": "Merhaba", + "emails.magicSession.hello": "Merhaba,", "emails.magicSession.body": "Giriş yapmak için tıklayın.", "emails.magicSession.footer": "Eğer bu eposta adresini kullanarak giriş yapmak istemediyseniz devam etmeyin.", - "emails.magicSession.thanks": "Teşekkürler", + "emails.magicSession.thanks": "Teşekkürler,", "emails.magicSession.signature": "{{project}} takımı", "emails.recovery.subject": "Şifremi Sıfırla", - "emails.recovery.hello": "Merhaba {{user}}", + "emails.recovery.hello": "Merhaba {{user}},", "emails.recovery.body": "{{project}} şifrenizi sıfırlamak için bu bağlantıyı kullanın.", "emails.recovery.footer": "Eğer şifre sıfırlama talebinde bulunmadıysanız devam etmeyin.", - "emails.recovery.thanks": "Teşekkürler", + "emails.recovery.thanks": "Teşekkürler,", "emails.recovery.signature": "{{project}} takımı", "emails.invitation.subject": "%s üzerinde %s Takımına Davet", - "emails.invitation.hello": "Merhaba", + "emails.invitation.hello": "Merhaba,", "emails.invitation.body": "Bu epostayı aldınız, çünkü {{owner}} sizi {{project}} üzerinde {{team}} takımının üyesi olmaya davet etti.", "emails.invitation.footer": "Eğer ilgilenmiyorsanız devam etmeyin.", - "emails.invitation.thanks": "Teşekkürler", + "emails.invitation.thanks": "Teşekkürler,", "emails.invitation.signature": "{{project}} takımı", "locale.country.unknown": "Bilinmeyen", "countries.af": "Afganistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Bu e-postanın güvenlik ifadesi {{phrase}}. Giriş sırasında gösterilen ifade ile bu ifade eşleşiyorsa bu e-postaya güvenebilirsiniz.", "emails.otpSession.thanks": "Teşekkürler,", "emails.otpSession.signature": "{{project}} takımı" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/uk.json b/app/config/locale/translations/uk.json index 78e3a6c556..3f66bd1c58 100644 --- a/app/config/locale/translations/uk.json +++ b/app/config/locale/translations/uk.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "Команда %s", "emails.verification.subject": "Верифікація акаунта", - "emails.verification.hello": "Вітаємо, {{user}}", + "emails.verification.hello": "Вітаємо, {{user}},", "emails.verification.body": "Перейдіть за цим посиланням, щоб підтвердити свою електронну адресу.", "emails.verification.footer": "Якщо ви не запитували підтвердження цієї адреси, ви можете ігнорувати це повідомлення.", - "emails.verification.thanks": "Дякуємо", + "emails.verification.thanks": "Дякуємо,", "emails.verification.signature": "команда {{project}}", "emails.magicSession.subject": "Логін", - "emails.magicSession.hello": "Вітаємо", + "emails.magicSession.hello": "Вітаємо,", "emails.magicSession.body": "Перейдіть за цим посиланням, щоб увійти.", "emails.magicSession.footer": "Якщо ви не просили увійти за допомогою цієї електронної пошти, ви можете ігнорувати це повідомлення.", - "emails.magicSession.thanks": "Дякуємо", + "emails.magicSession.thanks": "Дякуємо,", "emails.magicSession.signature": "команда {{project}}", "emails.recovery.subject": "Скидання пароля", - "emails.recovery.hello": "Вітаємо, {{user}}", + "emails.recovery.hello": "Вітаємо, {{user}},", "emails.recovery.body": "Перейдіть за цим посиланням для того щоб скинути свій пароль для проекту {{project}}", "emails.recovery.footer": "Якщо ви не запитували скидання паролю, проігноруйте це повідомлення.", - "emails.recovery.thanks": "Дякуємо", + "emails.recovery.thanks": "Дякуємо,", "emails.recovery.signature": "команда {{project}}", "emails.invitation.subject": "Запрошення до %s Команди у %s", - "emails.invitation.hello": "Вітаємо", + "emails.invitation.hello": "Вітаємо,", "emails.invitation.body": "Цей лист був надісланий вам тому що {{owner}} запрошує вас стати членом команди {{team}} у проекті {{project}}.", "emails.invitation.footer": "Якщо ви не зацікавлені, проігноруйте це повідомлення.", - "emails.invitation.thanks": "Дякуємо", + "emails.invitation.thanks": "Дякуємо,", "emails.invitation.signature": "команда {{project}}", "locale.country.unknown": "Невідомо", "countries.af": "Афганістан", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Фраза безпеки для цього електронного листа - {{phrase}}. Ви можете довіряти цьому електронному листу, якщо ця фраза відповідає фразі, показаній під час входу в систему.", "emails.otpSession.thanks": "Дякую,", "emails.otpSession.signature": "команда {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/ur.json b/app/config/locale/translations/ur.json index 060cea0736..9d6aa47762 100644 --- a/app/config/locale/translations/ur.json +++ b/app/config/locale/translations/ur.json @@ -4,28 +4,28 @@ "settings.direction": "rtl", "emails.sender": "%s ٹیم", "emails.verification.subject": "اکاؤنٹ کی تصدیق", - "emails.verification.hello": "خوش آمدید {{user}}", + "emails.verification.hello": "خوش آمدید {{user}}،", "emails.verification.body": "براہ کرم اپنے ای میل کی تصدیق کے لیے درج ذیل لنک پر عمل کریں۔", "emails.verification.footer": "اگر آپ نے اس پتے کی تصدیق کے لیے نہیں کہا تو آپ اس پیغام کو نظر انداز کر سکتے ہیں۔", - "emails.verification.thanks": "شکریہ", + "emails.verification.thanks": "شکریہ،", "emails.verification.signature": "ٹیم۔ {{project}}", "emails.magicSession.subject": "اگ ان کریں", - "emails.magicSession.hello": "خوش آمدید", + "emails.magicSession.hello": "خوش آمدید،", "emails.magicSession.body": "لاگ ان کرنے کے لیے اس لنک پر عمل کریں۔", "emails.magicSession.footer": "اگر آپ نے اس ای میل کا استعمال کرتے ہوئے لاگ ان کرنے کے لیے نہیں کہا تو آپ اس پیغام کو نظر انداز کر سکتے ہیں۔", - "emails.magicSession.thanks": "شکریہ", + "emails.magicSession.thanks": "شکریہ،", "emails.magicSession.signature": "ٹیم۔ {{project}}", "emails.recovery.subject": "پاس ورڈ ری سیٹ۔", - "emails.recovery.hello": "ہیلو {{user}}", + "emails.recovery.hello": "ہیلو {{user}}،", "emails.recovery.body": "{{project}} کا پاس ورڈ تبدیل کرنے کے لیے درج ذیل لنک پر عمل کریں", "emails.recovery.footer": "اگر آپ نے اپنا پاس ورڈ دوبارہ ترتیب دینے کے لیے نہیں کہا تو آپ اس پیغام کو نظر انداز کر سکتے ہیں۔", - "emails.recovery.thanks": "شکریہ", + "emails.recovery.thanks": "شکریہ،", "emails.recovery.signature": "ٹیم۔ {{project}}", "emails.invitation.subject": "%s پر %s ٹیم کو دعوت", - "emails.invitation.hello": "خوش آمدید", + "emails.invitation.hello": "خوش آمدید،", "emails.invitation.body": "یہ پیغام آپ کو اس لیے بھیجا گیا تھا کہ {{owner}} نے آپ کو {{project}} میں {{team}} ٹیم کا رکن بننے کی دعوت بھیجی", "emails.invitation.footer": "اگر آپ دلچسپی نہیں رکھتے تو آپ اس پیغام کو نظر انداز کر سکتے ہیں۔", - "emails.invitation.thanks": "شکریہ", + "emails.invitation.thanks": "شکریہ،", "emails.invitation.signature": "ٹیم۔ {{project}", "locale.country.unknown": "نامعلوم", "countries.af": "افغانستان", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "اس ایمیل کے لئے حفاظتی جملہ {{phrase}} ہے۔ اگر یہ جملہ سائن ان کے دوران دکھائے گئے جملے سے میل کھاتا ہے تو آپ اس ایمیل پر بھروسہ کر سکتے ہیں۔", "emails.otpSession.thanks": "شکریہ،", "emails.otpSession.signature": "ٹیم {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/vi.json b/app/config/locale/translations/vi.json index cf04a5b737..a727cabba9 100644 --- a/app/config/locale/translations/vi.json +++ b/app/config/locale/translations/vi.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "Nhóm %s", "emails.verification.subject": "Xác minh tài khoản", - "emails.verification.hello": "Chào {{user}}", + "emails.verification.hello": "Chào {{user}},", "emails.verification.body": "Nhấn vào đường dẫn sau để xác minh địa chỉ email của bạn.", "emails.verification.footer": "Nếu bạn không yêu cầu xác minh tài khoản, bạn có thể bỏ qua email này.", - "emails.verification.thanks": "Cảm ơn", + "emails.verification.thanks": "Cảm ơn,", "emails.verification.signature": "Nhóm {{project}}", "emails.magicSession.subject": "Đăng nhập", - "emails.magicSession.hello": "Chào", + "emails.magicSession.hello": "Chào,", "emails.magicSession.body": "Nhấn vào đường dẫn sau để đăng nhập.", "emails.magicSession.footer": "Nếu bạn không yêu cầu đăng nhập bằng email, bạn có thể bỏ qua email này.", - "emails.magicSession.thanks": "Cảm ơn", + "emails.magicSession.thanks": "Cảm ơn,", "emails.magicSession.signature": "Nhóm {{project}}", "emails.recovery.subject": "Thiết lập lại mật khẩu", - "emails.recovery.hello": "Chào {{user}}", + "emails.recovery.hello": "Chào {{user}},", "emails.recovery.body": "Nhấn vào đường dẫn sau để thiết lập lại mật khẩu {{project}} của bạn.", "emails.recovery.footer": "Nếu bạn không yêu cầu thiết lập lại mật khẩu, bạn có thể bỏ qua email này.", - "emails.recovery.thanks": "Cảm ơn", + "emails.recovery.thanks": "Cảm ơn,", "emails.recovery.signature": "Nhóm {{project}}", "emails.invitation.subject": "Lời mời tham gia nhóm %s tại %s", - "emails.invitation.hello": "Xin chào", + "emails.invitation.hello": "Xin chào,", "emails.invitation.body": "Email này được gửi cho bạn vì {{owner}} muốn mời bạn trở thành một thành viên của nhóm {{team}} tại {{project}}.", "emails.invitation.footer": "Nếu bạn không quan tâm, bạn có thể bỏ qua email này.", - "emails.invitation.thanks": "Cảm ơn", + "emails.invitation.thanks": "Cảm ơn,", "emails.invitation.signature": "Nhóm {{project}}", "locale.country.unknown": "Không xác định", "countries.af": "Afghanistan", @@ -245,4 +245,4 @@ "emails.otpSession.securityPhrase": "Cụm từ bảo mật cho email này là {{phrase}}. Bạn có thể tin tưởng email này nếu cụm từ này khớp với cụm từ hiển thị khi đăng nhập.", "emails.otpSession.thanks": "Cảm ơn,", "emails.otpSession.signature": "nhóm {{project}}" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/zh-cn.json b/app/config/locale/translations/zh-cn.json index 0bfbc54e0e..5e35a89bfe 100644 --- a/app/config/locale/translations/zh-cn.json +++ b/app/config/locale/translations/zh-cn.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s 小组", "emails.verification.subject": "帐户验证", - "emails.verification.hello": "你好 {{user}}", + "emails.verification.hello": "你好 {{user}}、", "emails.verification.body": "点此链接验证您的电子邮件地址。", "emails.verification.footer": "如果您没有要求验证此地址,则可忽略此消息。", - "emails.verification.thanks": "谢谢", + "emails.verification.thanks": "谢谢、", "emails.verification.signature": "{{project}} 团队", "emails.magicSession.subject": "登录", - "emails.magicSession.hello": "你好", + "emails.magicSession.hello": "你好、", "emails.magicSession.body": "点此链接登录。", "emails.magicSession.footer": "如果您没有要求使用此电子邮件登录,则可忽略此消息。", - "emails.magicSession.thanks": "谢谢", + "emails.magicSession.thanks": "谢谢、", "emails.magicSession.signature": "{{project}} 团队", "emails.recovery.subject": "重设密码", - "emails.recovery.hello": "你好 {{user}}", + "emails.recovery.hello": "你好 {{user}}、", "emails.recovery.body": "点此链接重置您的 {{project}} 密码。", "emails.recovery.footer": "如果您没有要求重置密码,则可以忽略此消息。", - "emails.recovery.thanks": "谢谢", + "emails.recovery.thanks": "谢谢、", "emails.recovery.signature": "{{project}} 团队", "emails.invitation.subject": "邀请 %s 团队在 %s", - "emails.invitation.hello": "你好", + "emails.invitation.hello": "你好、", "emails.invitation.body": "这封邮件发送给您是因为 {{owner}} 想邀请您成为 {{team}} 团队在 {{project}}.", "emails.invitation.footer": "如果您不感兴趣,可以忽略此消息。", - "emails.invitation.thanks": "谢谢", + "emails.invitation.thanks": "谢谢、", "emails.invitation.signature": "{{project}} 团队", "locale.country.unknown": "未知", "countries.af": "阿富汗", @@ -239,10 +239,10 @@ "emails.magicSession.securityPhrase": "此电子邮件的安全短语是{{phrase}}。如果此短语与登录时显示的短语相匹配,则您可以信任此电子邮件。", "emails.magicSession.optionUrl": "如果您无法使用上面的按钮登录,请访问以下链接:", "emails.otpSession.subject": "{{project}} 登录", - "emails.otpSession.hello": "你好,\n", + "emails.otpSession.hello": "你好,\n、", "emails.otpSession.description": "在提示时输入以下验证码以安全登录您的{{project}}账户。该验证码将在15分钟后过期。", "emails.otpSession.clientInfo": "此次登录是通过{{agentClient}}在{{agentDevice}} {{agentOs}}上请求的。如果您没有请求登录,可以放心忽略此电子邮件。", "emails.otpSession.securityPhrase": "此电子邮件的安全短语是{{phrase}}。如果此短语与登录时显示的短语一致,您可以信任此邮件。", - "emails.otpSession.thanks": "谢谢,", + "emails.otpSession.thanks": "谢谢,、", "emails.otpSession.signature": "{{project}} 团队" -} \ No newline at end of file +} diff --git a/app/config/locale/translations/zh-tw.json b/app/config/locale/translations/zh-tw.json index 24729de6b3..146dd0a401 100644 --- a/app/config/locale/translations/zh-tw.json +++ b/app/config/locale/translations/zh-tw.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "%s 小組", "emails.verification.subject": "帳戶驗證", - "emails.verification.hello": "嗨 {{user}}", + "emails.verification.hello": "嗨 {{user}}、", "emails.verification.body": "按照此連結驗證您的電子郵件地址。", "emails.verification.footer": "如果您沒有要求驗證此地址,則可以忽略此消息。", - "emails.verification.thanks": "謝謝", + "emails.verification.thanks": "謝謝、", "emails.verification.signature": "{{project}} 團隊", "emails.magicSession.subject": "登入", - "emails.magicSession.hello": "嗨", + "emails.magicSession.hello": "嗨、", "emails.magicSession.body": "點此連結登入。", "emails.magicSession.footer": "如果您沒有要求使用此電子郵件登入,則可以忽略此消息。", - "emails.magicSession.thanks": "謝謝", + "emails.magicSession.thanks": "謝謝、", "emails.magicSession.signature": "{{project}} 團隊", "emails.recovery.subject": "重設密碼", - "emails.recovery.hello": "您好 {{user}}", + "emails.recovery.hello": "您好 {{user}}、", "emails.recovery.body": "按照此連結重置您的 {{project}} 密碼。", "emails.recovery.footer": "如果您沒有要求重置密碼,則可以忽略此消息。", - "emails.recovery.thanks": "謝謝", + "emails.recovery.thanks": "謝謝、", "emails.recovery.signature": "{{project}} 團隊", "emails.invitation.subject": "邀請 %s 團隊在 %s", - "emails.invitation.hello": "您好", + "emails.invitation.hello": "您好、", "emails.invitation.body": "發送這封郵件給您是因為 {{owner}} 想邀請您成為 {{team}} 團隊在 {{project}}。", "emails.invitation.footer": "如果您不感興趣,可以忽略此消息。", - "emails.invitation.thanks": "謝謝", + "emails.invitation.thanks": "謝謝、", "emails.invitation.signature": "{{project}} 團隊", "locale.country.unknown": "未知", "countries.af": "阿富汗", @@ -239,10 +239,10 @@ "sms.verification.body": "{{secret}}", "emails.magicSession.securityPhrase": "這封電子郵件的安全密語是{{phrase}}。如果此密語與登入時顯示的密語相符,您就可以信任此郵件。", "emails.otpSession.subject": "{{project}} 登入", - "emails.otpSession.hello": "你好,", + "emails.otpSession.hello": "你好,、", "emails.otpSession.description": "在提示时輸入以下驗證碼以安全地登入您的{{project}}帳戶。該驗證碼將在15分鐘後過期。", "emails.otpSession.clientInfo": "這次的登入是使用{{agentClient}}在{{agentDevice}} {{agentOs}}上請求的。如果您沒有請求這次的登入,您可以放心地忽略這封電子郵件。", "emails.otpSession.securityPhrase": "這封電子郵件的安全口令是{{phrase}}。如果這個口令與登入時顯示的口令相匹配,您可以信任這封電子郵件。", - "emails.otpSession.thanks": "謝謝,", + "emails.otpSession.thanks": "謝謝,、", "emails.otpSession.signature": "{{project}} 團隊" -} \ No newline at end of file +} From edddaa561f7978006e742dfaa7203c86305aa386 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 29 Oct 2024 12:46:25 +0000 Subject: [PATCH 099/101] fix: remove commas where unusual --- app/config/locale/translations/km.json | 20 ++++++++++---------- app/config/locale/translations/th.json | 20 ++++++++++---------- app/config/locale/translations/vi.json | 20 ++++++++++---------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/app/config/locale/translations/km.json b/app/config/locale/translations/km.json index 301796f69e..12ac05e8da 100644 --- a/app/config/locale/translations/km.json +++ b/app/config/locale/translations/km.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "ក្រុម %s", "emails.verification.subject": "", - "emails.verification.hello": ",", + "emails.verification.hello": "", "emails.verification.body": "", "emails.verification.footer": "", - "emails.verification.thanks": ",", + "emails.verification.thanks": "", "emails.verification.signature": "", "emails.magicSession.subject": "", - "emails.magicSession.hello": ",", + "emails.magicSession.hello": "", "emails.magicSession.body": "", "emails.magicSession.footer": "", - "emails.magicSession.thanks": ",", + "emails.magicSession.thanks": "", "emails.magicSession.signature": "", "emails.recovery.subject": "", - "emails.recovery.hello": ",", + "emails.recovery.hello": "", "emails.recovery.body": "", "emails.recovery.footer": "", - "emails.recovery.thanks": ",", + "emails.recovery.thanks": "", "emails.recovery.signature": "", "emails.invitation.subject": "", - "emails.invitation.hello": ",", + "emails.invitation.hello": "", "emails.invitation.body": "", "emails.invitation.footer": "", - "emails.invitation.thanks": ",", + "emails.invitation.thanks": "", "emails.invitation.signature": "", "locale.country.unknown": "មិនស្គាល់", "countries.af": "អាហ្វហ្គានីស្ថាន", @@ -239,10 +239,10 @@ "emails.magicSession.securityPhrase": "ឃ្លាសម្ងាត់សម្រាប់អ៊ីមែលនេះគឺ {{phrase}}។ អ្នកអាចទុកចិត្តលើអ៊ីមែលនេះប្រសិនបើឃ្លានេះត្រូវគ្នាជាមួយឃ្លាដែលបង្ហាញឡើងពេលចូលប្រើ។", "emails.magicSession.optionUrl": "ប្រសិនបើអ្នកមិនអាចចូលប្រើប្រាស់ដោយប្រើប៊ូតុងខាងលើនេះទេ សូមចូលទៅកាន់តំណភ្ជាប់ខាងក្រោម៖", "emails.otpSession.subject": "ការចូល {{project}}", - "emails.otpSession.hello": "ជំរាបសួរ,", + "emails.otpSession.hello": "ជំរាបសួរ", "emails.otpSession.description": "បញ្ចូលលេខកូដផ្ទៀងផ្ទាត់ខាងក្រោមនេះនៅពេលដែលបានស្នើ ដើម្បីចូលទៅកាន់គណនី {{project}} របស់អ្នកដោយមានសុវត្ថិភាព។ វានឹងផុតកំណត់ក្នុងរយៈពេល 15 នាទី។", "emails.otpSession.clientInfo": "ការចូលប្រព័ន្ធនេះត្រូវបានស្នើរបស់អ្នកប្រើប្រាស់តាមរយៈ {{agentClient}} នៅលើ {{agentDevice}} {{agentOs}}។ ប្រសិនបើអ្នកមិនបានស្នើរការចូលប្រព័ន្ធនេះទេ អ្នកអាចមិនអើពើអ៊ីម៉ែលនេះបាន។", "emails.otpSession.securityPhrase": "ឃ្លាសម្រាប់សុវត្ថិភាពអ៊ីមែលនេះគឺ {{phrase}}។ អ្នកអាចទុកចិត្តនូវអ៊ីមែលនេះប្រសិនបើឃ្លានេះត្រូវគ្នាជាមួយឃ្លាដែលបានបង្ហាញពេលចូលគណនី។", - "emails.otpSession.thanks": "អរគុណ,", + "emails.otpSession.thanks": "អរគុណ", "emails.otpSession.signature": "ក្រុម {{project}}" } diff --git a/app/config/locale/translations/th.json b/app/config/locale/translations/th.json index 2ec02ee8e2..5a53b16055 100644 --- a/app/config/locale/translations/th.json +++ b/app/config/locale/translations/th.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "ทีม %s", "emails.verification.subject": "การยืนยันบัญชีผู้ใช้", - "emails.verification.hello": "เรียนคุณ {{user}},", + "emails.verification.hello": "เรียนคุณ {{user}}", "emails.verification.body": "กดเข้าไปที่ลิงก์นี้เพื่อยืนยันอีเมลของท่าน", "emails.verification.footer": "หากท่านไม่ได้ต้องการที่จะยืนยันอีเมลนี้ ท่านสามารถเพิกเฉยข้อความนี้ได้", - "emails.verification.thanks": "ขอบคุณ,", + "emails.verification.thanks": "ขอบคุณ", "emails.verification.signature": "ทีม {{project}}", "emails.magicSession.subject": "เข้าสู่ระบบ", - "emails.magicSession.hello": "เรียนผู้ใช้งาน,", + "emails.magicSession.hello": "เรียนผู้ใช้งาน", "emails.magicSession.body": "กดเข้าไปที่ลิงก์นี้เพื่อเข้าสู่ระบบ", "emails.magicSession.footer": "หากท่านไม่ได้ต้องการที่จะเข้าสู่ระบบด้วยอีเมลนี้ ท่านสามารถเพิกเฉยข้อความนี้ได้", - "emails.magicSession.thanks": "ขอบคุณ,", + "emails.magicSession.thanks": "ขอบคุณ", "emails.magicSession.signature": "ทีม {{project}}", "emails.recovery.subject": "รีเซ็ตรหัสผ่าน", - "emails.recovery.hello": "เรียนคุณ {{user}},", + "emails.recovery.hello": "เรียนคุณ {{user}}", "emails.recovery.body": "กดเข้าไปที่ลิงก์นี้เพื่อรีเซ็ตรหัสผ่านสำหรับโปรเจกต์ {{project}} ของท่าน", "emails.recovery.footer": "หากท่านไม่ได้ต้องการที่จะรีเซ็ตรหัสผ่านของท่าน ท่านสามารถเพิกเฉยข้อความนี้ได้", - "emails.recovery.thanks": "ขอบคุณ,", + "emails.recovery.thanks": "ขอบคุณ", "emails.recovery.signature": "ทีม {{project}}", "emails.invitation.subject": "เรียนเชิญเข้าร่วม ทีม %s จากโปรเจกต์ %s", - "emails.invitation.hello": "สวัสดี,", + "emails.invitation.hello": "สวัสดี", "emails.invitation.body": "ท่านได้รับอีเมลฉบับนี้เนื่องจาก {{owner}} ต้องการที่จะเชิญชวนคุณเข้าร่วมเป็นส่วนหนึ่งของ ทีม {{team}} จากโปรเจกต์ {{project}}", "emails.invitation.footer": "หากท่านไม่ได้สนใจที่จะเข้าร่วม ท่านสามารถเพิกเฉยข้อความนี้ได้", - "emails.invitation.thanks": "ขอบคุณ,", + "emails.invitation.thanks": "ขอบคุณ", "emails.invitation.signature": "ทีม {{project}}", "locale.country.unknown": "ไม่ทราบ", "countries.af": "อัฟกานิสถาน", @@ -239,10 +239,10 @@ "emails.magicSession.securityPhrase": "วลีรักษาความปลอดภัยสำหรับอีเมลนี้คือ {{phrase}} คุณสามารถเชื่อถืออีเมลนี้ได้หากวลีนี้ตรงกับวลีที่แสดงในระหว่างการเข้าสู่ระบบ", "emails.magicSession.optionUrl": "หากคุณไม่สามารถเข้าสู่ระบบโดยใช้ปุ่มด้านบน โปรดเยี่ยมชมลิงก์ต่อไปนี้:", "emails.otpSession.subject": "การเข้าสู่ระบบ {{project}}", - "emails.otpSession.hello": "สวัสดี,", + "emails.otpSession.hello": "สวัสดี", "emails.otpSession.description": "ป้อนรหัสยืนยันต่อไปนี้เมื่อได้รับการสั่งให้ทำเพื่อลงชื่อเข้าใช้บัญชี {{project}} ของคุณอย่างปลอดภัย รหัสนี้จะหมดอายุใน 15 นาที.", "emails.otpSession.clientInfo": "การลงชื่อเข้าใช้งานนี้ได้รับการทำผ่าน {{agentClient}} บน {{agentDevice}} {{agentOs}} หากคุณไม่ได้ทำการขอลงชื่อเข้าใช้นี้ คุณสามารถเพิกเฉยต่ออีเมลนี้ได้เลย", "emails.otpSession.securityPhrase": "วลีความปลอดภัยสำหรับอีเมลนี้คือ {{phrase}} คุณสามารถเชื่อถืออีเมลนี้ได้หากวลีนี้ตรงกับวลีที่แสดงขณะลงชื่อเข้าใช้งาน.", - "emails.otpSession.thanks": "ขอบคุณครับ/ค่ะ,", + "emails.otpSession.thanks": "ขอบคุณครับ/ค่ะ", "emails.otpSession.signature": "ทีม {{project}}" } diff --git a/app/config/locale/translations/vi.json b/app/config/locale/translations/vi.json index a727cabba9..76a545a1d4 100644 --- a/app/config/locale/translations/vi.json +++ b/app/config/locale/translations/vi.json @@ -4,28 +4,28 @@ "settings.direction": "ltr", "emails.sender": "Nhóm %s", "emails.verification.subject": "Xác minh tài khoản", - "emails.verification.hello": "Chào {{user}},", + "emails.verification.hello": "Chào {{user}}", "emails.verification.body": "Nhấn vào đường dẫn sau để xác minh địa chỉ email của bạn.", "emails.verification.footer": "Nếu bạn không yêu cầu xác minh tài khoản, bạn có thể bỏ qua email này.", - "emails.verification.thanks": "Cảm ơn,", + "emails.verification.thanks": "Cảm ơn", "emails.verification.signature": "Nhóm {{project}}", "emails.magicSession.subject": "Đăng nhập", - "emails.magicSession.hello": "Chào,", + "emails.magicSession.hello": "Chào", "emails.magicSession.body": "Nhấn vào đường dẫn sau để đăng nhập.", "emails.magicSession.footer": "Nếu bạn không yêu cầu đăng nhập bằng email, bạn có thể bỏ qua email này.", - "emails.magicSession.thanks": "Cảm ơn,", + "emails.magicSession.thanks": "Cảm ơn", "emails.magicSession.signature": "Nhóm {{project}}", "emails.recovery.subject": "Thiết lập lại mật khẩu", - "emails.recovery.hello": "Chào {{user}},", + "emails.recovery.hello": "Chào {{user}}", "emails.recovery.body": "Nhấn vào đường dẫn sau để thiết lập lại mật khẩu {{project}} của bạn.", "emails.recovery.footer": "Nếu bạn không yêu cầu thiết lập lại mật khẩu, bạn có thể bỏ qua email này.", - "emails.recovery.thanks": "Cảm ơn,", + "emails.recovery.thanks": "Cảm ơn", "emails.recovery.signature": "Nhóm {{project}}", "emails.invitation.subject": "Lời mời tham gia nhóm %s tại %s", - "emails.invitation.hello": "Xin chào,", + "emails.invitation.hello": "Xin chào", "emails.invitation.body": "Email này được gửi cho bạn vì {{owner}} muốn mời bạn trở thành một thành viên của nhóm {{team}} tại {{project}}.", "emails.invitation.footer": "Nếu bạn không quan tâm, bạn có thể bỏ qua email này.", - "emails.invitation.thanks": "Cảm ơn,", + "emails.invitation.thanks": "Cảm ơn", "emails.invitation.signature": "Nhóm {{project}}", "locale.country.unknown": "Không xác định", "countries.af": "Afghanistan", @@ -239,10 +239,10 @@ "emails.magicSession.securityPhrase": "Cụm từ bảo mật cho email này là {{phrase}}. Bạn có thể tin tưởng email này nếu cụm từ này khớp với cụm từ hiển thị khi đăng nhập.", "emails.magicSession.optionUrl": "Nếu bạn không thể đăng nhập bằng cách sử dụng nút ở trên, vui lòng truy cập liên kết sau:", "emails.otpSession.subject": "Đăng nhập {{project}}", - "emails.otpSession.hello": "Xin chào,", + "emails.otpSession.hello": "Xin chào", "emails.otpSession.description": "Nhập mã xác minh sau đây khi được yêu cầu để đăng nhập an toàn vào tài khoản {{project}} của bạn. Mã này sẽ hết hạn trong 15 phút.", "emails.otpSession.clientInfo": "Đăng nhập này được yêu cầu sử dụng {{agentClient}} trên {{agentDevice}} {{agentOs}}. Nếu bạn không yêu cầu đăng nhập, bạn có thể bỏ qua email này một cách an toàn.", "emails.otpSession.securityPhrase": "Cụm từ bảo mật cho email này là {{phrase}}. Bạn có thể tin tưởng email này nếu cụm từ này khớp với cụm từ hiển thị khi đăng nhập.", - "emails.otpSession.thanks": "Cảm ơn,", + "emails.otpSession.thanks": "Cảm ơn", "emails.otpSession.signature": "nhóm {{project}}" } From 3d463c5c81cd32e5306a17f1370e86ab7f9cdba4 Mon Sep 17 00:00:00 2001 From: Fabian Gruber Date: Fri, 25 Oct 2024 15:50:56 +0200 Subject: [PATCH 100/101] fix: revert function execution order --- src/Appwrite/Platform/Workers/Functions.php | 90 ++++++++------------- 1 file changed, 34 insertions(+), 56 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Functions.php b/src/Appwrite/Platform/Workers/Functions.php index 3dc3e65eee..1da8c88b92 100644 --- a/src/Appwrite/Platform/Workers/Functions.php +++ b/src/Appwrite/Platform/Workers/Functions.php @@ -52,21 +52,6 @@ class Functions extends Action ->callback(fn (Document $project, Message $message, Database $dbForProject, Func $queueForFunctions, Event $queueForEvents, Usage $queueForUsage, Log $log, callable $isResourceBlocked) => $this->action($project, $message, $dbForProject, $queueForFunctions, $queueForEvents, $queueForUsage, $log, $isResourceBlocked)); } - /** - * @param Document $project - * @param Message $message - * @param Database $dbForProject - * @param Func $queueForFunctions - * @param Event $queueForEvents - * @param Usage $queueForUsage - * @param Log $log - * @param callable $isResourceBlocked - * @return void - * @throws Authorization - * @throws Structure - * @throws \Utopia\Database\Exception - * @throws Conflict - */ public function action(Document $project, Message $message, Database $dbForProject, Func $queueForFunctions, Event $queueForEvents, Usage $queueForUsage, Log $log, callable $isResourceBlocked): void { $payload = $message->getPayload() ?? []; @@ -84,9 +69,41 @@ class Functions extends Action return; } - $eventData = $payload['payload'] ?? ''; - $user = new Document($payload['user'] ?? []); $events = $payload['events'] ?? []; + $data = $payload['body'] ?? ''; + $eventData = $payload['payload'] ?? ''; + $function = new Document($payload['function'] ?? []); + $functionId = $payload['functionId'] ?? ''; + $user = new Document($payload['user'] ?? []); + $userId = $payload['userId'] ?? ''; + $method = $payload['method'] ?? 'POST'; + $headers = $payload['headers'] ?? []; + $path = $payload['path'] ?? '/'; + $jwt = $payload['jwt'] ?? ''; + + if ($user->isEmpty() && !empty($userId)) { + $user = $dbForProject->getDocument('users', $userId); + } + + if (empty($jwt) && !$user->isEmpty()) { + $jwtExpiry = $function->getAttribute('timeout', 900); + $jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0); + $jwt = $jwtObj->encode([ + 'userId' => $user->getId(), + ]); + } + + if ($project->getId() === 'console') { + return; + } + + if ($function->isEmpty() && !empty($functionId)) { + $function = $dbForProject->getDocument('functions', $functionId); + } + + $log->addTag('functionId', $function->getId()); + $log->addTag('projectId', $project->getId()); + $log->addTag('type', $type); if (!empty($events)) { $limit = 30; @@ -144,50 +161,11 @@ class Functions extends Action return; } - $data = $payload['body'] ?? ''; - $function = new Document($payload['function'] ?? []); - $functionId = $payload['functionId'] ?? ''; - $userId = $payload['userId'] ?? ''; - $method = $payload['method'] ?? 'POST'; - $headers = $payload['headers'] ?? []; - $path = $payload['path'] ?? '/'; - $jwt = $payload['jwt'] ?? ''; - - if ($user->isEmpty() && !empty($userId)) { - $user = $dbForProject->getDocument('users', $userId); - } - - if (empty($jwt) && !$user->isEmpty()) { - $jwtExpiry = $function->getAttribute('timeout', 900); - $jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0); - $jwt = $jwtObj->encode([ - 'userId' => $user->getId(), - ]); - } - - if ($project->getId() === 'console') { - return; - } - - if ($function->isEmpty() && !empty($functionId)) { - $function = $dbForProject->getDocument('functions', $functionId); - } - - // $function still empty, we can't execute this - if ($function->isEmpty()) { - Console::warning('Got empty function without functionId.'); - return; - } - if ($isResourceBlocked($project, 'functions', $function->getId())) { Console::log('Function ' . $function->getId() . ' is blocked, skipping execution.'); return; } - $log->addTag('functionId', $function->getId()); - $log->addTag('projectId', $project->getId()); - $log->addTag('type', $type); - /** * Handle Schedule and HTTP execution. */ From fed9cdb40876b04a8bcd8f0507f4144651488996 Mon Sep 17 00:00:00 2001 From: Fabian Gruber Date: Tue, 29 Oct 2024 16:07:12 +0100 Subject: [PATCH 101/101] use resource type constants --- app/controllers/api/databases.php | 96 ++++++++++----------- app/controllers/api/functions.php | 58 ++++++------- app/controllers/api/messaging.php | 92 ++++++++++---------- app/controllers/api/storage.php | 32 +++---- app/controllers/general.php | 2 +- app/init.php | 11 +++ src/Appwrite/Platform/Workers/Functions.php | 4 +- 7 files changed, 153 insertions(+), 142 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 942f886417..70374b5331 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -439,7 +439,7 @@ App::post('/v1/databases') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].create') ->label('scope', 'databases.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'database.create') ->label('audits.resource', 'database/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -517,7 +517,7 @@ App::get('/v1/databases') ->desc('List databases') ->groups(['api', 'database']) ->label('scope', 'databases.read') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'list') @@ -578,7 +578,7 @@ App::get('/v1/databases/:databaseId') ->desc('Get database') ->groups(['api', 'database']) ->label('scope', 'databases.read') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'get') @@ -604,7 +604,7 @@ App::get('/v1/databases/:databaseId/logs') ->desc('List database logs') ->groups(['api', 'database']) ->label('scope', 'databases.read') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'listLogs') @@ -696,7 +696,7 @@ App::put('/v1/databases/:databaseId') ->desc('Update database') ->groups(['api', 'database', 'schema']) ->label('scope', 'databases.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('event', 'databases.[databaseId].update') ->label('audits.event', 'database.update') ->label('audits.resource', 'database/{response.$id}') @@ -735,7 +735,7 @@ App::delete('/v1/databases/:databaseId') ->desc('Delete database') ->groups(['api', 'database', 'schema']) ->label('scope', 'databases.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('event', 'databases.[databaseId].delete') ->label('audits.event', 'database.delete') ->label('audits.resource', 'database/{request.databaseId}') @@ -785,7 +785,7 @@ App::post('/v1/databases/:databaseId/collections') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].create') ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'collection.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -853,7 +853,7 @@ App::get('/v1/databases/:databaseId/collections') ->desc('List collections') ->groups(['api', 'database']) ->label('scope', 'collections.read') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'listCollections') @@ -923,7 +923,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId') ->desc('Get collection') ->groups(['api', 'database']) ->label('scope', 'collections.read') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'getCollection') @@ -958,7 +958,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs') ->desc('List collection logs') ->groups(['api', 'database']) ->label('scope', 'collections.read') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'listCollectionLogs') @@ -1059,7 +1059,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId') ->desc('Update collection') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('event', 'databases.[databaseId].collections.[collectionId].update') ->label('audits.event', 'collection.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -1123,7 +1123,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId') ->desc('Delete collection') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('event', 'databases.[databaseId].collections.[collectionId].delete') ->label('audits.event', 'collection.delete') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -1180,7 +1180,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -1238,7 +1238,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email' ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1281,7 +1281,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1329,7 +1329,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1372,7 +1372,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1415,7 +1415,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1487,7 +1487,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float' ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1562,7 +1562,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolea ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1604,7 +1604,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1649,7 +1649,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1777,7 +1777,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') ->desc('List attributes') ->groups(['api', 'database']) ->label('scope', 'collections.read') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'listAttributes') @@ -1861,7 +1861,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key') ->desc('Get attribute') ->groups(['api', 'database']) ->label('scope', 'collections.read') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'getAttribute') @@ -1936,7 +1936,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/strin ->desc('Update string attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -1980,7 +1980,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/email ->desc('Update email attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -2022,7 +2022,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/enum/ ->desc('Update enum attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -2066,7 +2066,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/ip/:k ->desc('Update IP address attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -2108,7 +2108,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/url/: ->desc('Update URL attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -2150,7 +2150,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/integ ->desc('Update integer attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -2202,7 +2202,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/float ->desc('Update float attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -2254,7 +2254,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/boole ->desc('Update boolean attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -2295,7 +2295,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datet ->desc('Update dateTime attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -2336,7 +2336,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/ ->desc('Update relationship attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -2394,7 +2394,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key ->desc('Delete attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update') ->label('audits.event', 'attribute.delete') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -2509,7 +2509,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].indexes.[indexId].create') ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'index.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -2679,7 +2679,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes') ->desc('List indexes') ->groups(['api', 'database']) ->label('scope', 'collections.read') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'listIndexes') @@ -2755,7 +2755,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') ->desc('Get index') ->groups(['api', 'database']) ->label('scope', 'collections.read') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'getIndex') @@ -2795,7 +2795,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') ->desc('Delete index') ->groups(['api', 'database']) ->label('scope', 'collections.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('event', 'databases.[databaseId].collections.[collectionId].indexes.[indexId].update') ->label('audits.event', 'index.delete') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') @@ -2861,7 +2861,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].create') ->label('scope', 'documents.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'document.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') @@ -3113,7 +3113,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') ->desc('List documents') ->groups(['api', 'database']) ->label('scope', 'documents.read') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'listDocuments') @@ -3275,7 +3275,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->desc('Get document') ->groups(['api', 'database']) ->label('scope', 'documents.read') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'getDocument') @@ -3368,7 +3368,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->desc('List document logs') ->groups(['api', 'database']) ->label('scope', 'documents.read') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'listDocumentLogs') @@ -3474,7 +3474,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].update') ->label('scope', 'documents.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'document.update') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}/document/{response.$id}') ->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}') @@ -3710,7 +3710,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu ->desc('Delete document') ->groups(['api', 'database']) ->label('scope', 'documents.write') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].delete') ->label('audits.event', 'document.delete') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}/document/{request.documentId}') @@ -3825,7 +3825,7 @@ App::get('/v1/databases/usage') ->desc('Get databases usage stats') ->groups(['api', 'database', 'usage']) ->label('scope', 'collections.read') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'getUsage') @@ -3907,7 +3907,7 @@ App::get('/v1/databases/:databaseId/usage') ->desc('Get database usage stats') ->groups(['api', 'database', 'usage']) ->label('scope', 'collections.read') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'getDatabaseUsage') @@ -3995,7 +3995,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/usage') ->desc('Get collection usage stats') ->groups(['api', 'database', 'usage']) ->label('scope', 'collections.read') - ->label('resourceType', 'databases') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'getCollectionUsage') diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 7de443625e..290eed8651 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -138,7 +138,7 @@ App::post('/v1/functions') ->desc('Create function') ->label('scope', 'functions.write') ->label('event', 'functions.[functionId].create') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('audits.event', 'function.create') ->label('audits.resource', 'function/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -401,7 +401,7 @@ App::get('/v1/functions') ->groups(['api', 'functions']) ->desc('List functions') ->label('scope', 'functions.read') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'list') @@ -462,7 +462,7 @@ App::get('/v1/functions/runtimes') ->groups(['api', 'functions']) ->desc('List runtimes') ->label('scope', 'functions.read') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'listRuntimes') @@ -496,7 +496,7 @@ App::get('/v1/functions/specifications') ->groups(['api', 'functions']) ->desc('List available function runtime specifications') ->label('scope', 'functions.read') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'listSpecifications') @@ -533,7 +533,7 @@ App::get('/v1/functions/:functionId') ->groups(['api', 'functions']) ->desc('Get function') ->label('scope', 'functions.read') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'get') @@ -558,7 +558,7 @@ App::get('/v1/functions/:functionId/usage') ->desc('Get function usage') ->groups(['api', 'functions', 'usage']) ->label('scope', 'functions.read') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'getFunctionUsage') @@ -663,7 +663,7 @@ App::get('/v1/functions/usage') ->desc('Get functions usage') ->groups(['api', 'functions']) ->label('scope', 'functions.read') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'getUsage') @@ -763,7 +763,7 @@ App::put('/v1/functions/:functionId') ->groups(['api', 'functions']) ->desc('Update function') ->label('scope', 'functions.write') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('event', 'functions.[functionId].update') ->label('audits.event', 'function.update') ->label('audits.resource', 'function/{response.$id}') @@ -966,7 +966,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download') ->groups(['api', 'functions']) ->desc('Download deployment') ->label('scope', 'functions.read') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'getDeploymentDownload') @@ -1052,7 +1052,7 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId') ->groups(['api', 'functions']) ->desc('Update deployment') ->label('scope', 'functions.write') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('event', 'functions.[functionId].deployments.[deploymentId].update') ->label('audits.event', 'deployment.update') ->label('audits.resource', 'function/{request.functionId}') @@ -1115,7 +1115,7 @@ App::delete('/v1/functions/:functionId') ->groups(['api', 'functions']) ->desc('Delete function') ->label('scope', 'functions.write') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('event', 'functions.[functionId].delete') ->label('audits.event', 'function.delete') ->label('audits.resource', 'function/{request.functionId}') @@ -1163,7 +1163,7 @@ App::post('/v1/functions/:functionId/deployments') ->groups(['api', 'functions']) ->desc('Create deployment') ->label('scope', 'functions.write') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('event', 'functions.[functionId].deployments.[deploymentId].create') ->label('audits.event', 'deployment.create') ->label('audits.resource', 'function/{request.functionId}') @@ -1383,7 +1383,7 @@ App::get('/v1/functions/:functionId/deployments') ->groups(['api', 'functions']) ->desc('List deployments') ->label('scope', 'functions.read') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'listDeployments') @@ -1467,7 +1467,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId') ->groups(['api', 'functions']) ->desc('Get deployment') ->label('scope', 'functions.read') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'getDeployment') @@ -1511,7 +1511,7 @@ App::delete('/v1/functions/:functionId/deployments/:deploymentId') ->groups(['api', 'functions']) ->desc('Delete deployment') ->label('scope', 'functions.write') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('event', 'functions.[functionId].deployments.[deploymentId].delete') ->label('audits.event', 'deployment.delete') ->label('audits.resource', 'function/{request.functionId}') @@ -1577,7 +1577,7 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/build') ->groups(['api', 'functions']) ->desc('Rebuild deployment') ->label('scope', 'functions.write') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('event', 'functions.[functionId].deployments.[deploymentId].update') ->label('audits.event', 'deployment.update') ->label('audits.resource', 'function/{request.functionId}') @@ -1646,7 +1646,7 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId/build') ->groups(['api', 'functions']) ->desc('Cancel deployment') ->label('scope', 'functions.write') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('audits.event', 'deployment.update') ->label('audits.resource', 'function/{request.functionId}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -1736,9 +1736,9 @@ App::post('/v1/functions/:functionId/executions') ->groups(['api', 'functions']) ->desc('Create execution') ->label('scope', 'execution.write') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('event', 'functions.[functionId].executions.[executionId].create') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'createExecution') @@ -2140,7 +2140,7 @@ App::get('/v1/functions/:functionId/executions') ->groups(['api', 'functions']) ->desc('List executions') ->label('scope', 'execution.read') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'listExecutions') @@ -2228,7 +2228,7 @@ App::get('/v1/functions/:functionId/executions/:executionId') ->groups(['api', 'functions']) ->desc('Get execution') ->label('scope', 'execution.read') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'getExecution') @@ -2276,7 +2276,7 @@ App::delete('/v1/functions/:functionId/executions/:executionId') ->groups(['api', 'functions']) ->desc('Delete execution') ->label('scope', 'execution.write') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('event', 'functions.[functionId].executions.[executionId].delete') ->label('audits.event', 'executions.delete') ->label('audits.resource', 'function/{request.functionId}') @@ -2347,7 +2347,7 @@ App::post('/v1/functions/:functionId/variables') ->desc('Create variable') ->groups(['api', 'functions']) ->label('scope', 'functions.write') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('audits.event', 'variable.create') ->label('audits.resource', 'function/{request.functionId}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -2412,7 +2412,7 @@ App::get('/v1/functions/:functionId/variables') ->desc('List variables') ->groups(['api', 'functions']) ->label('scope', 'functions.read') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'listVariables') @@ -2440,7 +2440,7 @@ App::get('/v1/functions/:functionId/variables/:variableId') ->desc('Get variable') ->groups(['api', 'functions']) ->label('scope', 'functions.read') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'getVariable') @@ -2480,7 +2480,7 @@ App::put('/v1/functions/:functionId/variables/:variableId') ->desc('Update variable') ->groups(['api', 'functions']) ->label('scope', 'functions.write') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('audits.event', 'variable.update') ->label('audits.resource', 'function/{request.functionId}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -2542,7 +2542,7 @@ App::delete('/v1/functions/:functionId/variables/:variableId') ->desc('Delete variable') ->groups(['api', 'functions']) ->label('scope', 'functions.write') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('audits.event', 'variable.delete') ->label('audits.resource', 'function/{request.functionId}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -2591,7 +2591,7 @@ App::get('/v1/functions/templates') ->groups(['api']) ->desc('List function templates') ->label('scope', 'public') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'listTemplates') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) @@ -2629,7 +2629,7 @@ App::get('/v1/functions/templates') App::get('/v1/functions/templates/:templateId') ->desc('Get function template') ->label('scope', 'public') - ->label('resourceType', 'functions') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'getTemplate') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 50fd30420e..e4a627d027 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -56,7 +56,7 @@ App::post('/v1/messaging/providers/mailgun') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].create') ->label('scope', 'providers.write') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createMailgunProvider') @@ -144,7 +144,7 @@ App::post('/v1/messaging/providers/sendgrid') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].create') ->label('scope', 'providers.write') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createSendgridProvider') @@ -220,7 +220,7 @@ App::post('/v1/messaging/providers/smtp') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].create') ->label('scope', 'providers.write') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createSmtpProvider') @@ -308,7 +308,7 @@ App::post('/v1/messaging/providers/msg91') ->label('audits.event', 'provider.create') ->label('audits.resource', 'provider/{response.$id}') ->label('scope', 'providers.write') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('event', 'providers.[providerId].create') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') @@ -386,7 +386,7 @@ App::post('/v1/messaging/providers/telesign') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].create') ->label('scope', 'providers.write') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createTelesignProvider') @@ -464,7 +464,7 @@ App::post('/v1/messaging/providers/textmagic') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].create') ->label('scope', 'providers.write') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createTextmagicProvider') @@ -542,7 +542,7 @@ App::post('/v1/messaging/providers/twilio') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].create') ->label('scope', 'providers.write') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createTwilioProvider') @@ -620,7 +620,7 @@ App::post('/v1/messaging/providers/vonage') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].create') ->label('scope', 'providers.write') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createVonageProvider') @@ -698,7 +698,7 @@ App::post('/v1/messaging/providers/fcm') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].create') ->label('scope', 'providers.write') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createFcmProvider') @@ -762,7 +762,7 @@ App::post('/v1/messaging/providers/apns') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].create') ->label('scope', 'providers.write') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createApnsProvider') @@ -846,7 +846,7 @@ App::get('/v1/messaging/providers') ->desc('List providers') ->groups(['api', 'messaging']) ->label('scope', 'providers.read') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listProviders') @@ -903,7 +903,7 @@ App::get('/v1/messaging/providers/:providerId/logs') ->desc('List provider logs') ->groups(['api', 'messaging']) ->label('scope', 'providers.read') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listProviderLogs') @@ -992,7 +992,7 @@ App::get('/v1/messaging/providers/:providerId') ->desc('Get provider') ->groups(['api', 'messaging']) ->label('scope', 'providers.read') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'getProvider') @@ -1020,7 +1020,7 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].update') ->label('scope', 'providers.write') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateMailgunProvider') @@ -1127,7 +1127,7 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].update') ->label('scope', 'providers.write') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateSendgridProvider') @@ -1219,7 +1219,7 @@ App::patch('/v1/messaging/providers/smtp/:providerId') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].update') ->label('scope', 'providers.write') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateSmtpProvider') @@ -1342,7 +1342,7 @@ App::patch('/v1/messaging/providers/msg91/:providerId') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].update') ->label('scope', 'providers.write') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateMsg91Provider') @@ -1423,7 +1423,7 @@ App::patch('/v1/messaging/providers/telesign/:providerId') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].update') ->label('scope', 'providers.write') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateTelesignProvider') @@ -1506,7 +1506,7 @@ App::patch('/v1/messaging/providers/textmagic/:providerId') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].update') ->label('scope', 'providers.write') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateTextmagicProvider') @@ -1589,7 +1589,7 @@ App::patch('/v1/messaging/providers/twilio/:providerId') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].update') ->label('scope', 'providers.write') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateTwilioProvider') @@ -1672,7 +1672,7 @@ App::patch('/v1/messaging/providers/vonage/:providerId') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].update') ->label('scope', 'providers.write') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateVonageProvider') @@ -1755,7 +1755,7 @@ App::patch('/v1/messaging/providers/fcm/:providerId') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].update') ->label('scope', 'providers.write') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateFcmProvider') @@ -1825,7 +1825,7 @@ App::patch('/v1/messaging/providers/apns/:providerId') ->label('audits.resource', 'provider/{response.$id}') ->label('event', 'providers.[providerId].update') ->label('scope', 'providers.write') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateApnsProvider') @@ -1921,7 +1921,7 @@ App::delete('/v1/messaging/providers/:providerId') ->label('audits.resource', 'provider/{request.$providerId}') ->label('event', 'providers.[providerId].delete') ->label('scope', 'providers.write') - ->label('resourceType', 'providers') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'deleteProvider') @@ -1957,7 +1957,7 @@ App::post('/v1/messaging/topics') ->label('audits.resource', 'topic/{response.$id}') ->label('event', 'topics.[topicId].create') ->label('scope', 'topics.write') - ->label('resourceType', 'topics') + ->label('resourceType', RESOURCE_TYPE_TOPICS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createTopic') @@ -1998,7 +1998,7 @@ App::get('/v1/messaging/topics') ->desc('List topics') ->groups(['api', 'messaging']) ->label('scope', 'topics.read') - ->label('resourceType', 'topics') + ->label('resourceType', RESOURCE_TYPE_TOPICS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listTopics') @@ -2055,7 +2055,7 @@ App::get('/v1/messaging/topics/:topicId/logs') ->desc('List topic logs') ->groups(['api', 'messaging']) ->label('scope', 'topics.read') - ->label('resourceType', 'topics') + ->label('resourceType', RESOURCE_TYPE_TOPICS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listTopicLogs') @@ -2145,7 +2145,7 @@ App::get('/v1/messaging/topics/:topicId') ->desc('Get topic') ->groups(['api', 'messaging']) ->label('scope', 'topics.read') - ->label('resourceType', 'topics') + ->label('resourceType', RESOURCE_TYPE_TOPICS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'getTopic') @@ -2174,7 +2174,7 @@ App::patch('/v1/messaging/topics/:topicId') ->label('audits.resource', 'topic/{response.$id}') ->label('event', 'topics.[topicId].update') ->label('scope', 'topics.write') - ->label('resourceType', 'topics') + ->label('resourceType', RESOURCE_TYPE_TOPICS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateTopic') @@ -2219,7 +2219,7 @@ App::delete('/v1/messaging/topics/:topicId') ->label('audits.resource', 'topic/{request.$topicId}') ->label('event', 'topics.[topicId].delete') ->label('scope', 'topics.write') - ->label('resourceType', 'topics') + ->label('resourceType', RESOURCE_TYPE_TOPICS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'deleteTopic') @@ -2260,7 +2260,7 @@ App::post('/v1/messaging/topics/:topicId/subscribers') ->label('audits.resource', 'subscriber/{response.$id}') ->label('event', 'topics.[topicId].subscribers.[subscriberId].create') ->label('scope', 'subscribers.write') - ->label('resourceType', 'subscribers') + ->label('resourceType', RESOURCE_TYPE_SUBSCRIBERS) ->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createSubscriber') @@ -2354,7 +2354,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers') ->desc('List subscribers') ->groups(['api', 'messaging']) ->label('scope', 'subscribers.read') - ->label('resourceType', 'subscribers') + ->label('resourceType', RESOURCE_TYPE_SUBSCRIBERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listSubscribers') @@ -2434,7 +2434,7 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs') ->desc('List subscriber logs') ->groups(['api', 'messaging']) ->label('scope', 'subscribers.read') - ->label('resourceType', 'subscribers') + ->label('resourceType', RESOURCE_TYPE_SUBSCRIBERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listSubscriberLogs') @@ -2524,7 +2524,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId') ->desc('Get subscriber') ->groups(['api', 'messaging']) ->label('scope', 'subscribers.read') - ->label('resourceType', 'subscribers') + ->label('resourceType', RESOURCE_TYPE_SUBSCRIBERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'getSubscriber') @@ -2567,7 +2567,7 @@ App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId') ->label('audits.resource', 'subscriber/{request.$subscriberId}') ->label('event', 'topics.[topicId].subscribers.[subscriberId].delete') ->label('scope', 'subscribers.write') - ->label('resourceType', 'subscribers') + ->label('resourceType', RESOURCE_TYPE_SUBSCRIBERS) ->label('sdk.auth', [APP_AUTH_TYPE_JWT, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'deleteSubscriber') @@ -2627,7 +2627,7 @@ App::post('/v1/messaging/messages/email') ->label('audits.resource', 'message/{response.$id}') ->label('event', 'messages.[messageId].create') ->label('scope', 'messages.write') - ->label('resourceType', 'messages') + ->label('resourceType', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createEmail') @@ -2780,7 +2780,7 @@ App::post('/v1/messaging/messages/sms') ->label('audits.resource', 'message/{response.$id}') ->label('event', 'messages.[messageId].create') ->label('scope', 'messages.write') - ->label('resourceType', 'messages') + ->label('resourceType', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createSms') @@ -2897,7 +2897,7 @@ App::post('/v1/messaging/messages/push') ->label('audits.resource', 'message/{response.$id}') ->label('event', 'messages.[messageId].create') ->label('scope', 'messages.write') - ->label('resourceType', 'messages') + ->label('resourceType', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createPush') @@ -3071,7 +3071,7 @@ App::get('/v1/messaging/messages') ->desc('List messages') ->groups(['api', 'messaging']) ->label('scope', 'messages.read') - ->label('resourceType', 'messages') + ->label('resourceType', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listMessages') @@ -3128,7 +3128,7 @@ App::get('/v1/messaging/messages/:messageId/logs') ->desc('List message logs') ->groups(['api', 'messaging']) ->label('scope', 'messages.read') - ->label('resourceType', 'messages') + ->label('resourceType', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listMessageLogs') @@ -3218,7 +3218,7 @@ App::get('/v1/messaging/messages/:messageId/targets') ->desc('List message targets') ->groups(['api', 'messaging']) ->label('scope', 'messages.read') - ->label('resourceType', 'messages') + ->label('resourceType', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listTargets') @@ -3289,7 +3289,7 @@ App::get('/v1/messaging/messages/:messageId') ->desc('Get message') ->groups(['api', 'messaging']) ->label('scope', 'messages.read') - ->label('resourceType', 'messages') + ->label('resourceType', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'getMessage') @@ -3317,7 +3317,7 @@ App::patch('/v1/messaging/messages/email/:messageId') ->label('audits.resource', 'message/{response.$id}') ->label('event', 'messages.[messageId].update') ->label('scope', 'messages.write') - ->label('resourceType', 'messages') + ->label('resourceType', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateEmail') @@ -3518,7 +3518,7 @@ App::patch('/v1/messaging/messages/sms/:messageId') ->label('audits.resource', 'message/{response.$id}') ->label('event', 'messages.[messageId].update') ->label('scope', 'messages.write') - ->label('resourceType', 'messages') + ->label('resourceType', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateSms') @@ -3674,7 +3674,7 @@ App::patch('/v1/messaging/messages/push/:messageId') ->label('audits.resource', 'message/{response.$id}') ->label('event', 'messages.[messageId].update') ->label('scope', 'messages.write') - ->label('resourceType', 'messages') + ->label('resourceType', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updatePush') @@ -3913,7 +3913,7 @@ App::delete('/v1/messaging/messages/:messageId') ->label('audits.resource', 'message/{request.messageId}') ->label('event', 'messages.[messageId].delete') ->label('scope', 'messages.write') - ->label('resourceType', 'messages') + ->label('resourceType', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'delete') diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index afd3c4687a..23dd21c173 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -49,7 +49,7 @@ App::post('/v1/storage/buckets') ->desc('Create bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.write') - ->label('resourceType', 'buckets') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('event', 'buckets.[bucketId].create') ->label('audits.event', 'bucket.create') ->label('audits.resource', 'bucket/{response.$id}') @@ -148,7 +148,7 @@ App::get('/v1/storage/buckets') ->desc('List buckets') ->groups(['api', 'storage']) ->label('scope', 'buckets.read') - ->label('resourceType', 'buckets') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'listBuckets') @@ -209,7 +209,7 @@ App::get('/v1/storage/buckets/:bucketId') ->desc('Get bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.read') - ->label('resourceType', 'buckets') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'getBucket') @@ -235,7 +235,7 @@ App::put('/v1/storage/buckets/:bucketId') ->desc('Update bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.write') - ->label('resourceType', 'buckets') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('event', 'buckets.[bucketId].update') ->label('audits.event', 'bucket.update') ->label('audits.resource', 'bucket/{response.$id}') @@ -303,7 +303,7 @@ App::delete('/v1/storage/buckets/:bucketId') ->desc('Delete bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.write') - ->label('resourceType', 'buckets') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('audits.event', 'bucket.delete') ->label('event', 'buckets.[bucketId].delete') ->label('audits.resource', 'bucket/{request.bucketId}') @@ -346,7 +346,7 @@ App::post('/v1/storage/buckets/:bucketId/files') ->desc('Create file') ->groups(['api', 'storage']) ->label('scope', 'files.write') - ->label('resourceType', 'buckets') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('audits.event', 'file.create') ->label('event', 'buckets.[bucketId].files.[fileId].create') ->label('audits.resource', 'file/{response.$id}') @@ -708,7 +708,7 @@ App::get('/v1/storage/buckets/:bucketId/files') ->desc('List files') ->groups(['api', 'storage']) ->label('scope', 'files.read') - ->label('resourceType', 'buckets') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'listFiles') @@ -800,7 +800,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId') ->desc('Get file') ->groups(['api', 'storage']) ->label('scope', 'files.read') - ->label('resourceType', 'buckets') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'getFile') @@ -848,7 +848,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') ->desc('Get file preview') ->groups(['api', 'storage']) ->label('scope', 'files.read') - ->label('resourceType', 'buckets') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('cache', true) ->label('cache.resourceType', 'bucket/{request.bucketId}') ->label('cache.resource', 'file/{request.fileId}') @@ -1021,7 +1021,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') ->desc('Get file for download') ->groups(['api', 'storage']) ->label('scope', 'files.read') - ->label('resourceType', 'buckets') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'getFileDownload') @@ -1162,7 +1162,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') ->desc('Get file for view') ->groups(['api', 'storage']) ->label('scope', 'files.read') - ->label('resourceType', 'buckets') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'getFileView') @@ -1314,7 +1314,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push') ->desc('Get file for push notification') ->groups(['api', 'storage']) ->label('scope', 'public') - ->label('resourceType', 'buckets') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', '*/*') ->label('sdk.methodType', 'location') @@ -1469,7 +1469,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') ->desc('Update file') ->groups(['api', 'storage']) ->label('scope', 'files.write') - ->label('resourceType', 'buckets') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('event', 'buckets.[bucketId].files.[fileId].update') ->label('audits.event', 'file.update') ->label('audits.resource', 'file/{response.$id}') @@ -1574,7 +1574,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') ->desc('Delete file') ->groups(['api', 'storage']) ->label('scope', 'files.write') - ->label('resourceType', 'buckets') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('event', 'buckets.[bucketId].files.[fileId].delete') ->label('audits.event', 'file.delete') ->label('audits.resource', 'file/{request.fileId}') @@ -1668,7 +1668,7 @@ App::get('/v1/storage/usage') ->desc('Get storage usage stats') ->groups(['api', 'storage']) ->label('scope', 'files.read') - ->label('resourceType', 'buckets') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'getUsage') @@ -1748,7 +1748,7 @@ App::get('/v1/storage/:bucketId/usage') ->desc('Get bucket usage stats') ->groups(['api', 'storage']) ->label('scope', 'files.read') - ->label('resourceType', 'buckets') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'getBucketUsage') diff --git a/app/controllers/general.php b/app/controllers/general.php index a5f6b38a44..59899d704b 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -137,7 +137,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo throw new AppwriteException(AppwriteException::FUNCTION_NOT_FOUND); } - if ($isResourceBlocked($project, 'functions', $functionId)) { + if ($isResourceBlocked($project, RESOURCE_TYPE_FUNCTIONS, $functionId)) { throw new AppwriteException(AppwriteException::GENERAL_RESOURCE_BLOCKED); } diff --git a/app/init.php b/app/init.php index 50ab123754..a558c0fa25 100644 --- a/app/init.php +++ b/app/init.php @@ -286,6 +286,17 @@ const METRIC_NETWORK_REQUESTS = 'network.requests'; const METRIC_NETWORK_INBOUND = 'network.inbound'; const METRIC_NETWORK_OUTBOUND = 'network.outbound'; +// Resource types + +const RESOURCE_TYPE_PROJECTS = 'projects'; +const RESOURCE_TYPE_FUNCTIONS = 'functions'; +const RESOURCE_TYPE_DATABASES = 'databases'; +const RESOURCE_TYPE_BUCKETS = 'buckets'; +const RESOURCE_TYPE_PROVIDERS = 'providers'; +const RESOURCE_TYPE_TOPICS = 'topics'; +const RESOURCE_TYPE_SUBSCRIBERS = 'subscribers'; +const RESOURCE_TYPE_MESSAGES = 'messages'; + $register = new Registry(); App::setMode(System::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION)); diff --git a/src/Appwrite/Platform/Workers/Functions.php b/src/Appwrite/Platform/Workers/Functions.php index 1da8c88b92..72a3334f2f 100644 --- a/src/Appwrite/Platform/Workers/Functions.php +++ b/src/Appwrite/Platform/Workers/Functions.php @@ -126,7 +126,7 @@ class Functions extends Action continue; } - if ($isResourceBlocked($project, 'functions', $function->getId())) { + if ($isResourceBlocked($project, RESOURCE_TYPE_FUNCTIONS, $function->getId())) { Console::log('Function ' . $function->getId() . ' is blocked, skipping execution.'); continue; } @@ -161,7 +161,7 @@ class Functions extends Action return; } - if ($isResourceBlocked($project, 'functions', $function->getId())) { + if ($isResourceBlocked($project, RESOURCE_TYPE_FUNCTIONS, $function->getId())) { Console::log('Function ' . $function->getId() . ' is blocked, skipping execution.'); return; }