diff --git a/.env b/.env index f6a6a7f642..8f7d7996e7 100644 --- a/.env +++ b/.env @@ -2,6 +2,7 @@ _APP_ENV=development _APP_EDITION=self-hosted _APP_LOCALE=en _APP_WORKER_PER_CORE=6 +_APP_COMPRESSION_MIN_SIZE_BYTES=1024 _APP_CONSOLE_WHITELIST_ROOT=disabled _APP_CONSOLE_WHITELIST_EMAILS= _APP_CONSOLE_SESSION_ALERTS=enabled @@ -22,6 +23,7 @@ _APP_OPENSSL_KEY_V1=your-secret-key _APP_DOMAIN=traefik _APP_DOMAIN_FUNCTIONS=functions.localhost _APP_DOMAIN_TARGET=localhost +_APP_RULES_FORMAT=md5 _APP_REDIS_HOST=redis _APP_REDIS_PORT=6379 _APP_REDIS_PASS= @@ -107,3 +109,4 @@ _APP_MESSAGE_EMAIL_TEST_DSN= _APP_MESSAGE_PUSH_TEST_DSN= _APP_WEBHOOK_MAX_FAILED_ATTEMPTS=10 _APP_PROJECT_REGIONS=default +_APP_FUNCTIONS_CREATION_ABUSE_LIMIT=5000 diff --git a/.github/workflows/sdk-preview.yml b/.github/workflows/sdk-preview.yml new file mode 100644 index 0000000000..92b4f454cb --- /dev/null +++ b/.github/workflows/sdk-preview.yml @@ -0,0 +1,33 @@ +name: "Console SDK Preview" + +on: + pull_request: + paths: + - 'app/config/specs/*-latest-console.json' + + +jobs: + setup: + name: Setup & Build Console SDK + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Load and Start Appwrite + run: | + docker compose build + docker compose up -d + docker compose exec appwrite sdks --platform=console --sdk=web --version=latest --git=no + sudo chown -R $USER:$USER ./app/sdks/console-web + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Build and Publish SDK + working-directory: ./app/sdks/console-web + run: | + npm install + npm run build + npx pkg-pr-new publish --comment=update diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7c53b03b52..3ab240c1b9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -125,7 +125,13 @@ jobs: Webhooks, VCS, Messaging, + Migrations ] + tables-mode: [ + 'Project', + 'Shared V1', + 'Shared V2', + ] steps: - name: checkout @@ -144,11 +150,26 @@ jobs: docker compose up -d sleep 30 - - name: Run ${{matrix.service}} Tests - run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug - - - name: Run ${{matrix.service}} Shared Tables Tests - run: _APP_DATABASE_SHARED_TABLES=database_db_main docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug + - name: Run ${{ matrix.service }} tests with ${{ matrix.tables-mode }} table mode + run: | + if [ "${{ matrix.tables-mode }}" == "Shared V1" ]; then + echo "Using shared tables V1" + export _APP_DATABASE_SHARED_TABLES=database_db_main + export _APP_DATABASE_SHARED_TABLES_V1=database_db_main + elif [ "${{ matrix.tables-mode }}" == "Shared V2" ]; then + echo "Using shared tables V2" + export _APP_DATABASE_SHARED_TABLES=database_db_main + export _APP_DATABASE_SHARED_TABLES_V1= + else + echo "Using project tables" + export _APP_DATABASE_SHARED_TABLES= + export _APP_DATABASE_SHARED_TABLES_V1= + fi + + docker compose exec -T \ + -e _APP_DATABASE_SHARED_TABLES \ + -e _APP_DATABASE_SHARED_TABLES_V1 \ + appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug benchmarking: name: Benchmark diff --git a/.gitignore b/.gitignore index 5ae03e2a56..a68af84071 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ dev/yasd_init.php .phpunit.result.cache Makefile appwrite.json +/.zed/ diff --git a/CHANGES.md b/CHANGES.md index 9b6172eeab..62db3d525e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,117 @@ +# Version 1.6.1 + +## What's Changed + +### Notable changes + +* Remove JPEG fallback for webp by @lohanidamodar in https://github.com/appwrite/appwrite/pull/8746 +* Add heic and avif support by @lohanidamodar in https://github.com/appwrite/appwrite/pull/7718 +* Add new runtimes by @Meldiron in https://github.com/appwrite/appwrite/pull/8771 +* Remove audits deletion by @shimonewman in https://github.com/appwrite/appwrite/pull/8766 +* Bump assistant by @loks0n in https://github.com/appwrite/appwrite/pull/8801 +* Change max queries values to 500 by @fogelito in https://github.com/appwrite/appwrite/pull/8802 +* Allow '.wav' as 'audio/x-wav' as well by @basert in https://github.com/appwrite/appwrite/pull/8846 +* Use 1 instead of 0.5 cpu for default function specification by @loks0n in https://github.com/appwrite/appwrite/pull/8848 +* Update function runtimes by @christyjacob4 in https://github.com/appwrite/appwrite/pull/8781 +* Add a realtime heartbeat by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/8943 + +### Fixes + +* Trigger functions event only if event is not paused by @lohanidamodar in https://github.com/appwrite/appwrite/pull/8526 +* Update docker-compose to restart usage-dump by @feschaffa in https://github.com/appwrite/appwrite/pull/8642 +* Fix typo in scheduler base by @fogelito in https://github.com/appwrite/appwrite/pull/8691 +* Add domain and force HTTPS env vars to mail worker by @stnguyen90 in https://github.com/appwrite/appwrite/pull/8722 +* Fix webp by @lohanidamodar in https://github.com/appwrite/appwrite/pull/8732 +* Ignore junction tables by @fogelito in https://github.com/appwrite/appwrite/pull/8728 +* Fix logger throwing fatal error by @lohanidamodar in https://github.com/appwrite/appwrite/pull/8724 +* Fix missing protocol for testing SMTP by @byawitz in https://github.com/appwrite/appwrite/pull/8749 +* Make create execution async loose by @loks0n in https://github.com/appwrite/appwrite/pull/8707 +* Fix invalid cursor value by @fogelito in https://github.com/appwrite/appwrite/pull/8109 +* Fix target deletes by @abnegate in https://github.com/appwrite/appwrite/pull/8833 +* Fix translation commas by @loks0n in https://github.com/appwrite/appwrite/pull/8892 +* Fix Migrations having source creds being overwritten and add Migration tests by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/8897 +* Fix validator usage for updating string size by @abnegate in https://github.com/appwrite/appwrite/pull/8890 +* Fix create user event not triggering by @loks0n in https://github.com/appwrite/appwrite/pull/8718 +* Improve error handling and logging in the database worker by @fogelito in https://github.com/appwrite/appwrite/pull/8944 +* Remove inaccurate info about leaving the URL parameter empty by @ebenezerdon in https://github.com/appwrite/appwrite/pull/8963 +* Ensure indexes are updated when updating an attribute key by @fogelito in https://github.com/appwrite/appwrite/pull/8971 +* Remove duplicate dart-2.16 runtime template by @stnguyen90 in https://github.com/appwrite/appwrite/pull/8972 +* Fix team invites with existing session by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/9006 +* Improve handling of HTTP requests by dispatching to safe workers by @Meldiron in https://github.com/appwrite/appwrite/pull/9016 +* Fix users create session secret by @stnguyen90 in https://github.com/appwrite/appwrite/pull/9019 +* Fix swoole task warning by @Meldiron in https://github.com/appwrite/appwrite/pull/9025 + +### Miscellaneous + +* Update Init copy by @adityaoberai in https://github.com/appwrite/appwrite/pull/8557 +* Fix security scan permissions and comment by @EVDOG4LIFE in https://github.com/appwrite/appwrite/pull/8525 +* Add Trivy security scans by @btme0011 in https://github.com/appwrite/appwrite/pull/6876 +* Update database stack by @abnegate in https://github.com/appwrite/appwrite/pull/8564 +* Bump database by @abnegate in https://github.com/appwrite/appwrite/pull/8573 +* Sync main with 1.5.x by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/8589 +* Add AWS to one-click installs by @byawitz in https://github.com/appwrite/appwrite/pull/8593 +* Update Init copy in readme by @adityaoberai in https://github.com/appwrite/appwrite/pull/8618 +* Sync main into 1.6.x by @stnguyen90 in https://github.com/appwrite/appwrite/pull/8685 +* Sync 1.6.x into main by @stnguyen90 in https://github.com/appwrite/appwrite/pull/8686 +* Feat coroutines by @Meldiron in https://github.com/appwrite/appwrite/pull/7826 +* Sync main into 1.6.x by @Meldiron in https://github.com/appwrite/appwrite/pull/8719 +* Sentence casing endpoint API reference by @choir241 in https://github.com/appwrite/appwrite/pull/8617 +* DB storage metrics by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/8404 +* Fix exception thrown when optional array attribute does not exist by @lohanidamodar in https://github.com/appwrite/appwrite/pull/8391 +* Add projects channels to realtime by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/8735 +* Base for console roles support by @lohanidamodar in https://github.com/appwrite/appwrite/pull/8565 +* Remove DB disk storage calculation by @christyjacob4 in https://github.com/appwrite/appwrite/pull/8745 +* Messaging adapter default values by @shimonewman in https://github.com/appwrite/appwrite/pull/8742 +* Add payload response type by @loks0n in https://github.com/appwrite/appwrite/pull/8720 +* Fix flaky functions tests by @loks0n in https://github.com/appwrite/appwrite/pull/8682 +* Migrations Backups by @fogelito in https://github.com/appwrite/appwrite/pull/8186 +* Add test for response and request filters by @vermakhushboo in https://github.com/appwrite/appwrite/pull/8697 +* Bump version in SECURITY.md by @EVDOG4LIFE in https://github.com/appwrite/appwrite/pull/8755 +* Add originalId attribute to databases collection by @fogelito in https://github.com/appwrite/appwrite/pull/8764 +* Fix Walter References by @ItzNotABug in https://github.com/appwrite/appwrite/pull/8757 +* Update database by @abnegate in https://github.com/appwrite/appwrite/pull/8769 +* Move new attributes by @abnegate in https://github.com/appwrite/appwrite/pull/8777 +* Add ping endpoint by @loks0n in https://github.com/appwrite/appwrite/pull/8761 +* Fix GitHub action caching by @loks0n in https://github.com/appwrite/appwrite/pull/8772 +* Chore release ruby SDK by @abnegate in https://github.com/appwrite/appwrite/pull/8767 +* Call migration success on success by @abnegate in https://github.com/appwrite/appwrite/pull/8782 +* Update utopia-php/system to 0.9.0 by @basert in https://github.com/appwrite/appwrite/pull/8780 +* Move createDocument from api to worker by @vermakhushboo in https://github.com/appwrite/appwrite/pull/8776 +* Add missing indexes by @christyjacob4 in https://github.com/appwrite/appwrite/pull/8803 +* Update database by @abnegate in https://github.com/appwrite/appwrite/pull/8809 +* Fix typo in BLR region by @stnguyen90 in https://github.com/appwrite/appwrite/pull/8756 +* Add tests for project variables by @vermakhushboo in https://github.com/appwrite/appwrite/pull/8815 +* Replace 'Expires' with 'Cache-Control: private' header to avoid CDN caching by @basert in https://github.com/appwrite/appwrite/pull/8836 +* Allow blocking based on resource attributes by @basert in https://github.com/appwrite/appwrite/pull/8812 +* Check if resource is blocked inside functions worker by @basert in https://github.com/appwrite/appwrite/pull/8855 +* Fix missing allow attribute by @abnegate in https://github.com/appwrite/appwrite/pull/8889 +* Revert function execution order by @basert in https://github.com/appwrite/appwrite/pull/8857 +* Use resource type constants by @basert in https://github.com/appwrite/appwrite/pull/8895 +* Update Database lib by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/8680 +* Update database by @abnegate in https://github.com/appwrite/appwrite/pull/8917 +* Update database by @abnegate in https://github.com/appwrite/appwrite/pull/8923 +* Update database for transaction counter fixes with retries by @abnegate in https://github.com/appwrite/appwrite/pull/8927 +* Validate string permissions by @fogelito in https://github.com/appwrite/appwrite/pull/8929 +* Add PubSub adapter support by @basert in https://github.com/appwrite/appwrite/pull/8905 +* List memberships as client by @loks0n in https://github.com/appwrite/appwrite/pull/8913 +* Fix XDebug Extension not being removed by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/8891 +* Update database by @abnegate in https://github.com/appwrite/appwrite/pull/8946 +* Use utopia compression by @loks0n in https://github.com/appwrite/appwrite/pull/8938 +* Make compression minimum size configurable by @loks0n in https://github.com/appwrite/appwrite/pull/8947 +* Revert "Update database" by @christyjacob4 in https://github.com/appwrite/appwrite/pull/8949 +* Fix setpaused by @loks0n in https://github.com/appwrite/appwrite/pull/8948 +* Use getDocument instead of find() for rules by @christyjacob4 in https://github.com/appwrite/appwrite/pull/8951 +* Remove double fetch from migrations worker by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/8956 +* Fix memberships privacy MFA by @loks0n in https://github.com/appwrite/appwrite/pull/8969 +* Add telemetry by @basert in https://github.com/appwrite/appwrite/pull/8960 +* Send migration errors individually by @PineappleIOnic in https://github.com/appwrite/appwrite/pull/8959 +* Add console sdk previews by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/8990 +* Unset index length by @fogelito in https://github.com/appwrite/appwrite/pull/8978 +* Update base to 0.9.5 by @basert in https://github.com/appwrite/appwrite/pull/9005 +* Sync main into 1.6.x by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/9011 +* Improved shared tables V2 by @abnegate in https://github.com/appwrite/appwrite/pull/9013 +* Ensure backwards compatibility for 1.6.x by @christyjacob4 in https://github.com/appwrite/appwrite/pull/9018 + # Version 1.6.0 ## What's Changed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b92361e51a..e89aa369cf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -613,6 +613,18 @@ If you need to clear the cache, you can do so by running the following command: docker compose exec redis redis-cli FLUSHALL ``` +## Using preview domains locally + +Appwrite Functions are automatically given a domain you can visit to execute the function. This domain has format `[SOMETHING].functions.localhost` unless you changed `_APP_DOMAIN_FUNCTIONS` environment variable. This default value works great when running Appwrite locally, but it can be impossible to use preview domains with Cloud woekspaces such as Gitpod or GitHub Codespaces. + +To use preview domains on Cloud workspaces, you can visit hostname provided by them, and supply function's preview domain as URL parameter: + +``` +https://8080-appwrite-appwrite-mjeb3ebilwv.ws-eu116.gitpod.io/ping?preview=672b3c7eab1ac523ccf5.functions.localhost +``` + +The path was set to `/ping` intentionally. Visiting `/` for preview domains might trigger Console background worker, and trigger redirect to Console without our preview URL param. Visiting different path ensures this doesnt happen. + ## Tutorials From time to time, our team will add tutorials that will help contributors find their way in the Appwrite source code. Below is a list of currently available tutorials: diff --git a/Dockerfile b/Dockerfile index 13b018df0f..41810f5dc4 100755 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \ --no-plugins --no-scripts --prefer-dist \ `if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi` -FROM appwrite/base:0.9.3 AS final +FROM appwrite/base:0.9.5 AS final LABEL maintainer="team@appwrite.io" @@ -92,10 +92,10 @@ RUN chmod +x /usr/local/bin/doctor && \ RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/ # Enable Extensions -RUN if [ "$DEBUG" == "true" ]; then cp /usr/src/code/dev/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini; fi -RUN if [ "$DEBUG" == "true" ]; then mkdir -p /tmp/xdebug; fi +RUN if [ "$DEBUG" = "true" ]; then cp /usr/src/code/dev/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini; fi +RUN if [ "$DEBUG" = "true" ]; then mkdir -p /tmp/xdebug; fi RUN if [ "$DEBUG" = "false" ]; then rm -rf /usr/src/code/dev; fi -RUN if [ "$DEBUG" = "false" ]; then rm -f /usr/local/lib/php/extensions/no-debug-non-zts-20220829/xdebug.so; fi +RUN if [ "$DEBUG" = "false" ]; then rm -f /usr/local/lib/php/extensions/no-debug-non-zts-20230831/xdebug.so; fi EXPOSE 80 diff --git a/README-CN.md b/README-CN.md index a5eac1be03..92a9bf9806 100644 --- a/README-CN.md +++ b/README-CN.md @@ -67,7 +67,7 @@ docker run -it --rm \ --volume /var/run/docker.sock:/var/run/docker.sock \ --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \ --entrypoint="install" \ - appwrite/appwrite:1.6.0 + appwrite/appwrite:1.6.1 ``` ### Windows @@ -79,7 +79,7 @@ docker run -it --rm ^ --volume //var/run/docker.sock:/var/run/docker.sock ^ --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^ --entrypoint="install" ^ - appwrite/appwrite:1.6.0 + appwrite/appwrite:1.6.1 ``` #### PowerShell @@ -89,7 +89,7 @@ docker run -it --rm ` --volume /var/run/docker.sock:/var/run/docker.sock ` --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ` --entrypoint="install" ` - appwrite/appwrite:1.6.0 + appwrite/appwrite:1.6.1 ``` 运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。 diff --git a/README.md b/README.md index a9856a7310..ab57e65c4c 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ docker run -it --rm \ --volume /var/run/docker.sock:/var/run/docker.sock \ --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \ --entrypoint="install" \ - appwrite/appwrite:1.6.0 + appwrite/appwrite:1.6.1 ``` ### Windows @@ -87,7 +87,7 @@ docker run -it --rm ^ --volume //var/run/docker.sock:/var/run/docker.sock ^ --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^ --entrypoint="install" ^ - appwrite/appwrite:1.6.0 + appwrite/appwrite:1.6.1 ``` #### PowerShell @@ -97,7 +97,7 @@ docker run -it --rm ` --volume /var/run/docker.sock:/var/run/docker.sock ` --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ` --entrypoint="install" ` - appwrite/appwrite:1.6.0 + appwrite/appwrite:1.6.1 ``` Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation. 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 diff --git a/app/cli.php b/app/cli.php index 23502ec402..47f4525f0b 100644 --- a/app/cli.php +++ b/app/cli.php @@ -52,7 +52,7 @@ CLI::setResource('pools', function (Registry $register) { return $register->get('pools'); }, ['register']); -CLI::setResource('dbForConsole', function ($pools, $cache) { +CLI::setResource('dbForPlatform', function ($pools, $cache) { $sleep = 3; $maxAttempts = 5; $attempts = 0; @@ -67,9 +67,9 @@ CLI::setResource('dbForConsole', function ($pools, $cache) { ->pop() ->getResource(); - $dbForConsole = new Database($dbAdapter, $cache); + $dbForPlatform = new Database($dbAdapter, $cache); - $dbForConsole + $dbForPlatform ->setNamespace('_console') ->setMetadata('host', \gethostname()) ->setMetadata('project', 'console'); @@ -78,7 +78,7 @@ CLI::setResource('dbForConsole', function ($pools, $cache) { $collections = Config::getParam('collections', [])['console']; $last = \array_key_last($collections); - if (!($dbForConsole->exists($dbForConsole->getDatabase(), $last))) { /** TODO cache ready variable using registry */ + if (!($dbForPlatform->exists($dbForPlatform->getDatabase(), $last))) { /** TODO cache ready variable using registry */ throw new Exception('Tables not ready yet.'); } @@ -94,15 +94,15 @@ CLI::setResource('dbForConsole', function ($pools, $cache) { throw new Exception("Console is not ready yet. Please try again later."); } - return $dbForConsole; + return $dbForPlatform; }, ['pools', 'cache']); -CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) { +CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) { $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools - return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases) { + return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) { if ($project->isEmpty() || $project->getId() === 'console') { - return $dbForConsole; + return $dbForPlatform; } try { @@ -114,8 +114,9 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, if (isset($databases[$dsn->getHost()])) { $database = $databases[$dsn->getHost()]; + $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + if (\in_array($dsn->getHost(), $sharedTables)) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -136,10 +137,10 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, ->getResource(); $database = new Database($dbAdapter, $cache); - $databases[$dsn->getHost()] = $database; + $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + if (\in_array($dsn->getHost(), $sharedTables)) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -157,7 +158,7 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, return $database; }; -}, ['pools', 'dbForConsole', 'cache']); +}, ['pools', 'dbForPlatform', 'cache']); CLI::setResource('queue', function (Group $pools) { return $pools->get('queue')->pop()->getResource(); @@ -180,7 +181,7 @@ CLI::setResource('logError', function (Registry $register) { $log = new Log(); $log->setNamespace($namespace); - $log->setServer(\gethostname()); + $log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname())); $log->setVersion($version); $log->setType(Log::TYPE_ERROR); $log->setMessage($error->getMessage()); 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' => [], + ] ], ], diff --git a/app/config/errors.php b/app/config/errors.php index fc79599b12..f09d1596eb 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.', @@ -681,7 +686,7 @@ return [ ], Exception::ATTRIBUTE_LIMIT_EXCEEDED => [ 'name' => Exception::ATTRIBUTE_LIMIT_EXCEEDED, - 'description' => 'The maximum number of attributes has been reached.', + 'description' => 'The maximum number or size of attributes for this collection has been reached.', 'code' => 400, ], Exception::ATTRIBUTE_VALUE_INVALID => [ diff --git a/app/config/function-templates.php b/app/config/function-templates.php index 762c33dd9a..fec9d2227f 100644 --- a/app/config/function-templates.php +++ b/app/config/function-templates.php @@ -11,7 +11,7 @@ const TEMPLATE_RUNTIMES = [ ], 'DART' => [ 'name' => 'dart', - 'versions' => ['3.5', '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'] ], 'GO' => [ 'name' => 'go', 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-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/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 937ab298de..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,44 +19,44 @@ "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", - "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.", "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..12ac05e8da 100644 --- a/app/config/locale/translations/km.json +++ b/app/config/locale/translations/km.json @@ -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/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..5a53b16055 100644 --- a/app/config/locale/translations/th.json +++ b/app/config/locale/translations/th.json @@ -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.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..76a545a1d4 100644 --- a/app/config/locale/translations/vi.json +++ b/app/config/locale/translations/vi.json @@ -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}}" -} \ 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 +} 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, diff --git a/app/config/runtimes/specifications.php b/app/config/runtimes/specifications.php index d3625db8a2..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' => 0.5 + 'cpus' => 1 // TODO: revert this, it's a temporary change to test function performance. ], Specification::S_1VCPU_512MB => [ 'slug' => Specification::S_1VCPU_512MB, 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 c00d4e2d07..e3cd909b4e 100644 --- a/app/config/specs/open-api3-1.6.x-client.json +++ b/app/config/specs/open-api3-1.6.x-client.json @@ -4945,7 +4945,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 305, + "weight": 306, "cookies": false, "type": "", "deprecated": false, @@ -5033,7 +5033,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 304, + "weight": 305, "cookies": false, "type": "", "deprecated": false, @@ -5150,7 +5150,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 306, + "weight": 307, "cookies": false, "type": "", "deprecated": false, @@ -5226,7 +5226,7 @@ }, "x-appwrite": { "method": "query", - "weight": 330, + "weight": 331, "cookies": false, "type": "graphql", "deprecated": false, @@ -5280,7 +5280,7 @@ }, "x-appwrite": { "method": "mutation", - "weight": 329, + "weight": 330, "cookies": false, "type": "graphql", "deprecated": false, @@ -5766,7 +5766,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 381, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -5851,7 +5851,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 385, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -5928,7 +5928,7 @@ }, "x-appwrite": { "method": "listFiles", - "weight": 207, + "weight": 208, "cookies": false, "type": "", "deprecated": false, @@ -6016,7 +6016,7 @@ }, "x-appwrite": { "method": "createFile", - "weight": 206, + "weight": 207, "cookies": false, "type": "upload", "deprecated": false, @@ -6116,7 +6116,7 @@ }, "x-appwrite": { "method": "getFile", - "weight": 208, + "weight": 209, "cookies": false, "type": "", "deprecated": false, @@ -6190,7 +6190,7 @@ }, "x-appwrite": { "method": "updateFile", - "weight": 213, + "weight": 214, "cookies": false, "type": "", "deprecated": false, @@ -6281,7 +6281,7 @@ }, "x-appwrite": { "method": "deleteFile", - "weight": 214, + "weight": 215, "cookies": false, "type": "", "deprecated": false, @@ -6350,7 +6350,7 @@ }, "x-appwrite": { "method": "getFileDownload", - "weight": 210, + "weight": 211, "cookies": false, "type": "location", "deprecated": false, @@ -6419,7 +6419,7 @@ }, "x-appwrite": { "method": "getFilePreview", - "weight": 209, + "weight": 210, "cookies": false, "type": "location", "deprecated": false, @@ -6637,7 +6637,7 @@ }, "x-appwrite": { "method": "getFileView", - "weight": 211, + "weight": 212, "cookies": false, "type": "location", "deprecated": false, @@ -6713,7 +6713,7 @@ }, "x-appwrite": { "method": "list", - "weight": 218, + "weight": 219, "cookies": false, "type": "", "deprecated": false, @@ -6791,7 +6791,7 @@ }, "x-appwrite": { "method": "create", - "weight": 217, + "weight": 218, "cookies": false, "type": "", "deprecated": false, @@ -6878,7 +6878,7 @@ }, "x-appwrite": { "method": "get", - "weight": 219, + "weight": 220, "cookies": false, "type": "", "deprecated": false, @@ -6942,7 +6942,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 221, + "weight": 222, "cookies": false, "type": "", "deprecated": false, @@ -7018,7 +7018,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 223, + "weight": 224, "cookies": false, "type": "", "deprecated": false, @@ -7069,7 +7069,7 @@ "tags": [ "teams" ], - "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint.", + "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Memberships List", @@ -7084,7 +7084,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 225, + "weight": 226, "cookies": false, "type": "", "deprecated": false, @@ -7172,7 +7172,7 @@ }, "x-appwrite": { "method": "createMembership", - "weight": 224, + "weight": 225, "cookies": false, "type": "", "deprecated": false, @@ -7270,7 +7270,7 @@ "tags": [ "teams" ], - "description": "Get a team member by the membership unique id. All team members have read access for this resource.", + "description": "Get a team member by the membership unique id. All team members have read access for this resource. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Membership", @@ -7285,7 +7285,7 @@ }, "x-appwrite": { "method": "getMembership", - "weight": 226, + "weight": 227, "cookies": false, "type": "", "deprecated": false, @@ -7359,7 +7359,7 @@ }, "x-appwrite": { "method": "updateMembership", - "weight": 227, + "weight": 228, "cookies": false, "type": "", "deprecated": false, @@ -7448,7 +7448,7 @@ }, "x-appwrite": { "method": "deleteMembership", - "weight": 229, + "weight": 230, "cookies": false, "type": "", "deprecated": false, @@ -7524,7 +7524,7 @@ }, "x-appwrite": { "method": "updateMembershipStatus", - "weight": 228, + "weight": 229, "cookies": false, "type": "", "deprecated": false, @@ -7624,7 +7624,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 220, + "weight": 221, "cookies": false, "type": "", "deprecated": false, @@ -7687,7 +7687,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 222, + "weight": 223, "cookies": false, "type": "", "deprecated": false, @@ -9266,12 +9266,12 @@ }, "userName": { "type": "string", - "description": "User name.", + "description": "User name. Hide this attribute by toggling membership privacy in the Console.", "x-example": "John Doe" }, "userEmail": { "type": "string", - "description": "User email address.", + "description": "User email address. Hide this attribute by toggling membership privacy in the Console.", "x-example": "john@appwrite.io" }, "teamId": { @@ -9301,7 +9301,7 @@ }, "mfa": { "type": "boolean", - "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise.", + "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise. Hide this attribute by toggling membership privacy in the Console.", "x-example": false }, "roles": { @@ -9826,7 +9826,7 @@ "name": { "type": "string", "description": "Target Name.", - "x-example": "Aegon apple token" + "x-example": "Apple iPhone 12" }, "userId": { "type": "string", @@ -9848,6 +9848,11 @@ "type": "string", "description": "The target identifier.", "x-example": "token" + }, + "expired": { + "type": "boolean", + "description": "Is the target expired.", + "x-example": false } }, "required": [ @@ -9857,7 +9862,8 @@ "name", "userId", "providerType", - "identifier" + "identifier", + "expired" ] } }, 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 87c61ada7b..369fea741a 100644 --- a/app/config/specs/open-api3-1.6.x-console.json +++ b/app/config/specs/open-api3-1.6.x-console.json @@ -4465,7 +4465,7 @@ }, "x-appwrite": { "method": "chat", - "weight": 332, + "weight": 333, "cookies": false, "type": "", "deprecated": false, @@ -4534,7 +4534,7 @@ }, "x-appwrite": { "method": "variables", - "weight": 331, + "weight": 332, "cookies": false, "type": "", "deprecated": false, @@ -7477,7 +7477,7 @@ "size": { "type": "integer", "description": "Maximum size of the string attribute.", - "x-example": null + "x-example": 1 }, "newKey": { "type": "string", @@ -9308,7 +9308,7 @@ }, "x-appwrite": { "method": "list", - "weight": 288, + "weight": 289, "cookies": false, "type": "", "deprecated": false, @@ -9383,7 +9383,7 @@ }, "x-appwrite": { "method": "create", - "weight": 287, + "weight": 288, "cookies": false, "type": "", "deprecated": false, @@ -9487,7 +9487,8 @@ "cpp-20", "bun-1.0", "bun-1.1", - "go-1.23" + "go-1.23", + "static-1" ], "x-enum-name": null, "x-enum-keys": [] @@ -9630,7 +9631,7 @@ }, "x-appwrite": { "method": "listRuntimes", - "weight": 289, + "weight": 290, "cookies": false, "type": "", "deprecated": false, @@ -9681,7 +9682,7 @@ }, "x-appwrite": { "method": "listSpecifications", - "weight": 290, + "weight": 291, "cookies": false, "type": "", "deprecated": false, @@ -9733,7 +9734,7 @@ }, "x-appwrite": { "method": "listTemplates", - "weight": 313, + "weight": 314, "cookies": false, "type": "", "deprecated": false, @@ -9835,7 +9836,7 @@ }, "x-appwrite": { "method": "getTemplate", - "weight": 314, + "weight": 315, "cookies": false, "type": "", "deprecated": false, @@ -9897,7 +9898,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 293, + "weight": 294, "cookies": false, "type": "", "deprecated": false, @@ -9971,7 +9972,7 @@ }, "x-appwrite": { "method": "get", - "weight": 291, + "weight": 292, "cookies": false, "type": "", "deprecated": false, @@ -10032,7 +10033,7 @@ }, "x-appwrite": { "method": "update", - "weight": 294, + "weight": 295, "cookies": false, "type": "", "deprecated": false, @@ -10143,7 +10144,8 @@ "cpp-20", "bun-1.0", "bun-1.1", - "go-1.23" + "go-1.23", + "static-1" ], "x-enum-name": null, "x-enum-keys": [] @@ -10256,7 +10258,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 297, + "weight": 298, "cookies": false, "type": "", "deprecated": false, @@ -10319,7 +10321,7 @@ }, "x-appwrite": { "method": "listDeployments", - "weight": 299, + "weight": 300, "cookies": false, "type": "", "deprecated": false, @@ -10404,7 +10406,7 @@ }, "x-appwrite": { "method": "createDeployment", - "weight": 298, + "weight": 299, "cookies": false, "type": "upload", "deprecated": false, @@ -10502,7 +10504,7 @@ }, "x-appwrite": { "method": "getDeployment", - "weight": 300, + "weight": 301, "cookies": false, "type": "", "deprecated": false, @@ -10573,7 +10575,7 @@ }, "x-appwrite": { "method": "updateDeployment", - "weight": 296, + "weight": 297, "cookies": false, "type": "", "deprecated": false, @@ -10637,7 +10639,7 @@ }, "x-appwrite": { "method": "deleteDeployment", - "weight": 301, + "weight": 302, "cookies": false, "type": "", "deprecated": false, @@ -10703,7 +10705,7 @@ }, "x-appwrite": { "method": "createBuild", - "weight": 302, + "weight": 303, "cookies": false, "type": "", "deprecated": false, @@ -10790,7 +10792,7 @@ }, "x-appwrite": { "method": "updateDeploymentBuild", - "weight": 303, + "weight": 304, "cookies": false, "type": "", "deprecated": false, @@ -10856,7 +10858,7 @@ }, "x-appwrite": { "method": "getDeploymentDownload", - "weight": 295, + "weight": 296, "cookies": false, "type": "location", "deprecated": false, @@ -10931,7 +10933,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 305, + "weight": 306, "cookies": false, "type": "", "deprecated": false, @@ -11019,7 +11021,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 304, + "weight": 305, "cookies": false, "type": "", "deprecated": false, @@ -11136,7 +11138,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 306, + "weight": 307, "cookies": false, "type": "", "deprecated": false, @@ -11203,7 +11205,7 @@ }, "x-appwrite": { "method": "deleteExecution", - "weight": 307, + "weight": 308, "cookies": false, "type": "", "deprecated": false, @@ -11276,7 +11278,7 @@ }, "x-appwrite": { "method": "getFunctionUsage", - "weight": 292, + "weight": 293, "cookies": false, "type": "", "deprecated": false, @@ -11360,7 +11362,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 309, + "weight": 310, "cookies": false, "type": "", "deprecated": false, @@ -11421,7 +11423,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 308, + "weight": 309, "cookies": false, "type": "", "deprecated": false, @@ -11509,7 +11511,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 310, + "weight": 311, "cookies": false, "type": "", "deprecated": false, @@ -11580,7 +11582,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 311, + "weight": 312, "cookies": false, "type": "", "deprecated": false, @@ -11668,7 +11670,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 312, + "weight": 313, "cookies": false, "type": "", "deprecated": false, @@ -11741,7 +11743,7 @@ }, "x-appwrite": { "method": "query", - "weight": 330, + "weight": 331, "cookies": false, "type": "graphql", "deprecated": false, @@ -11795,7 +11797,7 @@ }, "x-appwrite": { "method": "mutation", - "weight": 329, + "weight": 330, "cookies": false, "type": "graphql", "deprecated": false, @@ -13671,7 +13673,7 @@ }, "x-appwrite": { "method": "listMessages", - "weight": 389, + "weight": 390, "cookies": false, "type": "", "deprecated": false, @@ -13749,7 +13751,7 @@ }, "x-appwrite": { "method": "createEmail", - "weight": 386, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -13895,7 +13897,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 393, + "weight": 394, "cookies": false, "type": "", "deprecated": false, @@ -14043,7 +14045,7 @@ }, "x-appwrite": { "method": "createPush", - "weight": 388, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -14200,7 +14202,7 @@ }, "x-appwrite": { "method": "updatePush", - "weight": 395, + "weight": 396, "cookies": false, "type": "", "deprecated": false, @@ -14359,7 +14361,7 @@ }, "x-appwrite": { "method": "createSms", - "weight": 387, + "weight": 388, "cookies": false, "type": "", "deprecated": false, @@ -14470,7 +14472,7 @@ }, "x-appwrite": { "method": "updateSms", - "weight": 394, + "weight": 395, "cookies": false, "type": "", "deprecated": false, @@ -14584,7 +14586,7 @@ }, "x-appwrite": { "method": "getMessage", - "weight": 392, + "weight": 393, "cookies": false, "type": "", "deprecated": false, @@ -14639,7 +14641,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 396, + "weight": 397, "cookies": false, "type": "", "deprecated": false, @@ -14703,7 +14705,7 @@ }, "x-appwrite": { "method": "listMessageLogs", - "weight": 390, + "weight": 391, "cookies": false, "type": "", "deprecated": false, @@ -14780,7 +14782,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 391, + "weight": 392, "cookies": false, "type": "", "deprecated": false, @@ -14857,7 +14859,7 @@ }, "x-appwrite": { "method": "listProviders", - "weight": 361, + "weight": 362, "cookies": false, "type": "", "deprecated": false, @@ -14935,7 +14937,7 @@ }, "x-appwrite": { "method": "createApnsProvider", - "weight": 360, + "weight": 361, "cookies": false, "type": "", "deprecated": false, @@ -15042,7 +15044,7 @@ }, "x-appwrite": { "method": "updateApnsProvider", - "weight": 373, + "weight": 374, "cookies": false, "type": "", "deprecated": false, @@ -15152,7 +15154,7 @@ }, "x-appwrite": { "method": "createFcmProvider", - "weight": 359, + "weight": 360, "cookies": false, "type": "", "deprecated": false, @@ -15239,7 +15241,7 @@ }, "x-appwrite": { "method": "updateFcmProvider", - "weight": 372, + "weight": 373, "cookies": false, "type": "", "deprecated": false, @@ -15329,7 +15331,7 @@ }, "x-appwrite": { "method": "createMailgunProvider", - "weight": 351, + "weight": 352, "cookies": false, "type": "", "deprecated": false, @@ -15446,7 +15448,7 @@ }, "x-appwrite": { "method": "updateMailgunProvider", - "weight": 364, + "weight": 365, "cookies": false, "type": "", "deprecated": false, @@ -15566,7 +15568,7 @@ }, "x-appwrite": { "method": "createMsg91Provider", - "weight": 354, + "weight": 355, "cookies": false, "type": "", "deprecated": false, @@ -15663,7 +15665,7 @@ }, "x-appwrite": { "method": "updateMsg91Provider", - "weight": 367, + "weight": 368, "cookies": false, "type": "", "deprecated": false, @@ -15763,7 +15765,7 @@ }, "x-appwrite": { "method": "createSendgridProvider", - "weight": 352, + "weight": 353, "cookies": false, "type": "", "deprecated": false, @@ -15870,7 +15872,7 @@ }, "x-appwrite": { "method": "updateSendgridProvider", - "weight": 365, + "weight": 366, "cookies": false, "type": "", "deprecated": false, @@ -15980,7 +15982,7 @@ }, "x-appwrite": { "method": "createSmtpProvider", - "weight": 353, + "weight": 354, "cookies": false, "type": "", "deprecated": false, @@ -16125,7 +16127,7 @@ }, "x-appwrite": { "method": "updateSmtpProvider", - "weight": 366, + "weight": 367, "cookies": false, "type": "", "deprecated": false, @@ -16272,7 +16274,7 @@ }, "x-appwrite": { "method": "createTelesignProvider", - "weight": 355, + "weight": 356, "cookies": false, "type": "", "deprecated": false, @@ -16369,7 +16371,7 @@ }, "x-appwrite": { "method": "updateTelesignProvider", - "weight": 368, + "weight": 369, "cookies": false, "type": "", "deprecated": false, @@ -16469,7 +16471,7 @@ }, "x-appwrite": { "method": "createTextmagicProvider", - "weight": 356, + "weight": 357, "cookies": false, "type": "", "deprecated": false, @@ -16566,7 +16568,7 @@ }, "x-appwrite": { "method": "updateTextmagicProvider", - "weight": 369, + "weight": 370, "cookies": false, "type": "", "deprecated": false, @@ -16666,7 +16668,7 @@ }, "x-appwrite": { "method": "createTwilioProvider", - "weight": 357, + "weight": 358, "cookies": false, "type": "", "deprecated": false, @@ -16763,7 +16765,7 @@ }, "x-appwrite": { "method": "updateTwilioProvider", - "weight": 370, + "weight": 371, "cookies": false, "type": "", "deprecated": false, @@ -16863,7 +16865,7 @@ }, "x-appwrite": { "method": "createVonageProvider", - "weight": 358, + "weight": 359, "cookies": false, "type": "", "deprecated": false, @@ -16960,7 +16962,7 @@ }, "x-appwrite": { "method": "updateVonageProvider", - "weight": 371, + "weight": 372, "cookies": false, "type": "", "deprecated": false, @@ -17060,7 +17062,7 @@ }, "x-appwrite": { "method": "getProvider", - "weight": 363, + "weight": 364, "cookies": false, "type": "", "deprecated": false, @@ -17115,7 +17117,7 @@ }, "x-appwrite": { "method": "deleteProvider", - "weight": 374, + "weight": 375, "cookies": false, "type": "", "deprecated": false, @@ -17179,7 +17181,7 @@ }, "x-appwrite": { "method": "listProviderLogs", - "weight": 362, + "weight": 363, "cookies": false, "type": "", "deprecated": false, @@ -17256,7 +17258,7 @@ }, "x-appwrite": { "method": "listSubscriberLogs", - "weight": 383, + "weight": 384, "cookies": false, "type": "", "deprecated": false, @@ -17333,7 +17335,7 @@ }, "x-appwrite": { "method": "listTopics", - "weight": 376, + "weight": 377, "cookies": false, "type": "", "deprecated": false, @@ -17409,7 +17411,7 @@ }, "x-appwrite": { "method": "createTopic", - "weight": 375, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -17494,7 +17496,7 @@ }, "x-appwrite": { "method": "getTopic", - "weight": 378, + "weight": 379, "cookies": false, "type": "", "deprecated": false, @@ -17556,7 +17558,7 @@ }, "x-appwrite": { "method": "updateTopic", - "weight": 379, + "weight": 380, "cookies": false, "type": "", "deprecated": false, @@ -17635,7 +17637,7 @@ }, "x-appwrite": { "method": "deleteTopic", - "weight": 380, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -17699,7 +17701,7 @@ }, "x-appwrite": { "method": "listTopicLogs", - "weight": 377, + "weight": 378, "cookies": false, "type": "", "deprecated": false, @@ -17776,7 +17778,7 @@ }, "x-appwrite": { "method": "listSubscribers", - "weight": 382, + "weight": 383, "cookies": false, "type": "", "deprecated": false, @@ -17862,7 +17864,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 381, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -17954,7 +17956,7 @@ }, "x-appwrite": { "method": "getSubscriber", - "weight": 384, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -18019,7 +18021,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 385, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -18096,7 +18098,7 @@ }, "x-appwrite": { "method": "list", - "weight": 338, + "weight": 339, "cookies": false, "type": "", "deprecated": false, @@ -18125,7 +18127,7 @@ "parameters": [ { "name": "queries", - "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/databases#querying-documents). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: status, stage, source, resources, statusCounters, resourceData, errors", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/databases#querying-documents). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: status, stage, source, destination, resources, statusCounters, resourceData, errors", "required": false, "schema": { "type": "array", @@ -18172,7 +18174,7 @@ }, "x-appwrite": { "method": "createAppwriteMigration", - "weight": 333, + "weight": 334, "cookies": false, "type": "", "deprecated": false, @@ -18262,7 +18264,7 @@ }, "x-appwrite": { "method": "getAppwriteReport", - "weight": 340, + "weight": 341, "cookies": false, "type": "", "deprecated": false, @@ -18357,7 +18359,7 @@ }, "x-appwrite": { "method": "createFirebaseMigration", - "weight": 335, + "weight": 336, "cookies": false, "type": "", "deprecated": false, @@ -18428,7 +18430,7 @@ }, "x-appwrite": { "method": "deleteFirebaseAuth", - "weight": 346, + "weight": 347, "cookies": false, "type": "", "deprecated": false, @@ -18478,7 +18480,7 @@ }, "x-appwrite": { "method": "createFirebaseOAuthMigration", - "weight": 334, + "weight": 335, "cookies": false, "type": "", "deprecated": false, @@ -18556,7 +18558,7 @@ }, "x-appwrite": { "method": "listFirebaseProjects", - "weight": 345, + "weight": 346, "cookies": false, "type": "", "deprecated": false, @@ -18606,7 +18608,7 @@ }, "x-appwrite": { "method": "getFirebaseReport", - "weight": 341, + "weight": 342, "cookies": false, "type": "", "deprecated": false, @@ -18680,7 +18682,7 @@ }, "x-appwrite": { "method": "getFirebaseReportOAuth", - "weight": 342, + "weight": 343, "cookies": false, "type": "", "deprecated": false, @@ -18754,7 +18756,7 @@ }, "x-appwrite": { "method": "createNHostMigration", - "weight": 337, + "weight": 338, "cookies": false, "type": "", "deprecated": false, @@ -18867,7 +18869,7 @@ }, "x-appwrite": { "method": "getNHostReport", - "weight": 348, + "weight": 349, "cookies": false, "type": "", "deprecated": false, @@ -19002,7 +19004,7 @@ }, "x-appwrite": { "method": "createSupabaseMigration", - "weight": 336, + "weight": 337, "cookies": false, "type": "", "deprecated": false, @@ -19109,7 +19111,7 @@ }, "x-appwrite": { "method": "getSupabaseReport", - "weight": 347, + "weight": 348, "cookies": false, "type": "", "deprecated": false, @@ -19235,7 +19237,7 @@ }, "x-appwrite": { "method": "get", - "weight": 339, + "weight": 340, "cookies": false, "type": "", "deprecated": false, @@ -19295,7 +19297,7 @@ }, "x-appwrite": { "method": "retry", - "weight": 349, + "weight": 350, "cookies": false, "type": "", "deprecated": false, @@ -19348,7 +19350,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 350, + "weight": 351, "cookies": false, "type": "", "deprecated": false, @@ -19410,7 +19412,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 195, + "weight": 196, "cookies": false, "type": "", "deprecated": false, @@ -19500,7 +19502,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 197, + "weight": 198, "cookies": false, "type": "", "deprecated": false, @@ -19548,7 +19550,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 196, + "weight": 197, "cookies": false, "type": "", "deprecated": false, @@ -19623,7 +19625,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 198, + "weight": 199, "cookies": false, "type": "", "deprecated": false, @@ -19683,7 +19685,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 199, + "weight": 200, "cookies": false, "type": "", "deprecated": false, @@ -19760,7 +19762,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 200, + "weight": 201, "cookies": false, "type": "", "deprecated": false, @@ -20210,7 +20212,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 169, + "weight": 170, "cookies": false, "type": "", "deprecated": false, @@ -20447,7 +20449,7 @@ }, "x-appwrite": { "method": "updateAuthDuration", - "weight": 162, + "weight": 163, "cookies": false, "type": "", "deprecated": false, @@ -20528,7 +20530,7 @@ }, "x-appwrite": { "method": "updateAuthLimit", - "weight": 161, + "weight": 162, "cookies": false, "type": "", "deprecated": false, @@ -20609,7 +20611,7 @@ }, "x-appwrite": { "method": "updateAuthSessionsLimit", - "weight": 167, + "weight": 168, "cookies": false, "type": "", "deprecated": false, @@ -20668,6 +20670,99 @@ } } }, + "\/projects\/{projectId}\/auth\/memberships-privacy": { + "patch": { + "summary": "Update project memberships privacy attributes", + "operationId": "projectsUpdateMembershipsPrivacy", + "tags": [ + "projects" + ], + "description": "", + "responses": { + "200": { + "description": "Project", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/project" + } + } + } + } + }, + "x-appwrite": { + "method": "updateMembershipsPrivacy", + "weight": 161, + "cookies": false, + "type": "", + "deprecated": false, + "demo": "projects\/update-memberships-privacy.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "projects.write", + "platforms": [ + "console" + ], + "packaging": false, + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [] + } + ], + "parameters": [ + { + "name": "projectId", + "description": "Project unique ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "userName": { + "type": "boolean", + "description": "Set to true to show userName to members of a team.", + "x-example": false + }, + "userEmail": { + "type": "boolean", + "description": "Set to true to show email to members of a team.", + "x-example": false + }, + "mfa": { + "type": "boolean", + "description": "Set to true to show mfa to members of a team.", + "x-example": false + } + }, + "required": [ + "userName", + "userEmail", + "mfa" + ] + } + } + } + } + } + }, "\/projects\/{projectId}\/auth\/mock-numbers": { "patch": { "summary": "Update the mock numbers for the project", @@ -20690,7 +20785,7 @@ }, "x-appwrite": { "method": "updateMockNumbers", - "weight": 168, + "weight": 169, "cookies": false, "type": "", "deprecated": false, @@ -20774,7 +20869,7 @@ }, "x-appwrite": { "method": "updateAuthPasswordDictionary", - "weight": 165, + "weight": 166, "cookies": false, "type": "", "deprecated": false, @@ -20855,7 +20950,7 @@ }, "x-appwrite": { "method": "updateAuthPasswordHistory", - "weight": 164, + "weight": 165, "cookies": false, "type": "", "deprecated": false, @@ -20936,7 +21031,7 @@ }, "x-appwrite": { "method": "updatePersonalDataCheck", - "weight": 166, + "weight": 167, "cookies": false, "type": "", "deprecated": false, @@ -21098,7 +21193,7 @@ }, "x-appwrite": { "method": "updateAuthStatus", - "weight": 163, + "weight": 164, "cookies": false, "type": "", "deprecated": false, @@ -21200,7 +21295,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 181, + "weight": 182, "cookies": false, "type": "", "deprecated": false, @@ -21289,7 +21384,7 @@ }, "x-appwrite": { "method": "listKeys", - "weight": 177, + "weight": 178, "cookies": false, "type": "", "deprecated": false, @@ -21349,7 +21444,7 @@ }, "x-appwrite": { "method": "createKey", - "weight": 176, + "weight": 177, "cookies": false, "type": "", "deprecated": false, @@ -21444,7 +21539,7 @@ }, "x-appwrite": { "method": "getKey", - "weight": 178, + "weight": 179, "cookies": false, "type": "", "deprecated": false, @@ -21514,7 +21609,7 @@ }, "x-appwrite": { "method": "updateKey", - "weight": 179, + "weight": 180, "cookies": false, "type": "", "deprecated": false, @@ -21610,7 +21705,7 @@ }, "x-appwrite": { "method": "deleteKey", - "weight": 180, + "weight": 181, "cookies": false, "type": "", "deprecated": false, @@ -21821,7 +21916,7 @@ }, "x-appwrite": { "method": "listPlatforms", - "weight": 183, + "weight": 184, "cookies": false, "type": "", "deprecated": false, @@ -21881,7 +21976,7 @@ }, "x-appwrite": { "method": "createPlatform", - "weight": 182, + "weight": 183, "cookies": false, "type": "", "deprecated": false, @@ -22002,7 +22097,7 @@ }, "x-appwrite": { "method": "getPlatform", - "weight": 184, + "weight": 185, "cookies": false, "type": "", "deprecated": false, @@ -22072,7 +22167,7 @@ }, "x-appwrite": { "method": "updatePlatform", - "weight": 185, + "weight": 186, "cookies": false, "type": "", "deprecated": false, @@ -22169,7 +22264,7 @@ }, "x-appwrite": { "method": "deletePlatform", - "weight": 186, + "weight": 187, "cookies": false, "type": "", "deprecated": false, @@ -22424,7 +22519,7 @@ }, "x-appwrite": { "method": "updateSmtp", - "weight": 187, + "weight": 188, "cookies": false, "type": "", "deprecated": false, @@ -22544,7 +22639,7 @@ }, "x-appwrite": { "method": "createSmtpTest", - "weight": 188, + "weight": 189, "cookies": false, "type": "", "deprecated": false, @@ -22758,7 +22853,7 @@ }, "x-appwrite": { "method": "getEmailTemplate", - "weight": 190, + "weight": 191, "cookies": false, "type": "", "deprecated": false, @@ -22984,7 +23079,7 @@ }, "x-appwrite": { "method": "updateEmailTemplate", - "weight": 192, + "weight": 193, "cookies": false, "type": "", "deprecated": false, @@ -23250,7 +23345,7 @@ }, "x-appwrite": { "method": "deleteEmailTemplate", - "weight": 194, + "weight": 195, "cookies": false, "type": "", "deprecated": false, @@ -23478,7 +23573,7 @@ }, "x-appwrite": { "method": "getSmsTemplate", - "weight": 189, + "weight": 190, "cookies": false, "type": "", "deprecated": false, @@ -23701,7 +23796,7 @@ }, "x-appwrite": { "method": "updateSmsTemplate", - "weight": 191, + "weight": 192, "cookies": false, "type": "", "deprecated": false, @@ -23943,7 +24038,7 @@ }, "x-appwrite": { "method": "deleteSmsTemplate", - "weight": 193, + "weight": 194, "cookies": false, "type": "", "deprecated": false, @@ -24168,7 +24263,7 @@ }, "x-appwrite": { "method": "listWebhooks", - "weight": 171, + "weight": 172, "cookies": false, "type": "", "deprecated": false, @@ -24228,7 +24323,7 @@ }, "x-appwrite": { "method": "createWebhook", - "weight": 170, + "weight": 171, "cookies": false, "type": "", "deprecated": false, @@ -24345,7 +24440,7 @@ }, "x-appwrite": { "method": "getWebhook", - "weight": 172, + "weight": 173, "cookies": false, "type": "", "deprecated": false, @@ -24415,7 +24510,7 @@ }, "x-appwrite": { "method": "updateWebhook", - "weight": 173, + "weight": 174, "cookies": false, "type": "", "deprecated": false, @@ -24533,7 +24628,7 @@ }, "x-appwrite": { "method": "deleteWebhook", - "weight": 175, + "weight": 176, "cookies": false, "type": "", "deprecated": false, @@ -24605,7 +24700,7 @@ }, "x-appwrite": { "method": "updateWebhookSignature", - "weight": 174, + "weight": 175, "cookies": false, "type": "", "deprecated": false, @@ -24677,7 +24772,7 @@ }, "x-appwrite": { "method": "listRules", - "weight": 316, + "weight": 317, "cookies": false, "type": "", "deprecated": false, @@ -24751,7 +24846,7 @@ }, "x-appwrite": { "method": "createRule", - "weight": 315, + "weight": 316, "cookies": false, "type": "", "deprecated": false, @@ -24837,7 +24932,7 @@ }, "x-appwrite": { "method": "getRule", - "weight": 317, + "weight": 318, "cookies": false, "type": "", "deprecated": false, @@ -24890,7 +24985,7 @@ }, "x-appwrite": { "method": "deleteRule", - "weight": 318, + "weight": 319, "cookies": false, "type": "", "deprecated": false, @@ -24952,7 +25047,7 @@ }, "x-appwrite": { "method": "updateRuleVerification", - "weight": 319, + "weight": 320, "cookies": false, "type": "", "deprecated": false, @@ -25014,7 +25109,7 @@ }, "x-appwrite": { "method": "listBuckets", - "weight": 202, + "weight": 203, "cookies": false, "type": "", "deprecated": false, @@ -25089,7 +25184,7 @@ }, "x-appwrite": { "method": "createBucket", - "weight": 201, + "weight": 202, "cookies": false, "type": "", "deprecated": false, @@ -25218,7 +25313,7 @@ }, "x-appwrite": { "method": "getBucket", - "weight": 203, + "weight": 204, "cookies": false, "type": "", "deprecated": false, @@ -25279,7 +25374,7 @@ }, "x-appwrite": { "method": "updateBucket", - "weight": 204, + "weight": 205, "cookies": false, "type": "", "deprecated": false, @@ -25405,7 +25500,7 @@ }, "x-appwrite": { "method": "deleteBucket", - "weight": 205, + "weight": 206, "cookies": false, "type": "", "deprecated": false, @@ -25468,7 +25563,7 @@ }, "x-appwrite": { "method": "listFiles", - "weight": 207, + "weight": 208, "cookies": false, "type": "", "deprecated": false, @@ -25556,7 +25651,7 @@ }, "x-appwrite": { "method": "createFile", - "weight": 206, + "weight": 207, "cookies": false, "type": "upload", "deprecated": false, @@ -25656,7 +25751,7 @@ }, "x-appwrite": { "method": "getFile", - "weight": 208, + "weight": 209, "cookies": false, "type": "", "deprecated": false, @@ -25730,7 +25825,7 @@ }, "x-appwrite": { "method": "updateFile", - "weight": 213, + "weight": 214, "cookies": false, "type": "", "deprecated": false, @@ -25821,7 +25916,7 @@ }, "x-appwrite": { "method": "deleteFile", - "weight": 214, + "weight": 215, "cookies": false, "type": "", "deprecated": false, @@ -25890,7 +25985,7 @@ }, "x-appwrite": { "method": "getFileDownload", - "weight": 210, + "weight": 211, "cookies": false, "type": "location", "deprecated": false, @@ -25959,7 +26054,7 @@ }, "x-appwrite": { "method": "getFilePreview", - "weight": 209, + "weight": 210, "cookies": false, "type": "location", "deprecated": false, @@ -26177,7 +26272,7 @@ }, "x-appwrite": { "method": "getFileView", - "weight": 211, + "weight": 212, "cookies": false, "type": "location", "deprecated": false, @@ -26253,7 +26348,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 215, + "weight": 216, "cookies": false, "type": "", "deprecated": false, @@ -26327,7 +26422,7 @@ }, "x-appwrite": { "method": "getBucketUsage", - "weight": 216, + "weight": 217, "cookies": false, "type": "", "deprecated": false, @@ -26411,7 +26506,7 @@ }, "x-appwrite": { "method": "list", - "weight": 218, + "weight": 219, "cookies": false, "type": "", "deprecated": false, @@ -26489,7 +26584,7 @@ }, "x-appwrite": { "method": "create", - "weight": 217, + "weight": 218, "cookies": false, "type": "", "deprecated": false, @@ -26576,7 +26671,7 @@ }, "x-appwrite": { "method": "get", - "weight": 219, + "weight": 220, "cookies": false, "type": "", "deprecated": false, @@ -26640,7 +26735,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 221, + "weight": 222, "cookies": false, "type": "", "deprecated": false, @@ -26716,7 +26811,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 223, + "weight": 224, "cookies": false, "type": "", "deprecated": false, @@ -26782,7 +26877,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 230, + "weight": 231, "cookies": false, "type": "", "deprecated": false, @@ -26842,7 +26937,7 @@ "tags": [ "teams" ], - "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint.", + "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Memberships List", @@ -26857,7 +26952,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 225, + "weight": 226, "cookies": false, "type": "", "deprecated": false, @@ -26945,7 +27040,7 @@ }, "x-appwrite": { "method": "createMembership", - "weight": 224, + "weight": 225, "cookies": false, "type": "", "deprecated": false, @@ -27043,7 +27138,7 @@ "tags": [ "teams" ], - "description": "Get a team member by the membership unique id. All team members have read access for this resource.", + "description": "Get a team member by the membership unique id. All team members have read access for this resource. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Membership", @@ -27058,7 +27153,7 @@ }, "x-appwrite": { "method": "getMembership", - "weight": 226, + "weight": 227, "cookies": false, "type": "", "deprecated": false, @@ -27132,7 +27227,7 @@ }, "x-appwrite": { "method": "updateMembership", - "weight": 227, + "weight": 228, "cookies": false, "type": "", "deprecated": false, @@ -27221,7 +27316,7 @@ }, "x-appwrite": { "method": "deleteMembership", - "weight": 229, + "weight": 230, "cookies": false, "type": "", "deprecated": false, @@ -27297,7 +27392,7 @@ }, "x-appwrite": { "method": "updateMembershipStatus", - "weight": 228, + "weight": 229, "cookies": false, "type": "", "deprecated": false, @@ -27396,7 +27491,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 220, + "weight": 221, "cookies": false, "type": "", "deprecated": false, @@ -27458,7 +27553,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 222, + "weight": 223, "cookies": false, "type": "", "deprecated": false, @@ -27541,7 +27636,7 @@ }, "x-appwrite": { "method": "list", - "weight": 240, + "weight": 241, "cookies": false, "type": "", "deprecated": false, @@ -27616,7 +27711,7 @@ }, "x-appwrite": { "method": "create", - "weight": 231, + "weight": 232, "cookies": false, "type": "", "deprecated": false, @@ -27706,7 +27801,7 @@ }, "x-appwrite": { "method": "createArgon2User", - "weight": 234, + "weight": 235, "cookies": false, "type": "", "deprecated": false, @@ -27793,7 +27888,7 @@ }, "x-appwrite": { "method": "createBcryptUser", - "weight": 232, + "weight": 233, "cookies": false, "type": "", "deprecated": false, @@ -27880,7 +27975,7 @@ }, "x-appwrite": { "method": "listIdentities", - "weight": 248, + "weight": 249, "cookies": false, "type": "", "deprecated": false, @@ -27950,7 +28045,7 @@ }, "x-appwrite": { "method": "deleteIdentity", - "weight": 271, + "weight": 272, "cookies": false, "type": "", "deprecated": false, @@ -28013,7 +28108,7 @@ }, "x-appwrite": { "method": "createMD5User", - "weight": 233, + "weight": 234, "cookies": false, "type": "", "deprecated": false, @@ -28100,7 +28195,7 @@ }, "x-appwrite": { "method": "createPHPassUser", - "weight": 236, + "weight": 237, "cookies": false, "type": "", "deprecated": false, @@ -28187,7 +28282,7 @@ }, "x-appwrite": { "method": "createScryptUser", - "weight": 237, + "weight": 238, "cookies": false, "type": "", "deprecated": false, @@ -28304,7 +28399,7 @@ }, "x-appwrite": { "method": "createScryptModifiedUser", - "weight": 238, + "weight": 239, "cookies": false, "type": "", "deprecated": false, @@ -28409,7 +28504,7 @@ }, "x-appwrite": { "method": "createSHAUser", - "weight": 235, + "weight": 236, "cookies": false, "type": "", "deprecated": false, @@ -28516,7 +28611,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 273, + "weight": 274, "cookies": false, "type": "", "deprecated": false, @@ -28590,7 +28685,7 @@ }, "x-appwrite": { "method": "get", - "weight": 241, + "weight": 242, "cookies": false, "type": "", "deprecated": false, @@ -28644,7 +28739,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 269, + "weight": 270, "cookies": false, "type": "", "deprecated": false, @@ -28707,7 +28802,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 254, + "weight": 255, "cookies": false, "type": "", "deprecated": false, @@ -28789,7 +28884,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 272, + "weight": 273, "cookies": false, "type": "", "deprecated": false, @@ -28873,7 +28968,7 @@ }, "x-appwrite": { "method": "updateLabels", - "weight": 250, + "weight": 251, "cookies": false, "type": "", "deprecated": false, @@ -28958,7 +29053,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 246, + "weight": 247, "cookies": false, "type": "", "deprecated": false, @@ -29034,7 +29129,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 245, + "weight": 246, "cookies": false, "type": "", "deprecated": false, @@ -29097,7 +29192,7 @@ }, "x-appwrite": { "method": "updateMfa", - "weight": 259, + "weight": 260, "cookies": false, "type": "", "deprecated": false, @@ -29179,7 +29274,7 @@ }, "x-appwrite": { "method": "deleteMfaAuthenticator", - "weight": 264, + "weight": 265, "cookies": false, "type": "", "deprecated": false, @@ -29257,7 +29352,7 @@ }, "x-appwrite": { "method": "listMfaFactors", - "weight": 260, + "weight": 261, "cookies": false, "type": "", "deprecated": false, @@ -29320,7 +29415,7 @@ }, "x-appwrite": { "method": "getMfaRecoveryCodes", - "weight": 261, + "weight": 262, "cookies": false, "type": "", "deprecated": false, @@ -29381,7 +29476,7 @@ }, "x-appwrite": { "method": "updateMfaRecoveryCodes", - "weight": 263, + "weight": 264, "cookies": false, "type": "", "deprecated": false, @@ -29442,7 +29537,7 @@ }, "x-appwrite": { "method": "createMfaRecoveryCodes", - "weight": 262, + "weight": 263, "cookies": false, "type": "", "deprecated": false, @@ -29505,7 +29600,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 252, + "weight": 253, "cookies": false, "type": "", "deprecated": false, @@ -29587,7 +29682,7 @@ }, "x-appwrite": { "method": "updatePassword", - "weight": 253, + "weight": 254, "cookies": false, "type": "", "deprecated": false, @@ -29669,7 +29764,7 @@ }, "x-appwrite": { "method": "updatePhone", - "weight": 255, + "weight": 256, "cookies": false, "type": "", "deprecated": false, @@ -29751,7 +29846,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 242, + "weight": 243, "cookies": false, "type": "", "deprecated": false, @@ -29812,7 +29907,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 257, + "weight": 258, "cookies": false, "type": "", "deprecated": false, @@ -29894,7 +29989,7 @@ }, "x-appwrite": { "method": "listSessions", - "weight": 244, + "weight": 245, "cookies": false, "type": "", "deprecated": false, @@ -29955,7 +30050,7 @@ }, "x-appwrite": { "method": "createSession", - "weight": 265, + "weight": 266, "cookies": false, "type": "", "deprecated": false, @@ -30009,7 +30104,7 @@ }, "x-appwrite": { "method": "deleteSessions", - "weight": 268, + "weight": 269, "cookies": false, "type": "", "deprecated": false, @@ -30065,7 +30160,7 @@ }, "x-appwrite": { "method": "deleteSession", - "weight": 267, + "weight": 268, "cookies": false, "type": "", "deprecated": false, @@ -30138,7 +30233,7 @@ }, "x-appwrite": { "method": "updateStatus", - "weight": 249, + "weight": 250, "cookies": false, "type": "", "deprecated": false, @@ -30220,7 +30315,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 247, + "weight": 248, "cookies": false, "type": "", "deprecated": false, @@ -30295,7 +30390,7 @@ }, "x-appwrite": { "method": "createTarget", - "weight": 239, + "weight": 240, "cookies": false, "type": "", "deprecated": false, @@ -30407,7 +30502,7 @@ }, "x-appwrite": { "method": "getTarget", - "weight": 243, + "weight": 244, "cookies": false, "type": "", "deprecated": false, @@ -30479,7 +30574,7 @@ }, "x-appwrite": { "method": "updateTarget", - "weight": 258, + "weight": 259, "cookies": false, "type": "", "deprecated": false, @@ -30570,7 +30665,7 @@ }, "x-appwrite": { "method": "deleteTarget", - "weight": 270, + "weight": 271, "cookies": false, "type": "", "deprecated": false, @@ -30644,7 +30739,7 @@ }, "x-appwrite": { "method": "createToken", - "weight": 266, + "weight": 267, "cookies": false, "type": "", "deprecated": false, @@ -30728,7 +30823,7 @@ }, "x-appwrite": { "method": "updateEmailVerification", - "weight": 256, + "weight": 257, "cookies": false, "type": "", "deprecated": false, @@ -30810,7 +30905,7 @@ }, "x-appwrite": { "method": "updatePhoneVerification", - "weight": 251, + "weight": 252, "cookies": false, "type": "", "deprecated": false, @@ -30892,7 +30987,7 @@ }, "x-appwrite": { "method": "listRepositories", - "weight": 278, + "weight": 279, "cookies": false, "type": "", "deprecated": false, @@ -30963,7 +31058,7 @@ }, "x-appwrite": { "method": "createRepository", - "weight": 279, + "weight": 280, "cookies": false, "type": "", "deprecated": false, @@ -31050,7 +31145,7 @@ }, "x-appwrite": { "method": "getRepository", - "weight": 280, + "weight": 281, "cookies": false, "type": "", "deprecated": false, @@ -31122,7 +31217,7 @@ }, "x-appwrite": { "method": "listRepositoryBranches", - "weight": 281, + "weight": 282, "cookies": false, "type": "", "deprecated": false, @@ -31194,7 +31289,7 @@ }, "x-appwrite": { "method": "getRepositoryContents", - "weight": 276, + "weight": 277, "cookies": false, "type": "", "deprecated": false, @@ -31277,7 +31372,7 @@ }, "x-appwrite": { "method": "createRepositoryDetection", - "weight": 277, + "weight": 278, "cookies": false, "type": "", "deprecated": false, @@ -31358,7 +31453,7 @@ }, "x-appwrite": { "method": "updateExternalDeployments", - "weight": 286, + "weight": 287, "cookies": false, "type": "", "deprecated": false, @@ -31449,7 +31544,7 @@ }, "x-appwrite": { "method": "listInstallations", - "weight": 283, + "weight": 284, "cookies": false, "type": "", "deprecated": false, @@ -31525,7 +31620,7 @@ }, "x-appwrite": { "method": "getInstallation", - "weight": 284, + "weight": 285, "cookies": false, "type": "", "deprecated": false, @@ -31578,7 +31673,7 @@ }, "x-appwrite": { "method": "deleteInstallation", - "weight": 285, + "weight": 286, "cookies": false, "type": "", "deprecated": false, @@ -34866,12 +34961,12 @@ }, "userName": { "type": "string", - "description": "User name.", + "description": "User name. Hide this attribute by toggling membership privacy in the Console.", "x-example": "John Doe" }, "userEmail": { "type": "string", - "description": "User email address.", + "description": "User email address. Hide this attribute by toggling membership privacy in the Console.", "x-example": "john@appwrite.io" }, "teamId": { @@ -34901,7 +34996,7 @@ }, "mfa": { "type": "boolean", - "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise.", + "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise. Hide this attribute by toggling membership privacy in the Console.", "x-example": false }, "roles": { @@ -35066,7 +35161,7 @@ "specification": { "type": "string", "description": "Machine specification for builds and executions.", - "x-example": "s-0.5vcpu-512mb" + "x-example": "s-1vcpu-512mb" } }, "required": [ @@ -35982,6 +36077,21 @@ "description": "Whether or not to send session alert emails to users.", "x-example": true }, + "authMembershipsUserName": { + "type": "boolean", + "description": "Whether or not to show user names in the teams membership response.", + "x-example": true + }, + "authMembershipsUserEmail": { + "type": "boolean", + "description": "Whether or not to show user emails in the teams membership response.", + "x-example": true + }, + "authMembershipsMfa": { + "type": "boolean", + "description": "Whether or not to show user MFA status in the teams membership response.", + "x-example": true + }, "oAuthProviders": { "type": "array", "description": "List of Auth Providers.", @@ -36187,6 +36297,9 @@ "authPersonalDataCheck", "authMockNumbers", "authSessionAlerts", + "authMembershipsUserName", + "authMembershipsUserEmail", + "authMembershipsMfa", "oAuthProviders", "platforms", "webhooks", @@ -37765,7 +37878,7 @@ "slug": { "type": "string", "description": "Size slug.", - "x-example": "s-0.5vcpu-512mb" + "x-example": "s-1vcpu-512mb" } }, "required": [ @@ -38403,7 +38516,7 @@ "name": { "type": "string", "description": "Target Name.", - "x-example": "Aegon apple token" + "x-example": "Apple iPhone 12" }, "userId": { "type": "string", @@ -38425,6 +38538,11 @@ "type": "string", "description": "The target identifier.", "x-example": "token" + }, + "expired": { + "type": "boolean", + "description": "Is the target expired.", + "x-example": false } }, "required": [ @@ -38434,7 +38552,8 @@ "name", "userId", "providerType", - "identifier" + "identifier", + "expired" ] }, "migration": { @@ -38471,9 +38590,14 @@ "description": "A string containing the type of source of the migration.", "x-example": "Appwrite" }, + "destination": { + "type": "string", + "description": "A string containing the type of destination of the migration.", + "x-example": "Appwrite" + }, "resources": { "type": "array", - "description": "Resources to migration.", + "description": "Resources to migrate.", "items": { "type": "string" }, @@ -38507,6 +38631,7 @@ "status", "stage", "source", + "destination", "resources", "statusCounters", "resourceData", 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 44270f16af..e84b751743 100644 --- a/app/config/specs/open-api3-1.6.x-server.json +++ b/app/config/specs/open-api3-1.6.x-server.json @@ -6997,7 +6997,7 @@ "size": { "type": "integer", "description": "Maximum size of the string attribute.", - "x-example": null + "x-example": 1 }, "newKey": { "type": "string", @@ -8414,7 +8414,7 @@ }, "x-appwrite": { "method": "list", - "weight": 288, + "weight": 289, "cookies": false, "type": "", "deprecated": false, @@ -8490,7 +8490,7 @@ }, "x-appwrite": { "method": "create", - "weight": 287, + "weight": 288, "cookies": false, "type": "", "deprecated": false, @@ -8595,7 +8595,8 @@ "cpp-20", "bun-1.0", "bun-1.1", - "go-1.23" + "go-1.23", + "static-1" ], "x-enum-name": null, "x-enum-keys": [] @@ -8738,7 +8739,7 @@ }, "x-appwrite": { "method": "listRuntimes", - "weight": 289, + "weight": 290, "cookies": false, "type": "", "deprecated": false, @@ -8790,7 +8791,7 @@ }, "x-appwrite": { "method": "listSpecifications", - "weight": 290, + "weight": 291, "cookies": false, "type": "", "deprecated": false, @@ -8843,7 +8844,7 @@ }, "x-appwrite": { "method": "get", - "weight": 291, + "weight": 292, "cookies": false, "type": "", "deprecated": false, @@ -8905,7 +8906,7 @@ }, "x-appwrite": { "method": "update", - "weight": 294, + "weight": 295, "cookies": false, "type": "", "deprecated": false, @@ -9017,7 +9018,8 @@ "cpp-20", "bun-1.0", "bun-1.1", - "go-1.23" + "go-1.23", + "static-1" ], "x-enum-name": null, "x-enum-keys": [] @@ -9130,7 +9132,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 297, + "weight": 298, "cookies": false, "type": "", "deprecated": false, @@ -9194,7 +9196,7 @@ }, "x-appwrite": { "method": "listDeployments", - "weight": 299, + "weight": 300, "cookies": false, "type": "", "deprecated": false, @@ -9280,7 +9282,7 @@ }, "x-appwrite": { "method": "createDeployment", - "weight": 298, + "weight": 299, "cookies": false, "type": "upload", "deprecated": false, @@ -9379,7 +9381,7 @@ }, "x-appwrite": { "method": "getDeployment", - "weight": 300, + "weight": 301, "cookies": false, "type": "", "deprecated": false, @@ -9451,7 +9453,7 @@ }, "x-appwrite": { "method": "updateDeployment", - "weight": 296, + "weight": 297, "cookies": false, "type": "", "deprecated": false, @@ -9516,7 +9518,7 @@ }, "x-appwrite": { "method": "deleteDeployment", - "weight": 301, + "weight": 302, "cookies": false, "type": "", "deprecated": false, @@ -9583,7 +9585,7 @@ }, "x-appwrite": { "method": "createBuild", - "weight": 302, + "weight": 303, "cookies": false, "type": "", "deprecated": false, @@ -9671,7 +9673,7 @@ }, "x-appwrite": { "method": "updateDeploymentBuild", - "weight": 303, + "weight": 304, "cookies": false, "type": "", "deprecated": false, @@ -9738,7 +9740,7 @@ }, "x-appwrite": { "method": "getDeploymentDownload", - "weight": 295, + "weight": 296, "cookies": false, "type": "location", "deprecated": false, @@ -9814,7 +9816,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 305, + "weight": 306, "cookies": false, "type": "", "deprecated": false, @@ -9904,7 +9906,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 304, + "weight": 305, "cookies": false, "type": "", "deprecated": false, @@ -10023,7 +10025,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 306, + "weight": 307, "cookies": false, "type": "", "deprecated": false, @@ -10092,7 +10094,7 @@ }, "x-appwrite": { "method": "deleteExecution", - "weight": 307, + "weight": 308, "cookies": false, "type": "", "deprecated": false, @@ -10166,7 +10168,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 309, + "weight": 310, "cookies": false, "type": "", "deprecated": false, @@ -10228,7 +10230,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 308, + "weight": 309, "cookies": false, "type": "", "deprecated": false, @@ -10317,7 +10319,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 310, + "weight": 311, "cookies": false, "type": "", "deprecated": false, @@ -10389,7 +10391,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 311, + "weight": 312, "cookies": false, "type": "", "deprecated": false, @@ -10478,7 +10480,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 312, + "weight": 313, "cookies": false, "type": "", "deprecated": false, @@ -10552,7 +10554,7 @@ }, "x-appwrite": { "method": "query", - "weight": 330, + "weight": 331, "cookies": false, "type": "graphql", "deprecated": false, @@ -10608,7 +10610,7 @@ }, "x-appwrite": { "method": "mutation", - "weight": 329, + "weight": 330, "cookies": false, "type": "graphql", "deprecated": false, @@ -12525,7 +12527,7 @@ }, "x-appwrite": { "method": "listMessages", - "weight": 389, + "weight": 390, "cookies": false, "type": "", "deprecated": false, @@ -12604,7 +12606,7 @@ }, "x-appwrite": { "method": "createEmail", - "weight": 386, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -12751,7 +12753,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 393, + "weight": 394, "cookies": false, "type": "", "deprecated": false, @@ -12900,7 +12902,7 @@ }, "x-appwrite": { "method": "createPush", - "weight": 388, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -13058,7 +13060,7 @@ }, "x-appwrite": { "method": "updatePush", - "weight": 395, + "weight": 396, "cookies": false, "type": "", "deprecated": false, @@ -13218,7 +13220,7 @@ }, "x-appwrite": { "method": "createSms", - "weight": 387, + "weight": 388, "cookies": false, "type": "", "deprecated": false, @@ -13330,7 +13332,7 @@ }, "x-appwrite": { "method": "updateSms", - "weight": 394, + "weight": 395, "cookies": false, "type": "", "deprecated": false, @@ -13445,7 +13447,7 @@ }, "x-appwrite": { "method": "getMessage", - "weight": 392, + "weight": 393, "cookies": false, "type": "", "deprecated": false, @@ -13501,7 +13503,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 396, + "weight": 397, "cookies": false, "type": "", "deprecated": false, @@ -13566,7 +13568,7 @@ }, "x-appwrite": { "method": "listMessageLogs", - "weight": 390, + "weight": 391, "cookies": false, "type": "", "deprecated": false, @@ -13644,7 +13646,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 391, + "weight": 392, "cookies": false, "type": "", "deprecated": false, @@ -13722,7 +13724,7 @@ }, "x-appwrite": { "method": "listProviders", - "weight": 361, + "weight": 362, "cookies": false, "type": "", "deprecated": false, @@ -13801,7 +13803,7 @@ }, "x-appwrite": { "method": "createApnsProvider", - "weight": 360, + "weight": 361, "cookies": false, "type": "", "deprecated": false, @@ -13909,7 +13911,7 @@ }, "x-appwrite": { "method": "updateApnsProvider", - "weight": 373, + "weight": 374, "cookies": false, "type": "", "deprecated": false, @@ -14020,7 +14022,7 @@ }, "x-appwrite": { "method": "createFcmProvider", - "weight": 359, + "weight": 360, "cookies": false, "type": "", "deprecated": false, @@ -14108,7 +14110,7 @@ }, "x-appwrite": { "method": "updateFcmProvider", - "weight": 372, + "weight": 373, "cookies": false, "type": "", "deprecated": false, @@ -14199,7 +14201,7 @@ }, "x-appwrite": { "method": "createMailgunProvider", - "weight": 351, + "weight": 352, "cookies": false, "type": "", "deprecated": false, @@ -14317,7 +14319,7 @@ }, "x-appwrite": { "method": "updateMailgunProvider", - "weight": 364, + "weight": 365, "cookies": false, "type": "", "deprecated": false, @@ -14438,7 +14440,7 @@ }, "x-appwrite": { "method": "createMsg91Provider", - "weight": 354, + "weight": 355, "cookies": false, "type": "", "deprecated": false, @@ -14536,7 +14538,7 @@ }, "x-appwrite": { "method": "updateMsg91Provider", - "weight": 367, + "weight": 368, "cookies": false, "type": "", "deprecated": false, @@ -14637,7 +14639,7 @@ }, "x-appwrite": { "method": "createSendgridProvider", - "weight": 352, + "weight": 353, "cookies": false, "type": "", "deprecated": false, @@ -14745,7 +14747,7 @@ }, "x-appwrite": { "method": "updateSendgridProvider", - "weight": 365, + "weight": 366, "cookies": false, "type": "", "deprecated": false, @@ -14856,7 +14858,7 @@ }, "x-appwrite": { "method": "createSmtpProvider", - "weight": 353, + "weight": 354, "cookies": false, "type": "", "deprecated": false, @@ -15002,7 +15004,7 @@ }, "x-appwrite": { "method": "updateSmtpProvider", - "weight": 366, + "weight": 367, "cookies": false, "type": "", "deprecated": false, @@ -15150,7 +15152,7 @@ }, "x-appwrite": { "method": "createTelesignProvider", - "weight": 355, + "weight": 356, "cookies": false, "type": "", "deprecated": false, @@ -15248,7 +15250,7 @@ }, "x-appwrite": { "method": "updateTelesignProvider", - "weight": 368, + "weight": 369, "cookies": false, "type": "", "deprecated": false, @@ -15349,7 +15351,7 @@ }, "x-appwrite": { "method": "createTextmagicProvider", - "weight": 356, + "weight": 357, "cookies": false, "type": "", "deprecated": false, @@ -15447,7 +15449,7 @@ }, "x-appwrite": { "method": "updateTextmagicProvider", - "weight": 369, + "weight": 370, "cookies": false, "type": "", "deprecated": false, @@ -15548,7 +15550,7 @@ }, "x-appwrite": { "method": "createTwilioProvider", - "weight": 357, + "weight": 358, "cookies": false, "type": "", "deprecated": false, @@ -15646,7 +15648,7 @@ }, "x-appwrite": { "method": "updateTwilioProvider", - "weight": 370, + "weight": 371, "cookies": false, "type": "", "deprecated": false, @@ -15747,7 +15749,7 @@ }, "x-appwrite": { "method": "createVonageProvider", - "weight": 358, + "weight": 359, "cookies": false, "type": "", "deprecated": false, @@ -15845,7 +15847,7 @@ }, "x-appwrite": { "method": "updateVonageProvider", - "weight": 371, + "weight": 372, "cookies": false, "type": "", "deprecated": false, @@ -15946,7 +15948,7 @@ }, "x-appwrite": { "method": "getProvider", - "weight": 363, + "weight": 364, "cookies": false, "type": "", "deprecated": false, @@ -16002,7 +16004,7 @@ }, "x-appwrite": { "method": "deleteProvider", - "weight": 374, + "weight": 375, "cookies": false, "type": "", "deprecated": false, @@ -16067,7 +16069,7 @@ }, "x-appwrite": { "method": "listProviderLogs", - "weight": 362, + "weight": 363, "cookies": false, "type": "", "deprecated": false, @@ -16145,7 +16147,7 @@ }, "x-appwrite": { "method": "listSubscriberLogs", - "weight": 383, + "weight": 384, "cookies": false, "type": "", "deprecated": false, @@ -16223,7 +16225,7 @@ }, "x-appwrite": { "method": "listTopics", - "weight": 376, + "weight": 377, "cookies": false, "type": "", "deprecated": false, @@ -16300,7 +16302,7 @@ }, "x-appwrite": { "method": "createTopic", - "weight": 375, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -16386,7 +16388,7 @@ }, "x-appwrite": { "method": "getTopic", - "weight": 378, + "weight": 379, "cookies": false, "type": "", "deprecated": false, @@ -16449,7 +16451,7 @@ }, "x-appwrite": { "method": "updateTopic", - "weight": 379, + "weight": 380, "cookies": false, "type": "", "deprecated": false, @@ -16529,7 +16531,7 @@ }, "x-appwrite": { "method": "deleteTopic", - "weight": 380, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -16594,7 +16596,7 @@ }, "x-appwrite": { "method": "listTopicLogs", - "weight": 377, + "weight": 378, "cookies": false, "type": "", "deprecated": false, @@ -16672,7 +16674,7 @@ }, "x-appwrite": { "method": "listSubscribers", - "weight": 382, + "weight": 383, "cookies": false, "type": "", "deprecated": false, @@ -16759,7 +16761,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 381, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -16853,7 +16855,7 @@ }, "x-appwrite": { "method": "getSubscriber", - "weight": 384, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -16919,7 +16921,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 385, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -16998,7 +17000,7 @@ }, "x-appwrite": { "method": "listBuckets", - "weight": 202, + "weight": 203, "cookies": false, "type": "", "deprecated": false, @@ -17074,7 +17076,7 @@ }, "x-appwrite": { "method": "createBucket", - "weight": 201, + "weight": 202, "cookies": false, "type": "", "deprecated": false, @@ -17204,7 +17206,7 @@ }, "x-appwrite": { "method": "getBucket", - "weight": 203, + "weight": 204, "cookies": false, "type": "", "deprecated": false, @@ -17266,7 +17268,7 @@ }, "x-appwrite": { "method": "updateBucket", - "weight": 204, + "weight": 205, "cookies": false, "type": "", "deprecated": false, @@ -17393,7 +17395,7 @@ }, "x-appwrite": { "method": "deleteBucket", - "weight": 205, + "weight": 206, "cookies": false, "type": "", "deprecated": false, @@ -17457,7 +17459,7 @@ }, "x-appwrite": { "method": "listFiles", - "weight": 207, + "weight": 208, "cookies": false, "type": "", "deprecated": false, @@ -17547,7 +17549,7 @@ }, "x-appwrite": { "method": "createFile", - "weight": 206, + "weight": 207, "cookies": false, "type": "upload", "deprecated": false, @@ -17649,7 +17651,7 @@ }, "x-appwrite": { "method": "getFile", - "weight": 208, + "weight": 209, "cookies": false, "type": "", "deprecated": false, @@ -17725,7 +17727,7 @@ }, "x-appwrite": { "method": "updateFile", - "weight": 213, + "weight": 214, "cookies": false, "type": "", "deprecated": false, @@ -17818,7 +17820,7 @@ }, "x-appwrite": { "method": "deleteFile", - "weight": 214, + "weight": 215, "cookies": false, "type": "", "deprecated": false, @@ -17889,7 +17891,7 @@ }, "x-appwrite": { "method": "getFileDownload", - "weight": 210, + "weight": 211, "cookies": false, "type": "location", "deprecated": false, @@ -17960,7 +17962,7 @@ }, "x-appwrite": { "method": "getFilePreview", - "weight": 209, + "weight": 210, "cookies": false, "type": "location", "deprecated": false, @@ -18180,7 +18182,7 @@ }, "x-appwrite": { "method": "getFileView", - "weight": 211, + "weight": 212, "cookies": false, "type": "location", "deprecated": false, @@ -18258,7 +18260,7 @@ }, "x-appwrite": { "method": "list", - "weight": 218, + "weight": 219, "cookies": false, "type": "", "deprecated": false, @@ -18338,7 +18340,7 @@ }, "x-appwrite": { "method": "create", - "weight": 217, + "weight": 218, "cookies": false, "type": "", "deprecated": false, @@ -18427,7 +18429,7 @@ }, "x-appwrite": { "method": "get", - "weight": 219, + "weight": 220, "cookies": false, "type": "", "deprecated": false, @@ -18493,7 +18495,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 221, + "weight": 222, "cookies": false, "type": "", "deprecated": false, @@ -18571,7 +18573,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 223, + "weight": 224, "cookies": false, "type": "", "deprecated": false, @@ -18624,7 +18626,7 @@ "tags": [ "teams" ], - "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint.", + "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Memberships List", @@ -18639,7 +18641,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 225, + "weight": 226, "cookies": false, "type": "", "deprecated": false, @@ -18729,7 +18731,7 @@ }, "x-appwrite": { "method": "createMembership", - "weight": 224, + "weight": 225, "cookies": false, "type": "", "deprecated": false, @@ -18829,7 +18831,7 @@ "tags": [ "teams" ], - "description": "Get a team member by the membership unique id. All team members have read access for this resource.", + "description": "Get a team member by the membership unique id. All team members have read access for this resource. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Membership", @@ -18844,7 +18846,7 @@ }, "x-appwrite": { "method": "getMembership", - "weight": 226, + "weight": 227, "cookies": false, "type": "", "deprecated": false, @@ -18920,7 +18922,7 @@ }, "x-appwrite": { "method": "updateMembership", - "weight": 227, + "weight": 228, "cookies": false, "type": "", "deprecated": false, @@ -19011,7 +19013,7 @@ }, "x-appwrite": { "method": "deleteMembership", - "weight": 229, + "weight": 230, "cookies": false, "type": "", "deprecated": false, @@ -19089,7 +19091,7 @@ }, "x-appwrite": { "method": "updateMembershipStatus", - "weight": 228, + "weight": 229, "cookies": false, "type": "", "deprecated": false, @@ -19190,7 +19192,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 220, + "weight": 221, "cookies": false, "type": "", "deprecated": false, @@ -19254,7 +19256,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 222, + "weight": 223, "cookies": false, "type": "", "deprecated": false, @@ -19339,7 +19341,7 @@ }, "x-appwrite": { "method": "list", - "weight": 240, + "weight": 241, "cookies": false, "type": "", "deprecated": false, @@ -19415,7 +19417,7 @@ }, "x-appwrite": { "method": "create", - "weight": 231, + "weight": 232, "cookies": false, "type": "", "deprecated": false, @@ -19506,7 +19508,7 @@ }, "x-appwrite": { "method": "createArgon2User", - "weight": 234, + "weight": 235, "cookies": false, "type": "", "deprecated": false, @@ -19594,7 +19596,7 @@ }, "x-appwrite": { "method": "createBcryptUser", - "weight": 232, + "weight": 233, "cookies": false, "type": "", "deprecated": false, @@ -19682,7 +19684,7 @@ }, "x-appwrite": { "method": "listIdentities", - "weight": 248, + "weight": 249, "cookies": false, "type": "", "deprecated": false, @@ -19753,7 +19755,7 @@ }, "x-appwrite": { "method": "deleteIdentity", - "weight": 271, + "weight": 272, "cookies": false, "type": "", "deprecated": false, @@ -19817,7 +19819,7 @@ }, "x-appwrite": { "method": "createMD5User", - "weight": 233, + "weight": 234, "cookies": false, "type": "", "deprecated": false, @@ -19905,7 +19907,7 @@ }, "x-appwrite": { "method": "createPHPassUser", - "weight": 236, + "weight": 237, "cookies": false, "type": "", "deprecated": false, @@ -19993,7 +19995,7 @@ }, "x-appwrite": { "method": "createScryptUser", - "weight": 237, + "weight": 238, "cookies": false, "type": "", "deprecated": false, @@ -20111,7 +20113,7 @@ }, "x-appwrite": { "method": "createScryptModifiedUser", - "weight": 238, + "weight": 239, "cookies": false, "type": "", "deprecated": false, @@ -20217,7 +20219,7 @@ }, "x-appwrite": { "method": "createSHAUser", - "weight": 235, + "weight": 236, "cookies": false, "type": "", "deprecated": false, @@ -20325,7 +20327,7 @@ }, "x-appwrite": { "method": "get", - "weight": 241, + "weight": 242, "cookies": false, "type": "", "deprecated": false, @@ -20380,7 +20382,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 269, + "weight": 270, "cookies": false, "type": "", "deprecated": false, @@ -20444,7 +20446,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 254, + "weight": 255, "cookies": false, "type": "", "deprecated": false, @@ -20527,7 +20529,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 272, + "weight": 273, "cookies": false, "type": "", "deprecated": false, @@ -20612,7 +20614,7 @@ }, "x-appwrite": { "method": "updateLabels", - "weight": 250, + "weight": 251, "cookies": false, "type": "", "deprecated": false, @@ -20698,7 +20700,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 246, + "weight": 247, "cookies": false, "type": "", "deprecated": false, @@ -20775,7 +20777,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 245, + "weight": 246, "cookies": false, "type": "", "deprecated": false, @@ -20839,7 +20841,7 @@ }, "x-appwrite": { "method": "updateMfa", - "weight": 259, + "weight": 260, "cookies": false, "type": "", "deprecated": false, @@ -20922,7 +20924,7 @@ }, "x-appwrite": { "method": "deleteMfaAuthenticator", - "weight": 264, + "weight": 265, "cookies": false, "type": "", "deprecated": false, @@ -21001,7 +21003,7 @@ }, "x-appwrite": { "method": "listMfaFactors", - "weight": 260, + "weight": 261, "cookies": false, "type": "", "deprecated": false, @@ -21065,7 +21067,7 @@ }, "x-appwrite": { "method": "getMfaRecoveryCodes", - "weight": 261, + "weight": 262, "cookies": false, "type": "", "deprecated": false, @@ -21127,7 +21129,7 @@ }, "x-appwrite": { "method": "updateMfaRecoveryCodes", - "weight": 263, + "weight": 264, "cookies": false, "type": "", "deprecated": false, @@ -21189,7 +21191,7 @@ }, "x-appwrite": { "method": "createMfaRecoveryCodes", - "weight": 262, + "weight": 263, "cookies": false, "type": "", "deprecated": false, @@ -21253,7 +21255,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 252, + "weight": 253, "cookies": false, "type": "", "deprecated": false, @@ -21336,7 +21338,7 @@ }, "x-appwrite": { "method": "updatePassword", - "weight": 253, + "weight": 254, "cookies": false, "type": "", "deprecated": false, @@ -21419,7 +21421,7 @@ }, "x-appwrite": { "method": "updatePhone", - "weight": 255, + "weight": 256, "cookies": false, "type": "", "deprecated": false, @@ -21502,7 +21504,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 242, + "weight": 243, "cookies": false, "type": "", "deprecated": false, @@ -21564,7 +21566,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 257, + "weight": 258, "cookies": false, "type": "", "deprecated": false, @@ -21647,7 +21649,7 @@ }, "x-appwrite": { "method": "listSessions", - "weight": 244, + "weight": 245, "cookies": false, "type": "", "deprecated": false, @@ -21709,7 +21711,7 @@ }, "x-appwrite": { "method": "createSession", - "weight": 265, + "weight": 266, "cookies": false, "type": "", "deprecated": false, @@ -21764,7 +21766,7 @@ }, "x-appwrite": { "method": "deleteSessions", - "weight": 268, + "weight": 269, "cookies": false, "type": "", "deprecated": false, @@ -21821,7 +21823,7 @@ }, "x-appwrite": { "method": "deleteSession", - "weight": 267, + "weight": 268, "cookies": false, "type": "", "deprecated": false, @@ -21895,7 +21897,7 @@ }, "x-appwrite": { "method": "updateStatus", - "weight": 249, + "weight": 250, "cookies": false, "type": "", "deprecated": false, @@ -21978,7 +21980,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 247, + "weight": 248, "cookies": false, "type": "", "deprecated": false, @@ -22054,7 +22056,7 @@ }, "x-appwrite": { "method": "createTarget", - "weight": 239, + "weight": 240, "cookies": false, "type": "", "deprecated": false, @@ -22167,7 +22169,7 @@ }, "x-appwrite": { "method": "getTarget", - "weight": 243, + "weight": 244, "cookies": false, "type": "", "deprecated": false, @@ -22240,7 +22242,7 @@ }, "x-appwrite": { "method": "updateTarget", - "weight": 258, + "weight": 259, "cookies": false, "type": "", "deprecated": false, @@ -22332,7 +22334,7 @@ }, "x-appwrite": { "method": "deleteTarget", - "weight": 270, + "weight": 271, "cookies": false, "type": "", "deprecated": false, @@ -22407,7 +22409,7 @@ }, "x-appwrite": { "method": "createToken", - "weight": 266, + "weight": 267, "cookies": false, "type": "", "deprecated": false, @@ -22492,7 +22494,7 @@ }, "x-appwrite": { "method": "updateEmailVerification", - "weight": 256, + "weight": 257, "cookies": false, "type": "", "deprecated": false, @@ -22575,7 +22577,7 @@ }, "x-appwrite": { "method": "updatePhoneVerification", - "weight": 251, + "weight": 252, "cookies": false, "type": "", "deprecated": false, @@ -25596,12 +25598,12 @@ }, "userName": { "type": "string", - "description": "User name.", + "description": "User name. Hide this attribute by toggling membership privacy in the Console.", "x-example": "John Doe" }, "userEmail": { "type": "string", - "description": "User email address.", + "description": "User email address. Hide this attribute by toggling membership privacy in the Console.", "x-example": "john@appwrite.io" }, "teamId": { @@ -25631,7 +25633,7 @@ }, "mfa": { "type": "boolean", - "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise.", + "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise. Hide this attribute by toggling membership privacy in the Console.", "x-example": false }, "roles": { @@ -25796,7 +25798,7 @@ "specification": { "type": "string", "description": "Machine specification for builds and executions.", - "x-example": "s-0.5vcpu-512mb" + "x-example": "s-1vcpu-512mb" } }, "required": [ @@ -26608,7 +26610,7 @@ "slug": { "type": "string", "description": "Size slug.", - "x-example": "s-0.5vcpu-512mb" + "x-example": "s-1vcpu-512mb" } }, "required": [ @@ -27056,7 +27058,7 @@ "name": { "type": "string", "description": "Target Name.", - "x-example": "Aegon apple token" + "x-example": "Apple iPhone 12" }, "userId": { "type": "string", @@ -27078,6 +27080,11 @@ "type": "string", "description": "The target identifier.", "x-example": "token" + }, + "expired": { + "type": "boolean", + "description": "Is the target expired.", + "x-example": false } }, "required": [ @@ -27087,7 +27094,8 @@ "name", "userId", "providerType", - "identifier" + "identifier", + "expired" ] } }, diff --git a/app/config/specs/open-api3-latest-client.json b/app/config/specs/open-api3-latest-client.json index 021ae27c45..d948a101f2 100644 --- a/app/config/specs/open-api3-latest-client.json +++ b/app/config/specs/open-api3-latest-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, @@ -166,7 +166,7 @@ "tags": [ "account" ], - "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n", + "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n", "responses": { "200": { "description": "User", @@ -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, @@ -1570,7 +1570,7 @@ "tags": [ "account" ], - "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.", + "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.", "responses": { "200": { "description": "Token", @@ -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, @@ -1802,7 +1802,7 @@ "tags": [ "account" ], - "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Session", @@ -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, @@ -1954,7 +1954,7 @@ "tags": [ "account" ], - "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", + "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", "responses": { "301": { "description": "File" @@ -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, @@ -2703,7 +2703,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2718,7 +2718,7 @@ }, "x-appwrite": { "method": "createEmailToken", - "weight": 24, + "weight": 25, "cookies": false, "type": "", "deprecated": false, @@ -2784,7 +2784,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", "responses": { "201": { "description": "Token", @@ -2799,7 +2799,7 @@ }, "x-appwrite": { "method": "createMagicURLToken", - "weight": 23, + "weight": 24, "cookies": false, "type": "", "deprecated": false, @@ -2873,7 +2873,7 @@ "tags": [ "account" ], - "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "301": { "description": "File" @@ -2881,7 +2881,7 @@ }, "x-appwrite": { "method": "createOAuth2Token", - "weight": 22, + "weight": 23, "cookies": false, "type": "webAuth", "deprecated": false, @@ -3009,7 +3009,7 @@ "tags": [ "account" ], - "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -3024,7 +3024,7 @@ }, "x-appwrite": { "method": "createPhoneToken", - "weight": 27, + "weight": 28, "cookies": false, "type": "", "deprecated": false, @@ -3088,7 +3088,7 @@ "tags": [ "account" ], - "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n", + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", "responses": { "201": { "description": "Token", @@ -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, @@ -3368,7 +3368,7 @@ "tags": [ "avatars" ], - "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", + "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", "responses": { "200": { "description": "Image" @@ -3376,7 +3376,7 @@ }, "x-appwrite": { "method": "getBrowser", - "weight": 59, + "weight": 60, "cookies": false, "type": "location", "deprecated": false, @@ -3496,7 +3496,7 @@ "tags": [ "avatars" ], - "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", + "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", "responses": { "200": { "description": "Image" @@ -3504,7 +3504,7 @@ }, "x-appwrite": { "method": "getCreditCard", - "weight": 58, + "weight": 59, "cookies": false, "type": "location", "deprecated": false, @@ -3628,7 +3628,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image" @@ -3636,7 +3636,7 @@ }, "x-appwrite": { "method": "getFavicon", - "weight": 62, + "weight": 63, "cookies": false, "type": "location", "deprecated": false, @@ -3688,7 +3688,7 @@ "tags": [ "avatars" ], - "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", + "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", "responses": { "200": { "description": "Image" @@ -3696,7 +3696,7 @@ }, "x-appwrite": { "method": "getFlag", - "weight": 60, + "weight": 61, "cookies": false, "type": "location", "deprecated": false, @@ -4178,7 +4178,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image" @@ -4186,7 +4186,7 @@ }, "x-appwrite": { "method": "getImage", - "weight": 61, + "weight": 62, "cookies": false, "type": "location", "deprecated": false, @@ -4262,7 +4262,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", + "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", "responses": { "200": { "description": "Image" @@ -4270,7 +4270,7 @@ }, "x-appwrite": { "method": "getInitials", - "weight": 64, + "weight": 65, "cookies": false, "type": "location", "deprecated": false, @@ -4356,7 +4356,7 @@ "tags": [ "avatars" ], - "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n", + "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n", "responses": { "200": { "description": "Image" @@ -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": 306, "cookies": false, "type": "", "deprecated": false, @@ -5033,7 +5033,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 303, + "weight": 305, "cookies": false, "type": "", "deprecated": false, @@ -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", @@ -5150,7 +5150,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 305, + "weight": 307, "cookies": false, "type": "", "deprecated": false, @@ -5226,7 +5226,7 @@ }, "x-appwrite": { "method": "query", - "weight": 329, + "weight": 331, "cookies": false, "type": "graphql", "deprecated": false, @@ -5280,7 +5280,7 @@ }, "x-appwrite": { "method": "mutation", - "weight": 328, + "weight": 330, "cookies": false, "type": "graphql", "deprecated": false, @@ -5319,7 +5319,7 @@ "tags": [ "locale" ], - "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", + "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", "responses": { "200": { "description": "Locale", @@ -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": 382, "cookies": false, "type": "", "deprecated": false, @@ -5851,7 +5851,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 384, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -5928,7 +5928,7 @@ }, "x-appwrite": { "method": "listFiles", - "weight": 206, + "weight": 208, "cookies": false, "type": "", "deprecated": false, @@ -6001,7 +6001,7 @@ "tags": [ "storage" ], - "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n", + "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n", "responses": { "201": { "description": "File", @@ -6016,7 +6016,7 @@ }, "x-appwrite": { "method": "createFile", - "weight": 205, + "weight": 207, "cookies": false, "type": "upload", "deprecated": false, @@ -6116,7 +6116,7 @@ }, "x-appwrite": { "method": "getFile", - "weight": 207, + "weight": 209, "cookies": false, "type": "", "deprecated": false, @@ -6190,7 +6190,7 @@ }, "x-appwrite": { "method": "updateFile", - "weight": 212, + "weight": 214, "cookies": false, "type": "", "deprecated": false, @@ -6281,7 +6281,7 @@ }, "x-appwrite": { "method": "deleteFile", - "weight": 213, + "weight": 215, "cookies": false, "type": "", "deprecated": false, @@ -6350,7 +6350,7 @@ }, "x-appwrite": { "method": "getFileDownload", - "weight": 209, + "weight": 211, "cookies": false, "type": "location", "deprecated": false, @@ -6419,7 +6419,7 @@ }, "x-appwrite": { "method": "getFilePreview", - "weight": 208, + "weight": 210, "cookies": false, "type": "location", "deprecated": false, @@ -6637,7 +6637,7 @@ }, "x-appwrite": { "method": "getFileView", - "weight": 210, + "weight": 212, "cookies": false, "type": "location", "deprecated": false, @@ -6713,7 +6713,7 @@ }, "x-appwrite": { "method": "list", - "weight": 217, + "weight": 219, "cookies": false, "type": "", "deprecated": false, @@ -6791,7 +6791,7 @@ }, "x-appwrite": { "method": "create", - "weight": 216, + "weight": 218, "cookies": false, "type": "", "deprecated": false, @@ -6878,7 +6878,7 @@ }, "x-appwrite": { "method": "get", - "weight": 218, + "weight": 220, "cookies": false, "type": "", "deprecated": false, @@ -6942,7 +6942,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 220, + "weight": 222, "cookies": false, "type": "", "deprecated": false, @@ -7018,7 +7018,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 222, + "weight": 224, "cookies": false, "type": "", "deprecated": false, @@ -7069,7 +7069,7 @@ "tags": [ "teams" ], - "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint.", + "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Memberships List", @@ -7084,7 +7084,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 224, + "weight": 226, "cookies": false, "type": "", "deprecated": false, @@ -7157,7 +7157,7 @@ "tags": [ "teams" ], - "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n", + "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n", "responses": { "201": { "description": "Membership", @@ -7172,7 +7172,7 @@ }, "x-appwrite": { "method": "createMembership", - "weight": 223, + "weight": 225, "cookies": false, "type": "", "deprecated": false, @@ -7270,7 +7270,7 @@ "tags": [ "teams" ], - "description": "Get a team member by the membership unique id. All team members have read access for this resource.", + "description": "Get a team member by the membership unique id. All team members have read access for this resource. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Membership", @@ -7285,7 +7285,7 @@ }, "x-appwrite": { "method": "getMembership", - "weight": 225, + "weight": 227, "cookies": false, "type": "", "deprecated": false, @@ -7344,7 +7344,7 @@ "tags": [ "teams" ], - "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n", + "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n", "responses": { "200": { "description": "Membership", @@ -7359,7 +7359,7 @@ }, "x-appwrite": { "method": "updateMembership", - "weight": 226, + "weight": 228, "cookies": false, "type": "", "deprecated": false, @@ -7448,7 +7448,7 @@ }, "x-appwrite": { "method": "deleteMembership", - "weight": 228, + "weight": 230, "cookies": false, "type": "", "deprecated": false, @@ -7509,7 +7509,7 @@ "tags": [ "teams" ], - "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n", + "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n", "responses": { "200": { "description": "Membership", @@ -7524,7 +7524,7 @@ }, "x-appwrite": { "method": "updateMembershipStatus", - "weight": 227, + "weight": 229, "cookies": false, "type": "", "deprecated": false, @@ -7624,7 +7624,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 219, + "weight": 221, "cookies": false, "type": "", "deprecated": false, @@ -7687,7 +7687,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 221, + "weight": 223, "cookies": false, "type": "", "deprecated": false, @@ -9266,12 +9266,12 @@ }, "userName": { "type": "string", - "description": "User name.", + "description": "User name. Hide this attribute by toggling membership privacy in the Console.", "x-example": "John Doe" }, "userEmail": { "type": "string", - "description": "User email address.", + "description": "User email address. Hide this attribute by toggling membership privacy in the Console.", "x-example": "john@appwrite.io" }, "teamId": { @@ -9301,7 +9301,7 @@ }, "mfa": { "type": "boolean", - "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise.", + "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise. Hide this attribute by toggling membership privacy in the Console.", "x-example": false }, "roles": { @@ -9826,7 +9826,7 @@ "name": { "type": "string", "description": "Target Name.", - "x-example": "Aegon apple token" + "x-example": "Apple iPhone 12" }, "userId": { "type": "string", @@ -9848,6 +9848,11 @@ "type": "string", "description": "The target identifier.", "x-example": "token" + }, + "expired": { + "type": "boolean", + "description": "Is the target expired.", + "x-example": false } }, "required": [ @@ -9857,7 +9862,8 @@ "name", "userId", "providerType", - "identifier" + "identifier", + "expired" ] } }, diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index 04e7c76e13..fb01509ecd 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-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, @@ -206,7 +206,7 @@ "tags": [ "account" ], - "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n", + "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n", "responses": { "200": { "description": "User", @@ -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, @@ -1591,7 +1591,7 @@ "tags": [ "account" ], - "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.", + "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.", "responses": { "200": { "description": "Token", @@ -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, @@ -1820,7 +1820,7 @@ "tags": [ "account" ], - "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Session", @@ -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, @@ -1972,7 +1972,7 @@ "tags": [ "account" ], - "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", + "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", "responses": { "301": { "description": "File" @@ -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, @@ -2714,7 +2714,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2729,7 +2729,7 @@ }, "x-appwrite": { "method": "createEmailToken", - "weight": 24, + "weight": 25, "cookies": false, "type": "", "deprecated": false, @@ -2795,7 +2795,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", "responses": { "201": { "description": "Token", @@ -2810,7 +2810,7 @@ }, "x-appwrite": { "method": "createMagicURLToken", - "weight": 23, + "weight": 24, "cookies": false, "type": "", "deprecated": false, @@ -2884,7 +2884,7 @@ "tags": [ "account" ], - "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "301": { "description": "File" @@ -2892,7 +2892,7 @@ }, "x-appwrite": { "method": "createOAuth2Token", - "weight": 22, + "weight": 23, "cookies": false, "type": "webAuth", "deprecated": false, @@ -3020,7 +3020,7 @@ "tags": [ "account" ], - "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -3035,7 +3035,7 @@ }, "x-appwrite": { "method": "createPhoneToken", - "weight": 27, + "weight": 28, "cookies": false, "type": "", "deprecated": false, @@ -3099,7 +3099,7 @@ "tags": [ "account" ], - "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n", + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", "responses": { "201": { "description": "Token", @@ -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, @@ -3375,7 +3375,7 @@ "tags": [ "avatars" ], - "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", + "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", "responses": { "200": { "description": "Image" @@ -3383,7 +3383,7 @@ }, "x-appwrite": { "method": "getBrowser", - "weight": 59, + "weight": 60, "cookies": false, "type": "location", "deprecated": false, @@ -3503,7 +3503,7 @@ "tags": [ "avatars" ], - "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", + "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", "responses": { "200": { "description": "Image" @@ -3511,7 +3511,7 @@ }, "x-appwrite": { "method": "getCreditCard", - "weight": 58, + "weight": 59, "cookies": false, "type": "location", "deprecated": false, @@ -3635,7 +3635,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image" @@ -3643,7 +3643,7 @@ }, "x-appwrite": { "method": "getFavicon", - "weight": 62, + "weight": 63, "cookies": false, "type": "location", "deprecated": false, @@ -3695,7 +3695,7 @@ "tags": [ "avatars" ], - "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", + "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", "responses": { "200": { "description": "Image" @@ -3703,7 +3703,7 @@ }, "x-appwrite": { "method": "getFlag", - "weight": 60, + "weight": 61, "cookies": false, "type": "location", "deprecated": false, @@ -4185,7 +4185,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image" @@ -4193,7 +4193,7 @@ }, "x-appwrite": { "method": "getImage", - "weight": 61, + "weight": 62, "cookies": false, "type": "location", "deprecated": false, @@ -4269,7 +4269,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", + "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", "responses": { "200": { "description": "Image" @@ -4277,7 +4277,7 @@ }, "x-appwrite": { "method": "getInitials", - "weight": 64, + "weight": 65, "cookies": false, "type": "location", "deprecated": false, @@ -4363,7 +4363,7 @@ "tags": [ "avatars" ], - "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n", + "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n", "responses": { "200": { "description": "Image" @@ -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": 333, "cookies": false, "type": "", "deprecated": false, @@ -4534,7 +4534,7 @@ }, "x-appwrite": { "method": "variables", - "weight": 330, + "weight": 332, "cookies": false, "type": "", "deprecated": false, @@ -4584,7 +4584,7 @@ }, "x-appwrite": { "method": "list", - "weight": 69, + "weight": 70, "cookies": false, "type": "", "deprecated": false, @@ -4644,7 +4644,7 @@ "tags": [ "databases" ], - "description": "Create a new Database.\r\n", + "description": "Create a new Database.\n", "responses": { "201": { "description": "Database", @@ -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, @@ -5523,7 +5523,7 @@ "tags": [ "databases" ], - "description": "Create a boolean attribute.\r\n", + "description": "Create a boolean attribute.\n", "responses": { "202": { "description": "AttributeBoolean", @@ -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, @@ -5965,7 +5965,7 @@ "tags": [ "databases" ], - "description": "Create an email attribute.\r\n", + "description": "Create an email attribute.\n", "responses": { "202": { "description": "AttributeEmail", @@ -5980,7 +5980,7 @@ }, "x-appwrite": { "method": "createEmailAttribute", - "weight": 81, + "weight": 82, "cookies": false, "type": "", "deprecated": false, @@ -6073,7 +6073,7 @@ "tags": [ "databases" ], - "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeEmail", @@ -6088,7 +6088,7 @@ }, "x-appwrite": { "method": "updateEmailAttribute", - "weight": 93, + "weight": 94, "cookies": false, "type": "", "deprecated": false, @@ -6186,7 +6186,7 @@ "tags": [ "databases" ], - "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n", + "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n", "responses": { "202": { "description": "AttributeEnum", @@ -6201,7 +6201,7 @@ }, "x-appwrite": { "method": "createEnumAttribute", - "weight": 82, + "weight": 83, "cookies": false, "type": "", "deprecated": false, @@ -6303,7 +6303,7 @@ "tags": [ "databases" ], - "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeEnum", @@ -6318,7 +6318,7 @@ }, "x-appwrite": { "method": "updateEnumAttribute", - "weight": 94, + "weight": 95, "cookies": false, "type": "", "deprecated": false, @@ -6425,7 +6425,7 @@ "tags": [ "databases" ], - "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n", + "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n", "responses": { "202": { "description": "AttributeFloat", @@ -6440,7 +6440,7 @@ }, "x-appwrite": { "method": "createFloatAttribute", - "weight": 86, + "weight": 87, "cookies": false, "type": "", "deprecated": false, @@ -6543,7 +6543,7 @@ "tags": [ "databases" ], - "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeFloat", @@ -6558,7 +6558,7 @@ }, "x-appwrite": { "method": "updateFloatAttribute", - "weight": 98, + "weight": 99, "cookies": false, "type": "", "deprecated": false, @@ -6668,7 +6668,7 @@ "tags": [ "databases" ], - "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n", + "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n", "responses": { "202": { "description": "AttributeInteger", @@ -6683,7 +6683,7 @@ }, "x-appwrite": { "method": "createIntegerAttribute", - "weight": 85, + "weight": 86, "cookies": false, "type": "", "deprecated": false, @@ -6786,7 +6786,7 @@ "tags": [ "databases" ], - "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeInteger", @@ -6801,7 +6801,7 @@ }, "x-appwrite": { "method": "updateIntegerAttribute", - "weight": 97, + "weight": 98, "cookies": false, "type": "", "deprecated": false, @@ -6911,7 +6911,7 @@ "tags": [ "databases" ], - "description": "Create IP address attribute.\r\n", + "description": "Create IP address attribute.\n", "responses": { "202": { "description": "AttributeIP", @@ -6926,7 +6926,7 @@ }, "x-appwrite": { "method": "createIpAttribute", - "weight": 83, + "weight": 84, "cookies": false, "type": "", "deprecated": false, @@ -7019,7 +7019,7 @@ "tags": [ "databases" ], - "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeIP", @@ -7034,7 +7034,7 @@ }, "x-appwrite": { "method": "updateIpAttribute", - "weight": 95, + "weight": 96, "cookies": false, "type": "", "deprecated": false, @@ -7132,7 +7132,7 @@ "tags": [ "databases" ], - "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", + "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", "responses": { "202": { "description": "AttributeRelationship", @@ -7147,7 +7147,7 @@ }, "x-appwrite": { "method": "createRelationshipAttribute", - "weight": 89, + "weight": 90, "cookies": false, "type": "", "deprecated": false, @@ -7265,7 +7265,7 @@ "tags": [ "databases" ], - "description": "Create a string attribute.\r\n", + "description": "Create a string attribute.\n", "responses": { "202": { "description": "AttributeString", @@ -7280,7 +7280,7 @@ }, "x-appwrite": { "method": "createStringAttribute", - "weight": 80, + "weight": 81, "cookies": false, "type": "", "deprecated": false, @@ -7384,7 +7384,7 @@ "tags": [ "databases" ], - "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeString", @@ -7399,7 +7399,7 @@ }, "x-appwrite": { "method": "updateStringAttribute", - "weight": 92, + "weight": 93, "cookies": false, "type": "", "deprecated": false, @@ -7477,7 +7477,7 @@ "size": { "type": "integer", "description": "Maximum size of the string attribute.", - "x-example": null + "x-example": 1 }, "newKey": { "type": "string", @@ -7502,7 +7502,7 @@ "tags": [ "databases" ], - "description": "Create a URL attribute.\r\n", + "description": "Create a URL attribute.\n", "responses": { "202": { "description": "AttributeURL", @@ -7517,7 +7517,7 @@ }, "x-appwrite": { "method": "createUrlAttribute", - "weight": 84, + "weight": 85, "cookies": false, "type": "", "deprecated": false, @@ -7610,7 +7610,7 @@ "tags": [ "databases" ], - "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeURL", @@ -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, @@ -7909,7 +7909,7 @@ "tags": [ "databases" ], - "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", + "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", "responses": { "200": { "description": "AttributeRelationship", @@ -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, @@ -8678,7 +8678,7 @@ "tags": [ "databases" ], - "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.", + "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.", "responses": { "202": { "description": "Index", @@ -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": 289, "cookies": false, "type": "", "deprecated": false, @@ -9383,7 +9383,7 @@ }, "x-appwrite": { "method": "create", - "weight": 286, + "weight": 288, "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,24 +9465,30 @@ "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", - "go-1.23" + "bun-1.1", + "go-1.23", + "static-1" ], "x-enum-name": null, "x-enum-keys": [] @@ -9622,7 +9631,7 @@ }, "x-appwrite": { "method": "listRuntimes", - "weight": 288, + "weight": 290, "cookies": false, "type": "", "deprecated": false, @@ -9658,7 +9667,7 @@ "tags": [ "functions" ], - "description": "List allowed function specifications for this instance.\r\n", + "description": "List allowed function specifications for this instance.\n", "responses": { "200": { "description": "Specifications List", @@ -9673,7 +9682,7 @@ }, "x-appwrite": { "method": "listSpecifications", - "weight": 289, + "weight": 291, "cookies": false, "type": "", "deprecated": false, @@ -9725,7 +9734,7 @@ }, "x-appwrite": { "method": "listTemplates", - "weight": 312, + "weight": 314, "cookies": false, "type": "", "deprecated": false, @@ -9827,7 +9836,7 @@ }, "x-appwrite": { "method": "getTemplate", - "weight": 313, + "weight": 315, "cookies": false, "type": "", "deprecated": false, @@ -9889,7 +9898,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 292, + "weight": 294, "cookies": false, "type": "", "deprecated": false, @@ -9963,7 +9972,7 @@ }, "x-appwrite": { "method": "get", - "weight": 290, + "weight": 292, "cookies": false, "type": "", "deprecated": false, @@ -10024,7 +10033,7 @@ }, "x-appwrite": { "method": "update", - "weight": 293, + "weight": 295, "cookies": false, "type": "", "deprecated": false, @@ -10085,6 +10094,7 @@ "node-19.0", "node-20.0", "node-21.0", + "node-22", "php-8.0", "php-8.1", "php-8.2", @@ -10103,6 +10113,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,24 +10122,30 @@ "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", - "go-1.23" + "bun-1.1", + "go-1.23", + "static-1" ], "x-enum-name": null, "x-enum-keys": [] @@ -10240,7 +10258,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 296, + "weight": 298, "cookies": false, "type": "", "deprecated": false, @@ -10303,7 +10321,7 @@ }, "x-appwrite": { "method": "listDeployments", - "weight": 298, + "weight": 300, "cookies": false, "type": "", "deprecated": false, @@ -10373,7 +10391,7 @@ "tags": [ "functions" ], - "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\r\n\r\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.", + "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\n\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.", "responses": { "202": { "description": "Deployment", @@ -10388,7 +10406,7 @@ }, "x-appwrite": { "method": "createDeployment", - "weight": 297, + "weight": 299, "cookies": false, "type": "upload", "deprecated": false, @@ -10486,7 +10504,7 @@ }, "x-appwrite": { "method": "getDeployment", - "weight": 299, + "weight": 301, "cookies": false, "type": "", "deprecated": false, @@ -10557,7 +10575,7 @@ }, "x-appwrite": { "method": "updateDeployment", - "weight": 295, + "weight": 297, "cookies": false, "type": "", "deprecated": false, @@ -10621,7 +10639,7 @@ }, "x-appwrite": { "method": "deleteDeployment", - "weight": 300, + "weight": 302, "cookies": false, "type": "", "deprecated": false, @@ -10687,7 +10705,7 @@ }, "x-appwrite": { "method": "createBuild", - "weight": 301, + "weight": 303, "cookies": false, "type": "", "deprecated": false, @@ -10774,7 +10792,7 @@ }, "x-appwrite": { "method": "updateDeploymentBuild", - "weight": 302, + "weight": 304, "cookies": false, "type": "", "deprecated": false, @@ -10840,7 +10858,7 @@ }, "x-appwrite": { "method": "getDeploymentDownload", - "weight": 294, + "weight": 296, "cookies": false, "type": "location", "deprecated": false, @@ -10915,7 +10933,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 304, + "weight": 306, "cookies": false, "type": "", "deprecated": false, @@ -11003,7 +11021,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 303, + "weight": 305, "cookies": false, "type": "", "deprecated": false, @@ -11054,7 +11072,7 @@ "body": { "type": "string", "description": "HTTP body of execution. Default value is empty string.", - "x-example": null + "x-example": "" }, "async": { "type": "boolean", @@ -11120,7 +11138,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 305, + "weight": 307, "cookies": false, "type": "", "deprecated": false, @@ -11179,7 +11197,7 @@ "tags": [ "functions" ], - "description": "Delete a function execution by its unique ID.\r\n", + "description": "Delete a function execution by its unique ID.\n", "responses": { "204": { "description": "No content" @@ -11187,7 +11205,7 @@ }, "x-appwrite": { "method": "deleteExecution", - "weight": 306, + "weight": 308, "cookies": false, "type": "", "deprecated": false, @@ -11260,7 +11278,7 @@ }, "x-appwrite": { "method": "getFunctionUsage", - "weight": 291, + "weight": 293, "cookies": false, "type": "", "deprecated": false, @@ -11344,7 +11362,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 308, + "weight": 310, "cookies": false, "type": "", "deprecated": false, @@ -11405,7 +11423,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 307, + "weight": 309, "cookies": false, "type": "", "deprecated": false, @@ -11493,7 +11511,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 309, + "weight": 311, "cookies": false, "type": "", "deprecated": false, @@ -11564,7 +11582,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 310, + "weight": 312, "cookies": false, "type": "", "deprecated": false, @@ -11652,7 +11670,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 311, + "weight": 313, "cookies": false, "type": "", "deprecated": false, @@ -11725,7 +11743,7 @@ }, "x-appwrite": { "method": "query", - "weight": 329, + "weight": 331, "cookies": false, "type": "graphql", "deprecated": false, @@ -11779,7 +11797,7 @@ }, "x-appwrite": { "method": "mutation", - "weight": 328, + "weight": 330, "cookies": false, "type": "graphql", "deprecated": false, @@ -11833,7 +11851,7 @@ }, "x-appwrite": { "method": "get", - "weight": 124, + "weight": 125, "cookies": false, "type": "", "deprecated": false, @@ -11884,7 +11902,7 @@ }, "x-appwrite": { "method": "getAntivirus", - "weight": 146, + "weight": 147, "cookies": false, "type": "", "deprecated": false, @@ -11935,7 +11953,7 @@ }, "x-appwrite": { "method": "getCache", - "weight": 127, + "weight": 128, "cookies": false, "type": "", "deprecated": false, @@ -11986,7 +12004,7 @@ }, "x-appwrite": { "method": "getCertificate", - "weight": 133, + "weight": 134, "cookies": false, "type": "", "deprecated": false, @@ -12048,7 +12066,7 @@ }, "x-appwrite": { "method": "getDB", - "weight": 126, + "weight": 127, "cookies": false, "type": "", "deprecated": false, @@ -12099,7 +12117,7 @@ }, "x-appwrite": { "method": "getPubSub", - "weight": 129, + "weight": 130, "cookies": false, "type": "", "deprecated": false, @@ -12150,7 +12168,7 @@ }, "x-appwrite": { "method": "getQueue", - "weight": 128, + "weight": 129, "cookies": false, "type": "", "deprecated": false, @@ -12201,7 +12219,7 @@ }, "x-appwrite": { "method": "getQueueBuilds", - "weight": 135, + "weight": 136, "cookies": false, "type": "", "deprecated": false, @@ -12265,7 +12283,7 @@ }, "x-appwrite": { "method": "getQueueCertificates", - "weight": 134, + "weight": 135, "cookies": false, "type": "", "deprecated": false, @@ -12329,7 +12347,7 @@ }, "x-appwrite": { "method": "getQueueDatabases", - "weight": 136, + "weight": 137, "cookies": false, "type": "", "deprecated": false, @@ -12404,7 +12422,7 @@ }, "x-appwrite": { "method": "getQueueDeletes", - "weight": 137, + "weight": 138, "cookies": false, "type": "", "deprecated": false, @@ -12453,7 +12471,7 @@ "tags": [ "health" ], - "description": "Returns the amount of failed jobs in a given queue.\r\n", + "description": "Returns the amount of failed jobs in a given queue.\n", "responses": { "200": { "description": "Health Queue", @@ -12468,7 +12486,7 @@ }, "x-appwrite": { "method": "getFailedJobs", - "weight": 147, + "weight": 148, "cookies": false, "type": "", "deprecated": false, @@ -12558,7 +12576,7 @@ }, "x-appwrite": { "method": "getQueueFunctions", - "weight": 141, + "weight": 142, "cookies": false, "type": "", "deprecated": false, @@ -12622,7 +12640,7 @@ }, "x-appwrite": { "method": "getQueueLogs", - "weight": 132, + "weight": 133, "cookies": false, "type": "", "deprecated": false, @@ -12686,7 +12704,7 @@ }, "x-appwrite": { "method": "getQueueMails", - "weight": 138, + "weight": 139, "cookies": false, "type": "", "deprecated": false, @@ -12750,7 +12768,7 @@ }, "x-appwrite": { "method": "getQueueMessaging", - "weight": 139, + "weight": 140, "cookies": false, "type": "", "deprecated": false, @@ -12814,7 +12832,7 @@ }, "x-appwrite": { "method": "getQueueMigrations", - "weight": 140, + "weight": 141, "cookies": false, "type": "", "deprecated": false, @@ -12878,7 +12896,7 @@ }, "x-appwrite": { "method": "getQueueUsage", - "weight": 142, + "weight": 143, "cookies": false, "type": "", "deprecated": false, @@ -12942,7 +12960,7 @@ }, "x-appwrite": { "method": "getQueueUsageDump", - "weight": 143, + "weight": 144, "cookies": false, "type": "", "deprecated": false, @@ -13006,7 +13024,7 @@ }, "x-appwrite": { "method": "getQueueWebhooks", - "weight": 131, + "weight": 132, "cookies": false, "type": "", "deprecated": false, @@ -13070,7 +13088,7 @@ }, "x-appwrite": { "method": "getStorage", - "weight": 145, + "weight": 146, "cookies": false, "type": "", "deprecated": false, @@ -13121,7 +13139,7 @@ }, "x-appwrite": { "method": "getStorageLocal", - "weight": 144, + "weight": 145, "cookies": false, "type": "", "deprecated": false, @@ -13172,7 +13190,7 @@ }, "x-appwrite": { "method": "getTime", - "weight": 130, + "weight": 131, "cookies": false, "type": "", "deprecated": false, @@ -13208,7 +13226,7 @@ "tags": [ "locale" ], - "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", + "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", "responses": { "200": { "description": "Locale", @@ -13223,7 +13241,7 @@ }, "x-appwrite": { "method": "get", - "weight": 116, + "weight": 117, "cookies": false, "type": "", "deprecated": false, @@ -13277,7 +13295,7 @@ }, "x-appwrite": { "method": "listCodes", - "weight": 117, + "weight": 118, "cookies": false, "type": "", "deprecated": false, @@ -13331,7 +13349,7 @@ }, "x-appwrite": { "method": "listContinents", - "weight": 121, + "weight": 122, "cookies": false, "type": "", "deprecated": false, @@ -13385,7 +13403,7 @@ }, "x-appwrite": { "method": "listCountries", - "weight": 118, + "weight": 119, "cookies": false, "type": "", "deprecated": false, @@ -13439,7 +13457,7 @@ }, "x-appwrite": { "method": "listCountriesEU", - "weight": 119, + "weight": 120, "cookies": false, "type": "", "deprecated": false, @@ -13493,7 +13511,7 @@ }, "x-appwrite": { "method": "listCountriesPhones", - "weight": 120, + "weight": 121, "cookies": false, "type": "", "deprecated": false, @@ -13547,7 +13565,7 @@ }, "x-appwrite": { "method": "listCurrencies", - "weight": 122, + "weight": 123, "cookies": false, "type": "", "deprecated": false, @@ -13601,7 +13619,7 @@ }, "x-appwrite": { "method": "listLanguages", - "weight": 123, + "weight": 124, "cookies": false, "type": "", "deprecated": false, @@ -13655,7 +13673,7 @@ }, "x-appwrite": { "method": "listMessages", - "weight": 388, + "weight": 390, "cookies": false, "type": "", "deprecated": false, @@ -13733,7 +13751,7 @@ }, "x-appwrite": { "method": "createEmail", - "weight": 385, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -13864,7 +13882,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\r\n", + "description": "Update an email message by its unique ID.\n", "responses": { "200": { "description": "Message", @@ -13879,7 +13897,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 392, + "weight": 394, "cookies": false, "type": "", "deprecated": false, @@ -14027,7 +14045,7 @@ }, "x-appwrite": { "method": "createPush", - "weight": 387, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -14169,7 +14187,7 @@ "tags": [ "messaging" ], - "description": "Update a push notification by its unique ID.\r\n", + "description": "Update a push notification by its unique ID.\n", "responses": { "200": { "description": "Message", @@ -14184,7 +14202,7 @@ }, "x-appwrite": { "method": "updatePush", - "weight": 394, + "weight": 396, "cookies": false, "type": "", "deprecated": false, @@ -14343,7 +14361,7 @@ }, "x-appwrite": { "method": "createSms", - "weight": 386, + "weight": 388, "cookies": false, "type": "", "deprecated": false, @@ -14439,7 +14457,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\r\n", + "description": "Update an email message by its unique ID.\n", "responses": { "200": { "description": "Message", @@ -14454,7 +14472,7 @@ }, "x-appwrite": { "method": "updateSms", - "weight": 393, + "weight": 395, "cookies": false, "type": "", "deprecated": false, @@ -14553,7 +14571,7 @@ "tags": [ "messaging" ], - "description": "Get a message by its unique ID.\r\n", + "description": "Get a message by its unique ID.\n", "responses": { "200": { "description": "Message", @@ -14568,7 +14586,7 @@ }, "x-appwrite": { "method": "getMessage", - "weight": 391, + "weight": 393, "cookies": false, "type": "", "deprecated": false, @@ -14623,7 +14641,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 395, + "weight": 397, "cookies": false, "type": "", "deprecated": false, @@ -14687,7 +14705,7 @@ }, "x-appwrite": { "method": "listMessageLogs", - "weight": 389, + "weight": 391, "cookies": false, "type": "", "deprecated": false, @@ -14764,7 +14782,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 390, + "weight": 392, "cookies": false, "type": "", "deprecated": false, @@ -14841,7 +14859,7 @@ }, "x-appwrite": { "method": "listProviders", - "weight": 360, + "weight": 362, "cookies": false, "type": "", "deprecated": false, @@ -14919,7 +14937,7 @@ }, "x-appwrite": { "method": "createApnsProvider", - "weight": 359, + "weight": 361, "cookies": false, "type": "", "deprecated": false, @@ -15026,7 +15044,7 @@ }, "x-appwrite": { "method": "updateApnsProvider", - "weight": 372, + "weight": 374, "cookies": false, "type": "", "deprecated": false, @@ -15136,7 +15154,7 @@ }, "x-appwrite": { "method": "createFcmProvider", - "weight": 358, + "weight": 360, "cookies": false, "type": "", "deprecated": false, @@ -15223,7 +15241,7 @@ }, "x-appwrite": { "method": "updateFcmProvider", - "weight": 371, + "weight": 373, "cookies": false, "type": "", "deprecated": false, @@ -15313,7 +15331,7 @@ }, "x-appwrite": { "method": "createMailgunProvider", - "weight": 350, + "weight": 352, "cookies": false, "type": "", "deprecated": false, @@ -15430,7 +15448,7 @@ }, "x-appwrite": { "method": "updateMailgunProvider", - "weight": 363, + "weight": 365, "cookies": false, "type": "", "deprecated": false, @@ -15550,7 +15568,7 @@ }, "x-appwrite": { "method": "createMsg91Provider", - "weight": 353, + "weight": 355, "cookies": false, "type": "", "deprecated": false, @@ -15647,7 +15665,7 @@ }, "x-appwrite": { "method": "updateMsg91Provider", - "weight": 366, + "weight": 368, "cookies": false, "type": "", "deprecated": false, @@ -15747,7 +15765,7 @@ }, "x-appwrite": { "method": "createSendgridProvider", - "weight": 351, + "weight": 353, "cookies": false, "type": "", "deprecated": false, @@ -15854,7 +15872,7 @@ }, "x-appwrite": { "method": "updateSendgridProvider", - "weight": 364, + "weight": 366, "cookies": false, "type": "", "deprecated": false, @@ -15964,7 +15982,7 @@ }, "x-appwrite": { "method": "createSmtpProvider", - "weight": 352, + "weight": 354, "cookies": false, "type": "", "deprecated": false, @@ -16109,7 +16127,7 @@ }, "x-appwrite": { "method": "updateSmtpProvider", - "weight": 365, + "weight": 367, "cookies": false, "type": "", "deprecated": false, @@ -16256,7 +16274,7 @@ }, "x-appwrite": { "method": "createTelesignProvider", - "weight": 354, + "weight": 356, "cookies": false, "type": "", "deprecated": false, @@ -16353,7 +16371,7 @@ }, "x-appwrite": { "method": "updateTelesignProvider", - "weight": 367, + "weight": 369, "cookies": false, "type": "", "deprecated": false, @@ -16453,7 +16471,7 @@ }, "x-appwrite": { "method": "createTextmagicProvider", - "weight": 355, + "weight": 357, "cookies": false, "type": "", "deprecated": false, @@ -16550,7 +16568,7 @@ }, "x-appwrite": { "method": "updateTextmagicProvider", - "weight": 368, + "weight": 370, "cookies": false, "type": "", "deprecated": false, @@ -16650,7 +16668,7 @@ }, "x-appwrite": { "method": "createTwilioProvider", - "weight": 356, + "weight": 358, "cookies": false, "type": "", "deprecated": false, @@ -16747,7 +16765,7 @@ }, "x-appwrite": { "method": "updateTwilioProvider", - "weight": 369, + "weight": 371, "cookies": false, "type": "", "deprecated": false, @@ -16847,7 +16865,7 @@ }, "x-appwrite": { "method": "createVonageProvider", - "weight": 357, + "weight": 359, "cookies": false, "type": "", "deprecated": false, @@ -16944,7 +16962,7 @@ }, "x-appwrite": { "method": "updateVonageProvider", - "weight": 370, + "weight": 372, "cookies": false, "type": "", "deprecated": false, @@ -17029,7 +17047,7 @@ "tags": [ "messaging" ], - "description": "Get a provider by its unique ID.\r\n", + "description": "Get a provider by its unique ID.\n", "responses": { "200": { "description": "Provider", @@ -17044,7 +17062,7 @@ }, "x-appwrite": { "method": "getProvider", - "weight": 362, + "weight": 364, "cookies": false, "type": "", "deprecated": false, @@ -17099,7 +17117,7 @@ }, "x-appwrite": { "method": "deleteProvider", - "weight": 373, + "weight": 375, "cookies": false, "type": "", "deprecated": false, @@ -17163,7 +17181,7 @@ }, "x-appwrite": { "method": "listProviderLogs", - "weight": 361, + "weight": 363, "cookies": false, "type": "", "deprecated": false, @@ -17240,7 +17258,7 @@ }, "x-appwrite": { "method": "listSubscriberLogs", - "weight": 382, + "weight": 384, "cookies": false, "type": "", "deprecated": false, @@ -17317,7 +17335,7 @@ }, "x-appwrite": { "method": "listTopics", - "weight": 375, + "weight": 377, "cookies": false, "type": "", "deprecated": false, @@ -17393,7 +17411,7 @@ }, "x-appwrite": { "method": "createTopic", - "weight": 374, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -17463,7 +17481,7 @@ "tags": [ "messaging" ], - "description": "Get a topic by its unique ID.\r\n", + "description": "Get a topic by its unique ID.\n", "responses": { "200": { "description": "Topic", @@ -17478,7 +17496,7 @@ }, "x-appwrite": { "method": "getTopic", - "weight": 377, + "weight": 379, "cookies": false, "type": "", "deprecated": false, @@ -17525,7 +17543,7 @@ "tags": [ "messaging" ], - "description": "Update a topic by its unique ID.\r\n", + "description": "Update a topic by its unique ID.\n", "responses": { "200": { "description": "Topic", @@ -17540,7 +17558,7 @@ }, "x-appwrite": { "method": "updateTopic", - "weight": 378, + "weight": 380, "cookies": false, "type": "", "deprecated": false, @@ -17619,7 +17637,7 @@ }, "x-appwrite": { "method": "deleteTopic", - "weight": 379, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -17683,7 +17701,7 @@ }, "x-appwrite": { "method": "listTopicLogs", - "weight": 376, + "weight": 378, "cookies": false, "type": "", "deprecated": false, @@ -17760,7 +17778,7 @@ }, "x-appwrite": { "method": "listSubscribers", - "weight": 381, + "weight": 383, "cookies": false, "type": "", "deprecated": false, @@ -17846,7 +17864,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 380, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -17923,7 +17941,7 @@ "tags": [ "messaging" ], - "description": "Get a subscriber by its unique ID.\r\n", + "description": "Get a subscriber by its unique ID.\n", "responses": { "200": { "description": "Subscriber", @@ -17938,7 +17956,7 @@ }, "x-appwrite": { "method": "getSubscriber", - "weight": 383, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -18003,7 +18021,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 384, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -18080,7 +18098,7 @@ }, "x-appwrite": { "method": "list", - "weight": 337, + "weight": 339, "cookies": false, "type": "", "deprecated": false, @@ -18109,7 +18127,7 @@ "parameters": [ { "name": "queries", - "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/databases#querying-documents). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: status, stage, source, resources, statusCounters, resourceData, errors", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/databases#querying-documents). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: status, stage, source, destination, resources, statusCounters, resourceData, errors", "required": false, "schema": { "type": "array", @@ -18156,7 +18174,7 @@ }, "x-appwrite": { "method": "createAppwriteMigration", - "weight": 332, + "weight": 334, "cookies": false, "type": "", "deprecated": false, @@ -18246,7 +18264,7 @@ }, "x-appwrite": { "method": "getAppwriteReport", - "weight": 339, + "weight": 341, "cookies": false, "type": "", "deprecated": false, @@ -18341,7 +18359,7 @@ }, "x-appwrite": { "method": "createFirebaseMigration", - "weight": 334, + "weight": 336, "cookies": false, "type": "", "deprecated": false, @@ -18412,7 +18430,7 @@ }, "x-appwrite": { "method": "deleteFirebaseAuth", - "weight": 345, + "weight": 347, "cookies": false, "type": "", "deprecated": false, @@ -18462,7 +18480,7 @@ }, "x-appwrite": { "method": "createFirebaseOAuthMigration", - "weight": 333, + "weight": 335, "cookies": false, "type": "", "deprecated": false, @@ -18540,7 +18558,7 @@ }, "x-appwrite": { "method": "listFirebaseProjects", - "weight": 344, + "weight": 346, "cookies": false, "type": "", "deprecated": false, @@ -18590,7 +18608,7 @@ }, "x-appwrite": { "method": "getFirebaseReport", - "weight": 340, + "weight": 342, "cookies": false, "type": "", "deprecated": false, @@ -18664,7 +18682,7 @@ }, "x-appwrite": { "method": "getFirebaseReportOAuth", - "weight": 341, + "weight": 343, "cookies": false, "type": "", "deprecated": false, @@ -18738,7 +18756,7 @@ }, "x-appwrite": { "method": "createNHostMigration", - "weight": 336, + "weight": 338, "cookies": false, "type": "", "deprecated": false, @@ -18851,7 +18869,7 @@ }, "x-appwrite": { "method": "getNHostReport", - "weight": 347, + "weight": 349, "cookies": false, "type": "", "deprecated": false, @@ -18986,7 +19004,7 @@ }, "x-appwrite": { "method": "createSupabaseMigration", - "weight": 335, + "weight": 337, "cookies": false, "type": "", "deprecated": false, @@ -19093,7 +19111,7 @@ }, "x-appwrite": { "method": "getSupabaseReport", - "weight": 346, + "weight": 348, "cookies": false, "type": "", "deprecated": false, @@ -19219,7 +19237,7 @@ }, "x-appwrite": { "method": "get", - "weight": 338, + "weight": 340, "cookies": false, "type": "", "deprecated": false, @@ -19279,7 +19297,7 @@ }, "x-appwrite": { "method": "retry", - "weight": 348, + "weight": 350, "cookies": false, "type": "", "deprecated": false, @@ -19332,7 +19350,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 349, + "weight": 351, "cookies": false, "type": "", "deprecated": false, @@ -19394,7 +19412,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 194, + "weight": 196, "cookies": false, "type": "", "deprecated": false, @@ -19484,7 +19502,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 196, + "weight": 198, "cookies": false, "type": "", "deprecated": false, @@ -19532,7 +19550,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 195, + "weight": 197, "cookies": false, "type": "", "deprecated": false, @@ -19607,7 +19625,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 197, + "weight": 199, "cookies": false, "type": "", "deprecated": false, @@ -19667,7 +19685,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 198, + "weight": 200, "cookies": false, "type": "", "deprecated": false, @@ -19744,7 +19762,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 199, + "weight": 201, "cookies": false, "type": "", "deprecated": false, @@ -19806,7 +19824,7 @@ }, "x-appwrite": { "method": "list", - "weight": 150, + "weight": 151, "cookies": false, "type": "", "deprecated": false, @@ -19880,7 +19898,7 @@ }, "x-appwrite": { "method": "create", - "weight": 149, + "weight": 150, "cookies": false, "type": "", "deprecated": false, @@ -20017,7 +20035,7 @@ }, "x-appwrite": { "method": "get", - "weight": 151, + "weight": 152, "cookies": false, "type": "", "deprecated": false, @@ -20077,7 +20095,7 @@ }, "x-appwrite": { "method": "update", - "weight": 152, + "weight": 153, "cookies": false, "type": "", "deprecated": false, @@ -20194,7 +20212,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 168, + "weight": 170, "cookies": false, "type": "", "deprecated": false, @@ -20256,7 +20274,7 @@ }, "x-appwrite": { "method": "updateApiStatus", - "weight": 156, + "weight": 157, "cookies": false, "type": "", "deprecated": false, @@ -20350,7 +20368,7 @@ }, "x-appwrite": { "method": "updateApiStatusAll", - "weight": 157, + "weight": 158, "cookies": false, "type": "", "deprecated": false, @@ -20431,7 +20449,7 @@ }, "x-appwrite": { "method": "updateAuthDuration", - "weight": 161, + "weight": 163, "cookies": false, "type": "", "deprecated": false, @@ -20512,7 +20530,7 @@ }, "x-appwrite": { "method": "updateAuthLimit", - "weight": 160, + "weight": 162, "cookies": false, "type": "", "deprecated": false, @@ -20593,7 +20611,7 @@ }, "x-appwrite": { "method": "updateAuthSessionsLimit", - "weight": 166, + "weight": 168, "cookies": false, "type": "", "deprecated": false, @@ -20652,6 +20670,99 @@ } } }, + "\/projects\/{projectId}\/auth\/memberships-privacy": { + "patch": { + "summary": "Update project memberships privacy attributes", + "operationId": "projectsUpdateMembershipsPrivacy", + "tags": [ + "projects" + ], + "description": "", + "responses": { + "200": { + "description": "Project", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/project" + } + } + } + } + }, + "x-appwrite": { + "method": "updateMembershipsPrivacy", + "weight": 161, + "cookies": false, + "type": "", + "deprecated": false, + "demo": "projects\/update-memberships-privacy.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "projects.write", + "platforms": [ + "console" + ], + "packaging": false, + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [] + } + ], + "parameters": [ + { + "name": "projectId", + "description": "Project unique ID.", + "required": true, + "schema": { + "type": "string", + "x-example": "" + }, + "in": "path" + } + ], + "requestBody": { + "content": { + "application\/json": { + "schema": { + "type": "object", + "properties": { + "userName": { + "type": "boolean", + "description": "Set to true to show userName to members of a team.", + "x-example": false + }, + "userEmail": { + "type": "boolean", + "description": "Set to true to show email to members of a team.", + "x-example": false + }, + "mfa": { + "type": "boolean", + "description": "Set to true to show mfa to members of a team.", + "x-example": false + } + }, + "required": [ + "userName", + "userEmail", + "mfa" + ] + } + } + } + } + } + }, "\/projects\/{projectId}\/auth\/mock-numbers": { "patch": { "summary": "Update the mock numbers for the project", @@ -20674,7 +20785,7 @@ }, "x-appwrite": { "method": "updateMockNumbers", - "weight": 167, + "weight": 169, "cookies": false, "type": "", "deprecated": false, @@ -20758,7 +20869,7 @@ }, "x-appwrite": { "method": "updateAuthPasswordDictionary", - "weight": 164, + "weight": 166, "cookies": false, "type": "", "deprecated": false, @@ -20839,7 +20950,7 @@ }, "x-appwrite": { "method": "updateAuthPasswordHistory", - "weight": 163, + "weight": 165, "cookies": false, "type": "", "deprecated": false, @@ -20920,7 +21031,7 @@ }, "x-appwrite": { "method": "updatePersonalDataCheck", - "weight": 165, + "weight": 167, "cookies": false, "type": "", "deprecated": false, @@ -21001,7 +21112,7 @@ }, "x-appwrite": { "method": "updateSessionAlerts", - "weight": 159, + "weight": 160, "cookies": false, "type": "", "deprecated": false, @@ -21082,7 +21193,7 @@ }, "x-appwrite": { "method": "updateAuthStatus", - "weight": 162, + "weight": 164, "cookies": false, "type": "", "deprecated": false, @@ -21184,7 +21295,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 180, + "weight": 182, "cookies": false, "type": "", "deprecated": false, @@ -21273,7 +21384,7 @@ }, "x-appwrite": { "method": "listKeys", - "weight": 176, + "weight": 178, "cookies": false, "type": "", "deprecated": false, @@ -21333,7 +21444,7 @@ }, "x-appwrite": { "method": "createKey", - "weight": 175, + "weight": 177, "cookies": false, "type": "", "deprecated": false, @@ -21428,7 +21539,7 @@ }, "x-appwrite": { "method": "getKey", - "weight": 177, + "weight": 179, "cookies": false, "type": "", "deprecated": false, @@ -21498,7 +21609,7 @@ }, "x-appwrite": { "method": "updateKey", - "weight": 178, + "weight": 180, "cookies": false, "type": "", "deprecated": false, @@ -21594,7 +21705,7 @@ }, "x-appwrite": { "method": "deleteKey", - "weight": 179, + "weight": 181, "cookies": false, "type": "", "deprecated": false, @@ -21666,7 +21777,7 @@ }, "x-appwrite": { "method": "updateOAuth2", - "weight": 158, + "weight": 159, "cookies": false, "type": "", "deprecated": false, @@ -21805,7 +21916,7 @@ }, "x-appwrite": { "method": "listPlatforms", - "weight": 182, + "weight": 184, "cookies": false, "type": "", "deprecated": false, @@ -21865,7 +21976,7 @@ }, "x-appwrite": { "method": "createPlatform", - "weight": 181, + "weight": 183, "cookies": false, "type": "", "deprecated": false, @@ -21986,7 +22097,7 @@ }, "x-appwrite": { "method": "getPlatform", - "weight": 183, + "weight": 185, "cookies": false, "type": "", "deprecated": false, @@ -22056,7 +22167,7 @@ }, "x-appwrite": { "method": "updatePlatform", - "weight": 184, + "weight": 186, "cookies": false, "type": "", "deprecated": false, @@ -22153,7 +22264,7 @@ }, "x-appwrite": { "method": "deletePlatform", - "weight": 185, + "weight": 187, "cookies": false, "type": "", "deprecated": false, @@ -22225,7 +22336,7 @@ }, "x-appwrite": { "method": "updateServiceStatus", - "weight": 154, + "weight": 155, "cookies": false, "type": "", "deprecated": false, @@ -22327,7 +22438,7 @@ }, "x-appwrite": { "method": "updateServiceStatusAll", - "weight": 155, + "weight": 156, "cookies": false, "type": "", "deprecated": false, @@ -22408,7 +22519,7 @@ }, "x-appwrite": { "method": "updateSmtp", - "weight": 186, + "weight": 188, "cookies": false, "type": "", "deprecated": false, @@ -22528,7 +22639,7 @@ }, "x-appwrite": { "method": "createSmtpTest", - "weight": 187, + "weight": 189, "cookies": false, "type": "", "deprecated": false, @@ -22661,7 +22772,7 @@ }, "x-appwrite": { "method": "updateTeam", - "weight": 153, + "weight": 154, "cookies": false, "type": "", "deprecated": false, @@ -22742,7 +22853,7 @@ }, "x-appwrite": { "method": "getEmailTemplate", - "weight": 189, + "weight": 191, "cookies": false, "type": "", "deprecated": false, @@ -22968,7 +23079,7 @@ }, "x-appwrite": { "method": "updateEmailTemplate", - "weight": 191, + "weight": 193, "cookies": false, "type": "", "deprecated": false, @@ -23234,7 +23345,7 @@ }, "x-appwrite": { "method": "deleteEmailTemplate", - "weight": 193, + "weight": 195, "cookies": false, "type": "", "deprecated": false, @@ -23462,7 +23573,7 @@ }, "x-appwrite": { "method": "getSmsTemplate", - "weight": 188, + "weight": 190, "cookies": false, "type": "", "deprecated": false, @@ -23685,7 +23796,7 @@ }, "x-appwrite": { "method": "updateSmsTemplate", - "weight": 190, + "weight": 192, "cookies": false, "type": "", "deprecated": false, @@ -23927,7 +24038,7 @@ }, "x-appwrite": { "method": "deleteSmsTemplate", - "weight": 192, + "weight": 194, "cookies": false, "type": "", "deprecated": false, @@ -24152,7 +24263,7 @@ }, "x-appwrite": { "method": "listWebhooks", - "weight": 170, + "weight": 172, "cookies": false, "type": "", "deprecated": false, @@ -24212,7 +24323,7 @@ }, "x-appwrite": { "method": "createWebhook", - "weight": 169, + "weight": 171, "cookies": false, "type": "", "deprecated": false, @@ -24329,7 +24440,7 @@ }, "x-appwrite": { "method": "getWebhook", - "weight": 171, + "weight": 173, "cookies": false, "type": "", "deprecated": false, @@ -24399,7 +24510,7 @@ }, "x-appwrite": { "method": "updateWebhook", - "weight": 172, + "weight": 174, "cookies": false, "type": "", "deprecated": false, @@ -24517,7 +24628,7 @@ }, "x-appwrite": { "method": "deleteWebhook", - "weight": 174, + "weight": 176, "cookies": false, "type": "", "deprecated": false, @@ -24589,7 +24700,7 @@ }, "x-appwrite": { "method": "updateWebhookSignature", - "weight": 173, + "weight": 175, "cookies": false, "type": "", "deprecated": false, @@ -24661,7 +24772,7 @@ }, "x-appwrite": { "method": "listRules", - "weight": 315, + "weight": 317, "cookies": false, "type": "", "deprecated": false, @@ -24735,7 +24846,7 @@ }, "x-appwrite": { "method": "createRule", - "weight": 314, + "weight": 316, "cookies": false, "type": "", "deprecated": false, @@ -24821,7 +24932,7 @@ }, "x-appwrite": { "method": "getRule", - "weight": 316, + "weight": 318, "cookies": false, "type": "", "deprecated": false, @@ -24874,7 +24985,7 @@ }, "x-appwrite": { "method": "deleteRule", - "weight": 317, + "weight": 319, "cookies": false, "type": "", "deprecated": false, @@ -24936,7 +25047,7 @@ }, "x-appwrite": { "method": "updateRuleVerification", - "weight": 318, + "weight": 320, "cookies": false, "type": "", "deprecated": false, @@ -24998,7 +25109,7 @@ }, "x-appwrite": { "method": "listBuckets", - "weight": 201, + "weight": 203, "cookies": false, "type": "", "deprecated": false, @@ -25073,7 +25184,7 @@ }, "x-appwrite": { "method": "createBucket", - "weight": 200, + "weight": 202, "cookies": false, "type": "", "deprecated": false, @@ -25202,7 +25313,7 @@ }, "x-appwrite": { "method": "getBucket", - "weight": 202, + "weight": 204, "cookies": false, "type": "", "deprecated": false, @@ -25263,7 +25374,7 @@ }, "x-appwrite": { "method": "updateBucket", - "weight": 203, + "weight": 205, "cookies": false, "type": "", "deprecated": false, @@ -25389,7 +25500,7 @@ }, "x-appwrite": { "method": "deleteBucket", - "weight": 204, + "weight": 206, "cookies": false, "type": "", "deprecated": false, @@ -25452,7 +25563,7 @@ }, "x-appwrite": { "method": "listFiles", - "weight": 206, + "weight": 208, "cookies": false, "type": "", "deprecated": false, @@ -25525,7 +25636,7 @@ "tags": [ "storage" ], - "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n", + "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n", "responses": { "201": { "description": "File", @@ -25540,7 +25651,7 @@ }, "x-appwrite": { "method": "createFile", - "weight": 205, + "weight": 207, "cookies": false, "type": "upload", "deprecated": false, @@ -25640,7 +25751,7 @@ }, "x-appwrite": { "method": "getFile", - "weight": 207, + "weight": 209, "cookies": false, "type": "", "deprecated": false, @@ -25714,7 +25825,7 @@ }, "x-appwrite": { "method": "updateFile", - "weight": 212, + "weight": 214, "cookies": false, "type": "", "deprecated": false, @@ -25805,7 +25916,7 @@ }, "x-appwrite": { "method": "deleteFile", - "weight": 213, + "weight": 215, "cookies": false, "type": "", "deprecated": false, @@ -25874,7 +25985,7 @@ }, "x-appwrite": { "method": "getFileDownload", - "weight": 209, + "weight": 211, "cookies": false, "type": "location", "deprecated": false, @@ -25943,7 +26054,7 @@ }, "x-appwrite": { "method": "getFilePreview", - "weight": 208, + "weight": 210, "cookies": false, "type": "location", "deprecated": false, @@ -26161,7 +26272,7 @@ }, "x-appwrite": { "method": "getFileView", - "weight": 210, + "weight": 212, "cookies": false, "type": "location", "deprecated": false, @@ -26237,7 +26348,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 214, + "weight": 216, "cookies": false, "type": "", "deprecated": false, @@ -26311,7 +26422,7 @@ }, "x-appwrite": { "method": "getBucketUsage", - "weight": 215, + "weight": 217, "cookies": false, "type": "", "deprecated": false, @@ -26395,7 +26506,7 @@ }, "x-appwrite": { "method": "list", - "weight": 217, + "weight": 219, "cookies": false, "type": "", "deprecated": false, @@ -26473,7 +26584,7 @@ }, "x-appwrite": { "method": "create", - "weight": 216, + "weight": 218, "cookies": false, "type": "", "deprecated": false, @@ -26560,7 +26671,7 @@ }, "x-appwrite": { "method": "get", - "weight": 218, + "weight": 220, "cookies": false, "type": "", "deprecated": false, @@ -26624,7 +26735,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 220, + "weight": 222, "cookies": false, "type": "", "deprecated": false, @@ -26700,7 +26811,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 222, + "weight": 224, "cookies": false, "type": "", "deprecated": false, @@ -26766,7 +26877,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 229, + "weight": 231, "cookies": false, "type": "", "deprecated": false, @@ -26826,7 +26937,7 @@ "tags": [ "teams" ], - "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint.", + "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Memberships List", @@ -26841,7 +26952,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 224, + "weight": 226, "cookies": false, "type": "", "deprecated": false, @@ -26914,7 +27025,7 @@ "tags": [ "teams" ], - "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n", + "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n", "responses": { "201": { "description": "Membership", @@ -26929,7 +27040,7 @@ }, "x-appwrite": { "method": "createMembership", - "weight": 223, + "weight": 225, "cookies": false, "type": "", "deprecated": false, @@ -27027,7 +27138,7 @@ "tags": [ "teams" ], - "description": "Get a team member by the membership unique id. All team members have read access for this resource.", + "description": "Get a team member by the membership unique id. All team members have read access for this resource. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Membership", @@ -27042,7 +27153,7 @@ }, "x-appwrite": { "method": "getMembership", - "weight": 225, + "weight": 227, "cookies": false, "type": "", "deprecated": false, @@ -27101,7 +27212,7 @@ "tags": [ "teams" ], - "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n", + "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n", "responses": { "200": { "description": "Membership", @@ -27116,7 +27227,7 @@ }, "x-appwrite": { "method": "updateMembership", - "weight": 226, + "weight": 228, "cookies": false, "type": "", "deprecated": false, @@ -27205,7 +27316,7 @@ }, "x-appwrite": { "method": "deleteMembership", - "weight": 228, + "weight": 230, "cookies": false, "type": "", "deprecated": false, @@ -27266,7 +27377,7 @@ "tags": [ "teams" ], - "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n", + "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n", "responses": { "200": { "description": "Membership", @@ -27281,7 +27392,7 @@ }, "x-appwrite": { "method": "updateMembershipStatus", - "weight": 227, + "weight": 229, "cookies": false, "type": "", "deprecated": false, @@ -27380,7 +27491,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 219, + "weight": 221, "cookies": false, "type": "", "deprecated": false, @@ -27442,7 +27553,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 221, + "weight": 223, "cookies": false, "type": "", "deprecated": false, @@ -27525,7 +27636,7 @@ }, "x-appwrite": { "method": "list", - "weight": 239, + "weight": 241, "cookies": false, "type": "", "deprecated": false, @@ -27600,7 +27711,7 @@ }, "x-appwrite": { "method": "create", - "weight": 230, + "weight": 232, "cookies": false, "type": "", "deprecated": false, @@ -27690,7 +27801,7 @@ }, "x-appwrite": { "method": "createArgon2User", - "weight": 233, + "weight": 235, "cookies": false, "type": "", "deprecated": false, @@ -27777,7 +27888,7 @@ }, "x-appwrite": { "method": "createBcryptUser", - "weight": 231, + "weight": 233, "cookies": false, "type": "", "deprecated": false, @@ -27864,7 +27975,7 @@ }, "x-appwrite": { "method": "listIdentities", - "weight": 247, + "weight": 249, "cookies": false, "type": "", "deprecated": false, @@ -27934,7 +28045,7 @@ }, "x-appwrite": { "method": "deleteIdentity", - "weight": 270, + "weight": 272, "cookies": false, "type": "", "deprecated": false, @@ -27997,7 +28108,7 @@ }, "x-appwrite": { "method": "createMD5User", - "weight": 232, + "weight": 234, "cookies": false, "type": "", "deprecated": false, @@ -28084,7 +28195,7 @@ }, "x-appwrite": { "method": "createPHPassUser", - "weight": 235, + "weight": 237, "cookies": false, "type": "", "deprecated": false, @@ -28171,7 +28282,7 @@ }, "x-appwrite": { "method": "createScryptUser", - "weight": 236, + "weight": 238, "cookies": false, "type": "", "deprecated": false, @@ -28288,7 +28399,7 @@ }, "x-appwrite": { "method": "createScryptModifiedUser", - "weight": 237, + "weight": 239, "cookies": false, "type": "", "deprecated": false, @@ -28393,7 +28504,7 @@ }, "x-appwrite": { "method": "createSHAUser", - "weight": 234, + "weight": 236, "cookies": false, "type": "", "deprecated": false, @@ -28500,7 +28611,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 272, + "weight": 274, "cookies": false, "type": "", "deprecated": false, @@ -28574,7 +28685,7 @@ }, "x-appwrite": { "method": "get", - "weight": 240, + "weight": 242, "cookies": false, "type": "", "deprecated": false, @@ -28628,7 +28739,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 268, + "weight": 270, "cookies": false, "type": "", "deprecated": false, @@ -28691,7 +28802,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 253, + "weight": 255, "cookies": false, "type": "", "deprecated": false, @@ -28773,7 +28884,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 271, + "weight": 273, "cookies": false, "type": "", "deprecated": false, @@ -28842,7 +28953,7 @@ "tags": [ "users" ], - "description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", + "description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", "responses": { "200": { "description": "User", @@ -28857,7 +28968,7 @@ }, "x-appwrite": { "method": "updateLabels", - "weight": 249, + "weight": 251, "cookies": false, "type": "", "deprecated": false, @@ -28942,7 +29053,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 245, + "weight": 247, "cookies": false, "type": "", "deprecated": false, @@ -29018,7 +29129,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 244, + "weight": 246, "cookies": false, "type": "", "deprecated": false, @@ -29081,7 +29192,7 @@ }, "x-appwrite": { "method": "updateMfa", - "weight": 258, + "weight": 260, "cookies": false, "type": "", "deprecated": false, @@ -29163,7 +29274,7 @@ }, "x-appwrite": { "method": "deleteMfaAuthenticator", - "weight": 263, + "weight": 265, "cookies": false, "type": "", "deprecated": false, @@ -29241,7 +29352,7 @@ }, "x-appwrite": { "method": "listMfaFactors", - "weight": 259, + "weight": 261, "cookies": false, "type": "", "deprecated": false, @@ -29304,7 +29415,7 @@ }, "x-appwrite": { "method": "getMfaRecoveryCodes", - "weight": 260, + "weight": 262, "cookies": false, "type": "", "deprecated": false, @@ -29365,7 +29476,7 @@ }, "x-appwrite": { "method": "updateMfaRecoveryCodes", - "weight": 262, + "weight": 264, "cookies": false, "type": "", "deprecated": false, @@ -29426,7 +29537,7 @@ }, "x-appwrite": { "method": "createMfaRecoveryCodes", - "weight": 261, + "weight": 263, "cookies": false, "type": "", "deprecated": false, @@ -29489,7 +29600,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 251, + "weight": 253, "cookies": false, "type": "", "deprecated": false, @@ -29571,7 +29682,7 @@ }, "x-appwrite": { "method": "updatePassword", - "weight": 252, + "weight": 254, "cookies": false, "type": "", "deprecated": false, @@ -29653,7 +29764,7 @@ }, "x-appwrite": { "method": "updatePhone", - "weight": 254, + "weight": 256, "cookies": false, "type": "", "deprecated": false, @@ -29735,7 +29846,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 241, + "weight": 243, "cookies": false, "type": "", "deprecated": false, @@ -29796,7 +29907,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 256, + "weight": 258, "cookies": false, "type": "", "deprecated": false, @@ -29878,7 +29989,7 @@ }, "x-appwrite": { "method": "listSessions", - "weight": 243, + "weight": 245, "cookies": false, "type": "", "deprecated": false, @@ -29924,7 +30035,7 @@ "tags": [ "users" ], - "description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", + "description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", "responses": { "201": { "description": "Session", @@ -29939,7 +30050,7 @@ }, "x-appwrite": { "method": "createSession", - "weight": 264, + "weight": 266, "cookies": false, "type": "", "deprecated": false, @@ -29993,7 +30104,7 @@ }, "x-appwrite": { "method": "deleteSessions", - "weight": 267, + "weight": 269, "cookies": false, "type": "", "deprecated": false, @@ -30049,7 +30160,7 @@ }, "x-appwrite": { "method": "deleteSession", - "weight": 266, + "weight": 268, "cookies": false, "type": "", "deprecated": false, @@ -30122,7 +30233,7 @@ }, "x-appwrite": { "method": "updateStatus", - "weight": 248, + "weight": 250, "cookies": false, "type": "", "deprecated": false, @@ -30204,7 +30315,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 246, + "weight": 248, "cookies": false, "type": "", "deprecated": false, @@ -30279,7 +30390,7 @@ }, "x-appwrite": { "method": "createTarget", - "weight": 238, + "weight": 240, "cookies": false, "type": "", "deprecated": false, @@ -30391,7 +30502,7 @@ }, "x-appwrite": { "method": "getTarget", - "weight": 242, + "weight": 244, "cookies": false, "type": "", "deprecated": false, @@ -30463,7 +30574,7 @@ }, "x-appwrite": { "method": "updateTarget", - "weight": 257, + "weight": 259, "cookies": false, "type": "", "deprecated": false, @@ -30554,7 +30665,7 @@ }, "x-appwrite": { "method": "deleteTarget", - "weight": 269, + "weight": 271, "cookies": false, "type": "", "deprecated": false, @@ -30613,7 +30724,7 @@ "tags": [ "users" ], - "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n", + "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n", "responses": { "201": { "description": "Token", @@ -30628,7 +30739,7 @@ }, "x-appwrite": { "method": "createToken", - "weight": 265, + "weight": 267, "cookies": false, "type": "", "deprecated": false, @@ -30712,7 +30823,7 @@ }, "x-appwrite": { "method": "updateEmailVerification", - "weight": 255, + "weight": 257, "cookies": false, "type": "", "deprecated": false, @@ -30794,7 +30905,7 @@ }, "x-appwrite": { "method": "updatePhoneVerification", - "weight": 250, + "weight": 252, "cookies": false, "type": "", "deprecated": false, @@ -30876,7 +30987,7 @@ }, "x-appwrite": { "method": "listRepositories", - "weight": 277, + "weight": 279, "cookies": false, "type": "", "deprecated": false, @@ -30947,7 +31058,7 @@ }, "x-appwrite": { "method": "createRepository", - "weight": 278, + "weight": 280, "cookies": false, "type": "", "deprecated": false, @@ -31034,7 +31145,7 @@ }, "x-appwrite": { "method": "getRepository", - "weight": 279, + "weight": 281, "cookies": false, "type": "", "deprecated": false, @@ -31106,7 +31217,7 @@ }, "x-appwrite": { "method": "listRepositoryBranches", - "weight": 280, + "weight": 282, "cookies": false, "type": "", "deprecated": false, @@ -31178,7 +31289,7 @@ }, "x-appwrite": { "method": "getRepositoryContents", - "weight": 275, + "weight": 277, "cookies": false, "type": "", "deprecated": false, @@ -31261,7 +31372,7 @@ }, "x-appwrite": { "method": "createRepositoryDetection", - "weight": 276, + "weight": 278, "cookies": false, "type": "", "deprecated": false, @@ -31342,7 +31453,7 @@ }, "x-appwrite": { "method": "updateExternalDeployments", - "weight": 285, + "weight": 287, "cookies": false, "type": "", "deprecated": false, @@ -31433,7 +31544,7 @@ }, "x-appwrite": { "method": "listInstallations", - "weight": 282, + "weight": 284, "cookies": false, "type": "", "deprecated": false, @@ -31509,7 +31620,7 @@ }, "x-appwrite": { "method": "getInstallation", - "weight": 283, + "weight": 285, "cookies": false, "type": "", "deprecated": false, @@ -31562,7 +31673,7 @@ }, "x-appwrite": { "method": "deleteInstallation", - "weight": 284, + "weight": 286, "cookies": false, "type": "", "deprecated": false, @@ -34850,12 +34961,12 @@ }, "userName": { "type": "string", - "description": "User name.", + "description": "User name. Hide this attribute by toggling membership privacy in the Console.", "x-example": "John Doe" }, "userEmail": { "type": "string", - "description": "User email address.", + "description": "User email address. Hide this attribute by toggling membership privacy in the Console.", "x-example": "john@appwrite.io" }, "teamId": { @@ -34885,7 +34996,7 @@ }, "mfa": { "type": "boolean", - "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise.", + "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise. Hide this attribute by toggling membership privacy in the Console.", "x-example": false }, "roles": { @@ -35050,7 +35161,7 @@ "specification": { "type": "string", "description": "Machine specification for builds and executions.", - "x-example": "s-0.5vcpu-512mb" + "x-example": "s-1vcpu-512mb" } }, "required": [ @@ -35966,6 +36077,21 @@ "description": "Whether or not to send session alert emails to users.", "x-example": true }, + "authMembershipsUserName": { + "type": "boolean", + "description": "Whether or not to show user names in the teams membership response.", + "x-example": true + }, + "authMembershipsUserEmail": { + "type": "boolean", + "description": "Whether or not to show user emails in the teams membership response.", + "x-example": true + }, + "authMembershipsMfa": { + "type": "boolean", + "description": "Whether or not to show user MFA status in the teams membership response.", + "x-example": true + }, "oAuthProviders": { "type": "array", "description": "List of Auth Providers.", @@ -36046,6 +36172,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", @@ -36160,6 +36297,9 @@ "authPersonalDataCheck", "authMockNumbers", "authSessionAlerts", + "authMembershipsUserName", + "authMembershipsUserEmail", + "authMembershipsMfa", "oAuthProviders", "platforms", "webhooks", @@ -36173,6 +36313,8 @@ "smtpUsername", "smtpPassword", "smtpSecure", + "pingCount", + "pingedAt", "authEmailPassword", "authUsersAuthMagicURL", "authEmailOtp", @@ -37736,7 +37878,7 @@ "slug": { "type": "string", "description": "Size slug.", - "x-example": "s-0.5vcpu-512mb" + "x-example": "s-1vcpu-512mb" } }, "required": [ @@ -38374,7 +38516,7 @@ "name": { "type": "string", "description": "Target Name.", - "x-example": "Aegon apple token" + "x-example": "Apple iPhone 12" }, "userId": { "type": "string", @@ -38396,6 +38538,11 @@ "type": "string", "description": "The target identifier.", "x-example": "token" + }, + "expired": { + "type": "boolean", + "description": "Is the target expired.", + "x-example": false } }, "required": [ @@ -38405,7 +38552,8 @@ "name", "userId", "providerType", - "identifier" + "identifier", + "expired" ] }, "migration": { @@ -38419,7 +38567,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": { @@ -38442,9 +38590,14 @@ "description": "A string containing the type of source of the migration.", "x-example": "Appwrite" }, + "destination": { + "type": "string", + "description": "A string containing the type of destination of the migration.", + "x-example": "Appwrite" + }, "resources": { "type": "array", - "description": "Resources to migration.", + "description": "Resources to migrate.", "items": { "type": "string" }, @@ -38478,6 +38631,7 @@ "status", "stage", "source", + "destination", "resources", "statusCounters", "resourceData", diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index 274eac9686..1504322456 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-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, @@ -167,7 +167,7 @@ "tags": [ "account" ], - "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n", + "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n", "responses": { "200": { "description": "User", @@ -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, @@ -1590,7 +1590,7 @@ "tags": [ "account" ], - "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.", + "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.", "responses": { "200": { "description": "Token", @@ -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, @@ -1825,7 +1825,7 @@ "tags": [ "account" ], - "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Session", @@ -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, @@ -2370,7 +2370,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2385,7 +2385,7 @@ }, "x-appwrite": { "method": "createEmailToken", - "weight": 24, + "weight": 25, "cookies": false, "type": "", "deprecated": false, @@ -2451,7 +2451,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", "responses": { "201": { "description": "Token", @@ -2466,7 +2466,7 @@ }, "x-appwrite": { "method": "createMagicURLToken", - "weight": 23, + "weight": 24, "cookies": false, "type": "", "deprecated": false, @@ -2540,7 +2540,7 @@ "tags": [ "account" ], - "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "301": { "description": "File" @@ -2548,7 +2548,7 @@ }, "x-appwrite": { "method": "createOAuth2Token", - "weight": 22, + "weight": 23, "cookies": false, "type": "webAuth", "deprecated": false, @@ -2676,7 +2676,7 @@ "tags": [ "account" ], - "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2691,7 +2691,7 @@ }, "x-appwrite": { "method": "createPhoneToken", - "weight": 27, + "weight": 28, "cookies": false, "type": "", "deprecated": false, @@ -2755,7 +2755,7 @@ "tags": [ "account" ], - "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n", + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", "responses": { "201": { "description": "Token", @@ -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, @@ -3039,7 +3039,7 @@ "tags": [ "avatars" ], - "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", + "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", "responses": { "200": { "description": "Image" @@ -3047,7 +3047,7 @@ }, "x-appwrite": { "method": "getBrowser", - "weight": 59, + "weight": 60, "cookies": false, "type": "location", "deprecated": false, @@ -3169,7 +3169,7 @@ "tags": [ "avatars" ], - "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", + "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", "responses": { "200": { "description": "Image" @@ -3177,7 +3177,7 @@ }, "x-appwrite": { "method": "getCreditCard", - "weight": 58, + "weight": 59, "cookies": false, "type": "location", "deprecated": false, @@ -3303,7 +3303,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image" @@ -3311,7 +3311,7 @@ }, "x-appwrite": { "method": "getFavicon", - "weight": 62, + "weight": 63, "cookies": false, "type": "location", "deprecated": false, @@ -3365,7 +3365,7 @@ "tags": [ "avatars" ], - "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", + "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", "responses": { "200": { "description": "Image" @@ -3373,7 +3373,7 @@ }, "x-appwrite": { "method": "getFlag", - "weight": 60, + "weight": 61, "cookies": false, "type": "location", "deprecated": false, @@ -3857,7 +3857,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image" @@ -3865,7 +3865,7 @@ }, "x-appwrite": { "method": "getImage", - "weight": 61, + "weight": 62, "cookies": false, "type": "location", "deprecated": false, @@ -3943,7 +3943,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", + "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", "responses": { "200": { "description": "Image" @@ -3951,7 +3951,7 @@ }, "x-appwrite": { "method": "getInitials", - "weight": 64, + "weight": 65, "cookies": false, "type": "location", "deprecated": false, @@ -4039,7 +4039,7 @@ "tags": [ "avatars" ], - "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n", + "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n", "responses": { "200": { "description": "Image" @@ -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, @@ -4211,7 +4211,7 @@ "tags": [ "databases" ], - "description": "Create a new Database.\r\n", + "description": "Create a new Database.\n", "responses": { "201": { "description": "Database", @@ -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, @@ -5026,7 +5026,7 @@ "tags": [ "databases" ], - "description": "Create a boolean attribute.\r\n", + "description": "Create a boolean attribute.\n", "responses": { "202": { "description": "AttributeBoolean", @@ -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, @@ -5472,7 +5472,7 @@ "tags": [ "databases" ], - "description": "Create an email attribute.\r\n", + "description": "Create an email attribute.\n", "responses": { "202": { "description": "AttributeEmail", @@ -5487,7 +5487,7 @@ }, "x-appwrite": { "method": "createEmailAttribute", - "weight": 81, + "weight": 82, "cookies": false, "type": "", "deprecated": false, @@ -5581,7 +5581,7 @@ "tags": [ "databases" ], - "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeEmail", @@ -5596,7 +5596,7 @@ }, "x-appwrite": { "method": "updateEmailAttribute", - "weight": 93, + "weight": 94, "cookies": false, "type": "", "deprecated": false, @@ -5695,7 +5695,7 @@ "tags": [ "databases" ], - "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n", + "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n", "responses": { "202": { "description": "AttributeEnum", @@ -5710,7 +5710,7 @@ }, "x-appwrite": { "method": "createEnumAttribute", - "weight": 82, + "weight": 83, "cookies": false, "type": "", "deprecated": false, @@ -5813,7 +5813,7 @@ "tags": [ "databases" ], - "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeEnum", @@ -5828,7 +5828,7 @@ }, "x-appwrite": { "method": "updateEnumAttribute", - "weight": 94, + "weight": 95, "cookies": false, "type": "", "deprecated": false, @@ -5936,7 +5936,7 @@ "tags": [ "databases" ], - "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n", + "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n", "responses": { "202": { "description": "AttributeFloat", @@ -5951,7 +5951,7 @@ }, "x-appwrite": { "method": "createFloatAttribute", - "weight": 86, + "weight": 87, "cookies": false, "type": "", "deprecated": false, @@ -6055,7 +6055,7 @@ "tags": [ "databases" ], - "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeFloat", @@ -6070,7 +6070,7 @@ }, "x-appwrite": { "method": "updateFloatAttribute", - "weight": 98, + "weight": 99, "cookies": false, "type": "", "deprecated": false, @@ -6181,7 +6181,7 @@ "tags": [ "databases" ], - "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n", + "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n", "responses": { "202": { "description": "AttributeInteger", @@ -6196,7 +6196,7 @@ }, "x-appwrite": { "method": "createIntegerAttribute", - "weight": 85, + "weight": 86, "cookies": false, "type": "", "deprecated": false, @@ -6300,7 +6300,7 @@ "tags": [ "databases" ], - "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeInteger", @@ -6315,7 +6315,7 @@ }, "x-appwrite": { "method": "updateIntegerAttribute", - "weight": 97, + "weight": 98, "cookies": false, "type": "", "deprecated": false, @@ -6426,7 +6426,7 @@ "tags": [ "databases" ], - "description": "Create IP address attribute.\r\n", + "description": "Create IP address attribute.\n", "responses": { "202": { "description": "AttributeIP", @@ -6441,7 +6441,7 @@ }, "x-appwrite": { "method": "createIpAttribute", - "weight": 83, + "weight": 84, "cookies": false, "type": "", "deprecated": false, @@ -6535,7 +6535,7 @@ "tags": [ "databases" ], - "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeIP", @@ -6550,7 +6550,7 @@ }, "x-appwrite": { "method": "updateIpAttribute", - "weight": 95, + "weight": 96, "cookies": false, "type": "", "deprecated": false, @@ -6649,7 +6649,7 @@ "tags": [ "databases" ], - "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", + "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", "responses": { "202": { "description": "AttributeRelationship", @@ -6664,7 +6664,7 @@ }, "x-appwrite": { "method": "createRelationshipAttribute", - "weight": 89, + "weight": 90, "cookies": false, "type": "", "deprecated": false, @@ -6783,7 +6783,7 @@ "tags": [ "databases" ], - "description": "Create a string attribute.\r\n", + "description": "Create a string attribute.\n", "responses": { "202": { "description": "AttributeString", @@ -6798,7 +6798,7 @@ }, "x-appwrite": { "method": "createStringAttribute", - "weight": 80, + "weight": 81, "cookies": false, "type": "", "deprecated": false, @@ -6903,7 +6903,7 @@ "tags": [ "databases" ], - "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeString", @@ -6918,7 +6918,7 @@ }, "x-appwrite": { "method": "updateStringAttribute", - "weight": 92, + "weight": 93, "cookies": false, "type": "", "deprecated": false, @@ -6997,7 +6997,7 @@ "size": { "type": "integer", "description": "Maximum size of the string attribute.", - "x-example": null + "x-example": 1 }, "newKey": { "type": "string", @@ -7022,7 +7022,7 @@ "tags": [ "databases" ], - "description": "Create a URL attribute.\r\n", + "description": "Create a URL attribute.\n", "responses": { "202": { "description": "AttributeURL", @@ -7037,7 +7037,7 @@ }, "x-appwrite": { "method": "createUrlAttribute", - "weight": 84, + "weight": 85, "cookies": false, "type": "", "deprecated": false, @@ -7131,7 +7131,7 @@ "tags": [ "databases" ], - "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeURL", @@ -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, @@ -7433,7 +7433,7 @@ "tags": [ "databases" ], - "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", + "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", "responses": { "200": { "description": "AttributeRelationship", @@ -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, @@ -8119,7 +8119,7 @@ "tags": [ "databases" ], - "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.", + "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.", "responses": { "202": { "description": "Index", @@ -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": 289, "cookies": false, "type": "", "deprecated": false, @@ -8490,7 +8490,7 @@ }, "x-appwrite": { "method": "create", - "weight": 286, + "weight": 288, "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,24 +8573,30 @@ "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", - "go-1.23" + "bun-1.1", + "go-1.23", + "static-1" ], "x-enum-name": null, "x-enum-keys": [] @@ -8730,7 +8739,7 @@ }, "x-appwrite": { "method": "listRuntimes", - "weight": 288, + "weight": 290, "cookies": false, "type": "", "deprecated": false, @@ -8767,7 +8776,7 @@ "tags": [ "functions" ], - "description": "List allowed function specifications for this instance.\r\n", + "description": "List allowed function specifications for this instance.\n", "responses": { "200": { "description": "Specifications List", @@ -8782,7 +8791,7 @@ }, "x-appwrite": { "method": "listSpecifications", - "weight": 289, + "weight": 291, "cookies": false, "type": "", "deprecated": false, @@ -8835,7 +8844,7 @@ }, "x-appwrite": { "method": "get", - "weight": 290, + "weight": 292, "cookies": false, "type": "", "deprecated": false, @@ -8897,7 +8906,7 @@ }, "x-appwrite": { "method": "update", - "weight": 293, + "weight": 295, "cookies": false, "type": "", "deprecated": false, @@ -8959,6 +8968,7 @@ "node-19.0", "node-20.0", "node-21.0", + "node-22", "php-8.0", "php-8.1", "php-8.2", @@ -8977,6 +8987,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,24 +8996,30 @@ "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", - "go-1.23" + "bun-1.1", + "go-1.23", + "static-1" ], "x-enum-name": null, "x-enum-keys": [] @@ -9114,7 +9132,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 296, + "weight": 298, "cookies": false, "type": "", "deprecated": false, @@ -9178,7 +9196,7 @@ }, "x-appwrite": { "method": "listDeployments", - "weight": 298, + "weight": 300, "cookies": false, "type": "", "deprecated": false, @@ -9249,7 +9267,7 @@ "tags": [ "functions" ], - "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\r\n\r\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.", + "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\n\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.", "responses": { "202": { "description": "Deployment", @@ -9264,7 +9282,7 @@ }, "x-appwrite": { "method": "createDeployment", - "weight": 297, + "weight": 299, "cookies": false, "type": "upload", "deprecated": false, @@ -9363,7 +9381,7 @@ }, "x-appwrite": { "method": "getDeployment", - "weight": 299, + "weight": 301, "cookies": false, "type": "", "deprecated": false, @@ -9435,7 +9453,7 @@ }, "x-appwrite": { "method": "updateDeployment", - "weight": 295, + "weight": 297, "cookies": false, "type": "", "deprecated": false, @@ -9500,7 +9518,7 @@ }, "x-appwrite": { "method": "deleteDeployment", - "weight": 300, + "weight": 302, "cookies": false, "type": "", "deprecated": false, @@ -9567,7 +9585,7 @@ }, "x-appwrite": { "method": "createBuild", - "weight": 301, + "weight": 303, "cookies": false, "type": "", "deprecated": false, @@ -9655,7 +9673,7 @@ }, "x-appwrite": { "method": "updateDeploymentBuild", - "weight": 302, + "weight": 304, "cookies": false, "type": "", "deprecated": false, @@ -9722,7 +9740,7 @@ }, "x-appwrite": { "method": "getDeploymentDownload", - "weight": 294, + "weight": 296, "cookies": false, "type": "location", "deprecated": false, @@ -9798,7 +9816,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 304, + "weight": 306, "cookies": false, "type": "", "deprecated": false, @@ -9888,7 +9906,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 303, + "weight": 305, "cookies": false, "type": "", "deprecated": false, @@ -9941,7 +9959,7 @@ "body": { "type": "string", "description": "HTTP body of execution. Default value is empty string.", - "x-example": null + "x-example": "" }, "async": { "type": "boolean", @@ -10007,7 +10025,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 305, + "weight": 307, "cookies": false, "type": "", "deprecated": false, @@ -10068,7 +10086,7 @@ "tags": [ "functions" ], - "description": "Delete a function execution by its unique ID.\r\n", + "description": "Delete a function execution by its unique ID.\n", "responses": { "204": { "description": "No content" @@ -10076,7 +10094,7 @@ }, "x-appwrite": { "method": "deleteExecution", - "weight": 306, + "weight": 308, "cookies": false, "type": "", "deprecated": false, @@ -10150,7 +10168,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 308, + "weight": 310, "cookies": false, "type": "", "deprecated": false, @@ -10212,7 +10230,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 307, + "weight": 309, "cookies": false, "type": "", "deprecated": false, @@ -10301,7 +10319,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 309, + "weight": 311, "cookies": false, "type": "", "deprecated": false, @@ -10373,7 +10391,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 310, + "weight": 312, "cookies": false, "type": "", "deprecated": false, @@ -10462,7 +10480,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 311, + "weight": 313, "cookies": false, "type": "", "deprecated": false, @@ -10536,7 +10554,7 @@ }, "x-appwrite": { "method": "query", - "weight": 329, + "weight": 331, "cookies": false, "type": "graphql", "deprecated": false, @@ -10592,7 +10610,7 @@ }, "x-appwrite": { "method": "mutation", - "weight": 328, + "weight": 330, "cookies": false, "type": "graphql", "deprecated": false, @@ -10648,7 +10666,7 @@ }, "x-appwrite": { "method": "get", - "weight": 124, + "weight": 125, "cookies": false, "type": "", "deprecated": false, @@ -10700,7 +10718,7 @@ }, "x-appwrite": { "method": "getAntivirus", - "weight": 146, + "weight": 147, "cookies": false, "type": "", "deprecated": false, @@ -10752,7 +10770,7 @@ }, "x-appwrite": { "method": "getCache", - "weight": 127, + "weight": 128, "cookies": false, "type": "", "deprecated": false, @@ -10804,7 +10822,7 @@ }, "x-appwrite": { "method": "getCertificate", - "weight": 133, + "weight": 134, "cookies": false, "type": "", "deprecated": false, @@ -10867,7 +10885,7 @@ }, "x-appwrite": { "method": "getDB", - "weight": 126, + "weight": 127, "cookies": false, "type": "", "deprecated": false, @@ -10919,7 +10937,7 @@ }, "x-appwrite": { "method": "getPubSub", - "weight": 129, + "weight": 130, "cookies": false, "type": "", "deprecated": false, @@ -10971,7 +10989,7 @@ }, "x-appwrite": { "method": "getQueue", - "weight": 128, + "weight": 129, "cookies": false, "type": "", "deprecated": false, @@ -11023,7 +11041,7 @@ }, "x-appwrite": { "method": "getQueueBuilds", - "weight": 135, + "weight": 136, "cookies": false, "type": "", "deprecated": false, @@ -11088,7 +11106,7 @@ }, "x-appwrite": { "method": "getQueueCertificates", - "weight": 134, + "weight": 135, "cookies": false, "type": "", "deprecated": false, @@ -11153,7 +11171,7 @@ }, "x-appwrite": { "method": "getQueueDatabases", - "weight": 136, + "weight": 137, "cookies": false, "type": "", "deprecated": false, @@ -11229,7 +11247,7 @@ }, "x-appwrite": { "method": "getQueueDeletes", - "weight": 137, + "weight": 138, "cookies": false, "type": "", "deprecated": false, @@ -11279,7 +11297,7 @@ "tags": [ "health" ], - "description": "Returns the amount of failed jobs in a given queue.\r\n", + "description": "Returns the amount of failed jobs in a given queue.\n", "responses": { "200": { "description": "Health Queue", @@ -11294,7 +11312,7 @@ }, "x-appwrite": { "method": "getFailedJobs", - "weight": 147, + "weight": 148, "cookies": false, "type": "", "deprecated": false, @@ -11385,7 +11403,7 @@ }, "x-appwrite": { "method": "getQueueFunctions", - "weight": 141, + "weight": 142, "cookies": false, "type": "", "deprecated": false, @@ -11450,7 +11468,7 @@ }, "x-appwrite": { "method": "getQueueLogs", - "weight": 132, + "weight": 133, "cookies": false, "type": "", "deprecated": false, @@ -11515,7 +11533,7 @@ }, "x-appwrite": { "method": "getQueueMails", - "weight": 138, + "weight": 139, "cookies": false, "type": "", "deprecated": false, @@ -11580,7 +11598,7 @@ }, "x-appwrite": { "method": "getQueueMessaging", - "weight": 139, + "weight": 140, "cookies": false, "type": "", "deprecated": false, @@ -11645,7 +11663,7 @@ }, "x-appwrite": { "method": "getQueueMigrations", - "weight": 140, + "weight": 141, "cookies": false, "type": "", "deprecated": false, @@ -11710,7 +11728,7 @@ }, "x-appwrite": { "method": "getQueueUsage", - "weight": 142, + "weight": 143, "cookies": false, "type": "", "deprecated": false, @@ -11775,7 +11793,7 @@ }, "x-appwrite": { "method": "getQueueUsageDump", - "weight": 143, + "weight": 144, "cookies": false, "type": "", "deprecated": false, @@ -11840,7 +11858,7 @@ }, "x-appwrite": { "method": "getQueueWebhooks", - "weight": 131, + "weight": 132, "cookies": false, "type": "", "deprecated": false, @@ -11905,7 +11923,7 @@ }, "x-appwrite": { "method": "getStorage", - "weight": 145, + "weight": 146, "cookies": false, "type": "", "deprecated": false, @@ -11957,7 +11975,7 @@ }, "x-appwrite": { "method": "getStorageLocal", - "weight": 144, + "weight": 145, "cookies": false, "type": "", "deprecated": false, @@ -12009,7 +12027,7 @@ }, "x-appwrite": { "method": "getTime", - "weight": 130, + "weight": 131, "cookies": false, "type": "", "deprecated": false, @@ -12046,7 +12064,7 @@ "tags": [ "locale" ], - "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", + "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", "responses": { "200": { "description": "Locale", @@ -12061,7 +12079,7 @@ }, "x-appwrite": { "method": "get", - "weight": 116, + "weight": 117, "cookies": false, "type": "", "deprecated": false, @@ -12117,7 +12135,7 @@ }, "x-appwrite": { "method": "listCodes", - "weight": 117, + "weight": 118, "cookies": false, "type": "", "deprecated": false, @@ -12173,7 +12191,7 @@ }, "x-appwrite": { "method": "listContinents", - "weight": 121, + "weight": 122, "cookies": false, "type": "", "deprecated": false, @@ -12229,7 +12247,7 @@ }, "x-appwrite": { "method": "listCountries", - "weight": 118, + "weight": 119, "cookies": false, "type": "", "deprecated": false, @@ -12285,7 +12303,7 @@ }, "x-appwrite": { "method": "listCountriesEU", - "weight": 119, + "weight": 120, "cookies": false, "type": "", "deprecated": false, @@ -12341,7 +12359,7 @@ }, "x-appwrite": { "method": "listCountriesPhones", - "weight": 120, + "weight": 121, "cookies": false, "type": "", "deprecated": false, @@ -12397,7 +12415,7 @@ }, "x-appwrite": { "method": "listCurrencies", - "weight": 122, + "weight": 123, "cookies": false, "type": "", "deprecated": false, @@ -12453,7 +12471,7 @@ }, "x-appwrite": { "method": "listLanguages", - "weight": 123, + "weight": 124, "cookies": false, "type": "", "deprecated": false, @@ -12509,7 +12527,7 @@ }, "x-appwrite": { "method": "listMessages", - "weight": 388, + "weight": 390, "cookies": false, "type": "", "deprecated": false, @@ -12588,7 +12606,7 @@ }, "x-appwrite": { "method": "createEmail", - "weight": 385, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -12720,7 +12738,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\r\n", + "description": "Update an email message by its unique ID.\n", "responses": { "200": { "description": "Message", @@ -12735,7 +12753,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 392, + "weight": 394, "cookies": false, "type": "", "deprecated": false, @@ -12884,7 +12902,7 @@ }, "x-appwrite": { "method": "createPush", - "weight": 387, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -13027,7 +13045,7 @@ "tags": [ "messaging" ], - "description": "Update a push notification by its unique ID.\r\n", + "description": "Update a push notification by its unique ID.\n", "responses": { "200": { "description": "Message", @@ -13042,7 +13060,7 @@ }, "x-appwrite": { "method": "updatePush", - "weight": 394, + "weight": 396, "cookies": false, "type": "", "deprecated": false, @@ -13202,7 +13220,7 @@ }, "x-appwrite": { "method": "createSms", - "weight": 386, + "weight": 388, "cookies": false, "type": "", "deprecated": false, @@ -13299,7 +13317,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\r\n", + "description": "Update an email message by its unique ID.\n", "responses": { "200": { "description": "Message", @@ -13314,7 +13332,7 @@ }, "x-appwrite": { "method": "updateSms", - "weight": 393, + "weight": 395, "cookies": false, "type": "", "deprecated": false, @@ -13414,7 +13432,7 @@ "tags": [ "messaging" ], - "description": "Get a message by its unique ID.\r\n", + "description": "Get a message by its unique ID.\n", "responses": { "200": { "description": "Message", @@ -13429,7 +13447,7 @@ }, "x-appwrite": { "method": "getMessage", - "weight": 391, + "weight": 393, "cookies": false, "type": "", "deprecated": false, @@ -13485,7 +13503,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 395, + "weight": 397, "cookies": false, "type": "", "deprecated": false, @@ -13550,7 +13568,7 @@ }, "x-appwrite": { "method": "listMessageLogs", - "weight": 389, + "weight": 391, "cookies": false, "type": "", "deprecated": false, @@ -13628,7 +13646,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 390, + "weight": 392, "cookies": false, "type": "", "deprecated": false, @@ -13706,7 +13724,7 @@ }, "x-appwrite": { "method": "listProviders", - "weight": 360, + "weight": 362, "cookies": false, "type": "", "deprecated": false, @@ -13785,7 +13803,7 @@ }, "x-appwrite": { "method": "createApnsProvider", - "weight": 359, + "weight": 361, "cookies": false, "type": "", "deprecated": false, @@ -13893,7 +13911,7 @@ }, "x-appwrite": { "method": "updateApnsProvider", - "weight": 372, + "weight": 374, "cookies": false, "type": "", "deprecated": false, @@ -14004,7 +14022,7 @@ }, "x-appwrite": { "method": "createFcmProvider", - "weight": 358, + "weight": 360, "cookies": false, "type": "", "deprecated": false, @@ -14092,7 +14110,7 @@ }, "x-appwrite": { "method": "updateFcmProvider", - "weight": 371, + "weight": 373, "cookies": false, "type": "", "deprecated": false, @@ -14183,7 +14201,7 @@ }, "x-appwrite": { "method": "createMailgunProvider", - "weight": 350, + "weight": 352, "cookies": false, "type": "", "deprecated": false, @@ -14301,7 +14319,7 @@ }, "x-appwrite": { "method": "updateMailgunProvider", - "weight": 363, + "weight": 365, "cookies": false, "type": "", "deprecated": false, @@ -14422,7 +14440,7 @@ }, "x-appwrite": { "method": "createMsg91Provider", - "weight": 353, + "weight": 355, "cookies": false, "type": "", "deprecated": false, @@ -14520,7 +14538,7 @@ }, "x-appwrite": { "method": "updateMsg91Provider", - "weight": 366, + "weight": 368, "cookies": false, "type": "", "deprecated": false, @@ -14621,7 +14639,7 @@ }, "x-appwrite": { "method": "createSendgridProvider", - "weight": 351, + "weight": 353, "cookies": false, "type": "", "deprecated": false, @@ -14729,7 +14747,7 @@ }, "x-appwrite": { "method": "updateSendgridProvider", - "weight": 364, + "weight": 366, "cookies": false, "type": "", "deprecated": false, @@ -14840,7 +14858,7 @@ }, "x-appwrite": { "method": "createSmtpProvider", - "weight": 352, + "weight": 354, "cookies": false, "type": "", "deprecated": false, @@ -14986,7 +15004,7 @@ }, "x-appwrite": { "method": "updateSmtpProvider", - "weight": 365, + "weight": 367, "cookies": false, "type": "", "deprecated": false, @@ -15134,7 +15152,7 @@ }, "x-appwrite": { "method": "createTelesignProvider", - "weight": 354, + "weight": 356, "cookies": false, "type": "", "deprecated": false, @@ -15232,7 +15250,7 @@ }, "x-appwrite": { "method": "updateTelesignProvider", - "weight": 367, + "weight": 369, "cookies": false, "type": "", "deprecated": false, @@ -15333,7 +15351,7 @@ }, "x-appwrite": { "method": "createTextmagicProvider", - "weight": 355, + "weight": 357, "cookies": false, "type": "", "deprecated": false, @@ -15431,7 +15449,7 @@ }, "x-appwrite": { "method": "updateTextmagicProvider", - "weight": 368, + "weight": 370, "cookies": false, "type": "", "deprecated": false, @@ -15532,7 +15550,7 @@ }, "x-appwrite": { "method": "createTwilioProvider", - "weight": 356, + "weight": 358, "cookies": false, "type": "", "deprecated": false, @@ -15630,7 +15648,7 @@ }, "x-appwrite": { "method": "updateTwilioProvider", - "weight": 369, + "weight": 371, "cookies": false, "type": "", "deprecated": false, @@ -15731,7 +15749,7 @@ }, "x-appwrite": { "method": "createVonageProvider", - "weight": 357, + "weight": 359, "cookies": false, "type": "", "deprecated": false, @@ -15829,7 +15847,7 @@ }, "x-appwrite": { "method": "updateVonageProvider", - "weight": 370, + "weight": 372, "cookies": false, "type": "", "deprecated": false, @@ -15915,7 +15933,7 @@ "tags": [ "messaging" ], - "description": "Get a provider by its unique ID.\r\n", + "description": "Get a provider by its unique ID.\n", "responses": { "200": { "description": "Provider", @@ -15930,7 +15948,7 @@ }, "x-appwrite": { "method": "getProvider", - "weight": 362, + "weight": 364, "cookies": false, "type": "", "deprecated": false, @@ -15986,7 +16004,7 @@ }, "x-appwrite": { "method": "deleteProvider", - "weight": 373, + "weight": 375, "cookies": false, "type": "", "deprecated": false, @@ -16051,7 +16069,7 @@ }, "x-appwrite": { "method": "listProviderLogs", - "weight": 361, + "weight": 363, "cookies": false, "type": "", "deprecated": false, @@ -16129,7 +16147,7 @@ }, "x-appwrite": { "method": "listSubscriberLogs", - "weight": 382, + "weight": 384, "cookies": false, "type": "", "deprecated": false, @@ -16207,7 +16225,7 @@ }, "x-appwrite": { "method": "listTopics", - "weight": 375, + "weight": 377, "cookies": false, "type": "", "deprecated": false, @@ -16284,7 +16302,7 @@ }, "x-appwrite": { "method": "createTopic", - "weight": 374, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -16355,7 +16373,7 @@ "tags": [ "messaging" ], - "description": "Get a topic by its unique ID.\r\n", + "description": "Get a topic by its unique ID.\n", "responses": { "200": { "description": "Topic", @@ -16370,7 +16388,7 @@ }, "x-appwrite": { "method": "getTopic", - "weight": 377, + "weight": 379, "cookies": false, "type": "", "deprecated": false, @@ -16418,7 +16436,7 @@ "tags": [ "messaging" ], - "description": "Update a topic by its unique ID.\r\n", + "description": "Update a topic by its unique ID.\n", "responses": { "200": { "description": "Topic", @@ -16433,7 +16451,7 @@ }, "x-appwrite": { "method": "updateTopic", - "weight": 378, + "weight": 380, "cookies": false, "type": "", "deprecated": false, @@ -16513,7 +16531,7 @@ }, "x-appwrite": { "method": "deleteTopic", - "weight": 379, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -16578,7 +16596,7 @@ }, "x-appwrite": { "method": "listTopicLogs", - "weight": 376, + "weight": 378, "cookies": false, "type": "", "deprecated": false, @@ -16656,7 +16674,7 @@ }, "x-appwrite": { "method": "listSubscribers", - "weight": 381, + "weight": 383, "cookies": false, "type": "", "deprecated": false, @@ -16743,7 +16761,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 380, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -16822,7 +16840,7 @@ "tags": [ "messaging" ], - "description": "Get a subscriber by its unique ID.\r\n", + "description": "Get a subscriber by its unique ID.\n", "responses": { "200": { "description": "Subscriber", @@ -16837,7 +16855,7 @@ }, "x-appwrite": { "method": "getSubscriber", - "weight": 383, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -16903,7 +16921,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 384, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -16982,7 +17000,7 @@ }, "x-appwrite": { "method": "listBuckets", - "weight": 201, + "weight": 203, "cookies": false, "type": "", "deprecated": false, @@ -17058,7 +17076,7 @@ }, "x-appwrite": { "method": "createBucket", - "weight": 200, + "weight": 202, "cookies": false, "type": "", "deprecated": false, @@ -17188,7 +17206,7 @@ }, "x-appwrite": { "method": "getBucket", - "weight": 202, + "weight": 204, "cookies": false, "type": "", "deprecated": false, @@ -17250,7 +17268,7 @@ }, "x-appwrite": { "method": "updateBucket", - "weight": 203, + "weight": 205, "cookies": false, "type": "", "deprecated": false, @@ -17377,7 +17395,7 @@ }, "x-appwrite": { "method": "deleteBucket", - "weight": 204, + "weight": 206, "cookies": false, "type": "", "deprecated": false, @@ -17441,7 +17459,7 @@ }, "x-appwrite": { "method": "listFiles", - "weight": 206, + "weight": 208, "cookies": false, "type": "", "deprecated": false, @@ -17516,7 +17534,7 @@ "tags": [ "storage" ], - "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n", + "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n", "responses": { "201": { "description": "File", @@ -17531,7 +17549,7 @@ }, "x-appwrite": { "method": "createFile", - "weight": 205, + "weight": 207, "cookies": false, "type": "upload", "deprecated": false, @@ -17633,7 +17651,7 @@ }, "x-appwrite": { "method": "getFile", - "weight": 207, + "weight": 209, "cookies": false, "type": "", "deprecated": false, @@ -17709,7 +17727,7 @@ }, "x-appwrite": { "method": "updateFile", - "weight": 212, + "weight": 214, "cookies": false, "type": "", "deprecated": false, @@ -17802,7 +17820,7 @@ }, "x-appwrite": { "method": "deleteFile", - "weight": 213, + "weight": 215, "cookies": false, "type": "", "deprecated": false, @@ -17873,7 +17891,7 @@ }, "x-appwrite": { "method": "getFileDownload", - "weight": 209, + "weight": 211, "cookies": false, "type": "location", "deprecated": false, @@ -17944,7 +17962,7 @@ }, "x-appwrite": { "method": "getFilePreview", - "weight": 208, + "weight": 210, "cookies": false, "type": "location", "deprecated": false, @@ -18164,7 +18182,7 @@ }, "x-appwrite": { "method": "getFileView", - "weight": 210, + "weight": 212, "cookies": false, "type": "location", "deprecated": false, @@ -18242,7 +18260,7 @@ }, "x-appwrite": { "method": "list", - "weight": 217, + "weight": 219, "cookies": false, "type": "", "deprecated": false, @@ -18322,7 +18340,7 @@ }, "x-appwrite": { "method": "create", - "weight": 216, + "weight": 218, "cookies": false, "type": "", "deprecated": false, @@ -18411,7 +18429,7 @@ }, "x-appwrite": { "method": "get", - "weight": 218, + "weight": 220, "cookies": false, "type": "", "deprecated": false, @@ -18477,7 +18495,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 220, + "weight": 222, "cookies": false, "type": "", "deprecated": false, @@ -18555,7 +18573,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 222, + "weight": 224, "cookies": false, "type": "", "deprecated": false, @@ -18608,7 +18626,7 @@ "tags": [ "teams" ], - "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint.", + "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Memberships List", @@ -18623,7 +18641,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 224, + "weight": 226, "cookies": false, "type": "", "deprecated": false, @@ -18698,7 +18716,7 @@ "tags": [ "teams" ], - "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n", + "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n", "responses": { "201": { "description": "Membership", @@ -18713,7 +18731,7 @@ }, "x-appwrite": { "method": "createMembership", - "weight": 223, + "weight": 225, "cookies": false, "type": "", "deprecated": false, @@ -18813,7 +18831,7 @@ "tags": [ "teams" ], - "description": "Get a team member by the membership unique id. All team members have read access for this resource.", + "description": "Get a team member by the membership unique id. All team members have read access for this resource. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Membership", @@ -18828,7 +18846,7 @@ }, "x-appwrite": { "method": "getMembership", - "weight": 225, + "weight": 227, "cookies": false, "type": "", "deprecated": false, @@ -18889,7 +18907,7 @@ "tags": [ "teams" ], - "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n", + "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n", "responses": { "200": { "description": "Membership", @@ -18904,7 +18922,7 @@ }, "x-appwrite": { "method": "updateMembership", - "weight": 226, + "weight": 228, "cookies": false, "type": "", "deprecated": false, @@ -18995,7 +19013,7 @@ }, "x-appwrite": { "method": "deleteMembership", - "weight": 228, + "weight": 230, "cookies": false, "type": "", "deprecated": false, @@ -19058,7 +19076,7 @@ "tags": [ "teams" ], - "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n", + "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n", "responses": { "200": { "description": "Membership", @@ -19073,7 +19091,7 @@ }, "x-appwrite": { "method": "updateMembershipStatus", - "weight": 227, + "weight": 229, "cookies": false, "type": "", "deprecated": false, @@ -19174,7 +19192,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 219, + "weight": 221, "cookies": false, "type": "", "deprecated": false, @@ -19238,7 +19256,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 221, + "weight": 223, "cookies": false, "type": "", "deprecated": false, @@ -19323,7 +19341,7 @@ }, "x-appwrite": { "method": "list", - "weight": 239, + "weight": 241, "cookies": false, "type": "", "deprecated": false, @@ -19399,7 +19417,7 @@ }, "x-appwrite": { "method": "create", - "weight": 230, + "weight": 232, "cookies": false, "type": "", "deprecated": false, @@ -19490,7 +19508,7 @@ }, "x-appwrite": { "method": "createArgon2User", - "weight": 233, + "weight": 235, "cookies": false, "type": "", "deprecated": false, @@ -19578,7 +19596,7 @@ }, "x-appwrite": { "method": "createBcryptUser", - "weight": 231, + "weight": 233, "cookies": false, "type": "", "deprecated": false, @@ -19666,7 +19684,7 @@ }, "x-appwrite": { "method": "listIdentities", - "weight": 247, + "weight": 249, "cookies": false, "type": "", "deprecated": false, @@ -19737,7 +19755,7 @@ }, "x-appwrite": { "method": "deleteIdentity", - "weight": 270, + "weight": 272, "cookies": false, "type": "", "deprecated": false, @@ -19801,7 +19819,7 @@ }, "x-appwrite": { "method": "createMD5User", - "weight": 232, + "weight": 234, "cookies": false, "type": "", "deprecated": false, @@ -19889,7 +19907,7 @@ }, "x-appwrite": { "method": "createPHPassUser", - "weight": 235, + "weight": 237, "cookies": false, "type": "", "deprecated": false, @@ -19977,7 +19995,7 @@ }, "x-appwrite": { "method": "createScryptUser", - "weight": 236, + "weight": 238, "cookies": false, "type": "", "deprecated": false, @@ -20095,7 +20113,7 @@ }, "x-appwrite": { "method": "createScryptModifiedUser", - "weight": 237, + "weight": 239, "cookies": false, "type": "", "deprecated": false, @@ -20201,7 +20219,7 @@ }, "x-appwrite": { "method": "createSHAUser", - "weight": 234, + "weight": 236, "cookies": false, "type": "", "deprecated": false, @@ -20309,7 +20327,7 @@ }, "x-appwrite": { "method": "get", - "weight": 240, + "weight": 242, "cookies": false, "type": "", "deprecated": false, @@ -20364,7 +20382,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 268, + "weight": 270, "cookies": false, "type": "", "deprecated": false, @@ -20428,7 +20446,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 253, + "weight": 255, "cookies": false, "type": "", "deprecated": false, @@ -20511,7 +20529,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 271, + "weight": 273, "cookies": false, "type": "", "deprecated": false, @@ -20581,7 +20599,7 @@ "tags": [ "users" ], - "description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", + "description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", "responses": { "200": { "description": "User", @@ -20596,7 +20614,7 @@ }, "x-appwrite": { "method": "updateLabels", - "weight": 249, + "weight": 251, "cookies": false, "type": "", "deprecated": false, @@ -20682,7 +20700,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 245, + "weight": 247, "cookies": false, "type": "", "deprecated": false, @@ -20759,7 +20777,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 244, + "weight": 246, "cookies": false, "type": "", "deprecated": false, @@ -20823,7 +20841,7 @@ }, "x-appwrite": { "method": "updateMfa", - "weight": 258, + "weight": 260, "cookies": false, "type": "", "deprecated": false, @@ -20906,7 +20924,7 @@ }, "x-appwrite": { "method": "deleteMfaAuthenticator", - "weight": 263, + "weight": 265, "cookies": false, "type": "", "deprecated": false, @@ -20985,7 +21003,7 @@ }, "x-appwrite": { "method": "listMfaFactors", - "weight": 259, + "weight": 261, "cookies": false, "type": "", "deprecated": false, @@ -21049,7 +21067,7 @@ }, "x-appwrite": { "method": "getMfaRecoveryCodes", - "weight": 260, + "weight": 262, "cookies": false, "type": "", "deprecated": false, @@ -21111,7 +21129,7 @@ }, "x-appwrite": { "method": "updateMfaRecoveryCodes", - "weight": 262, + "weight": 264, "cookies": false, "type": "", "deprecated": false, @@ -21173,7 +21191,7 @@ }, "x-appwrite": { "method": "createMfaRecoveryCodes", - "weight": 261, + "weight": 263, "cookies": false, "type": "", "deprecated": false, @@ -21237,7 +21255,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 251, + "weight": 253, "cookies": false, "type": "", "deprecated": false, @@ -21320,7 +21338,7 @@ }, "x-appwrite": { "method": "updatePassword", - "weight": 252, + "weight": 254, "cookies": false, "type": "", "deprecated": false, @@ -21403,7 +21421,7 @@ }, "x-appwrite": { "method": "updatePhone", - "weight": 254, + "weight": 256, "cookies": false, "type": "", "deprecated": false, @@ -21486,7 +21504,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 241, + "weight": 243, "cookies": false, "type": "", "deprecated": false, @@ -21548,7 +21566,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 256, + "weight": 258, "cookies": false, "type": "", "deprecated": false, @@ -21631,7 +21649,7 @@ }, "x-appwrite": { "method": "listSessions", - "weight": 243, + "weight": 245, "cookies": false, "type": "", "deprecated": false, @@ -21678,7 +21696,7 @@ "tags": [ "users" ], - "description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", + "description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", "responses": { "201": { "description": "Session", @@ -21693,7 +21711,7 @@ }, "x-appwrite": { "method": "createSession", - "weight": 264, + "weight": 266, "cookies": false, "type": "", "deprecated": false, @@ -21748,7 +21766,7 @@ }, "x-appwrite": { "method": "deleteSessions", - "weight": 267, + "weight": 269, "cookies": false, "type": "", "deprecated": false, @@ -21805,7 +21823,7 @@ }, "x-appwrite": { "method": "deleteSession", - "weight": 266, + "weight": 268, "cookies": false, "type": "", "deprecated": false, @@ -21879,7 +21897,7 @@ }, "x-appwrite": { "method": "updateStatus", - "weight": 248, + "weight": 250, "cookies": false, "type": "", "deprecated": false, @@ -21962,7 +21980,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 246, + "weight": 248, "cookies": false, "type": "", "deprecated": false, @@ -22038,7 +22056,7 @@ }, "x-appwrite": { "method": "createTarget", - "weight": 238, + "weight": 240, "cookies": false, "type": "", "deprecated": false, @@ -22151,7 +22169,7 @@ }, "x-appwrite": { "method": "getTarget", - "weight": 242, + "weight": 244, "cookies": false, "type": "", "deprecated": false, @@ -22224,7 +22242,7 @@ }, "x-appwrite": { "method": "updateTarget", - "weight": 257, + "weight": 259, "cookies": false, "type": "", "deprecated": false, @@ -22316,7 +22334,7 @@ }, "x-appwrite": { "method": "deleteTarget", - "weight": 269, + "weight": 271, "cookies": false, "type": "", "deprecated": false, @@ -22376,7 +22394,7 @@ "tags": [ "users" ], - "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n", + "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n", "responses": { "201": { "description": "Token", @@ -22391,7 +22409,7 @@ }, "x-appwrite": { "method": "createToken", - "weight": 265, + "weight": 267, "cookies": false, "type": "", "deprecated": false, @@ -22476,7 +22494,7 @@ }, "x-appwrite": { "method": "updateEmailVerification", - "weight": 255, + "weight": 257, "cookies": false, "type": "", "deprecated": false, @@ -22559,7 +22577,7 @@ }, "x-appwrite": { "method": "updatePhoneVerification", - "weight": 250, + "weight": 252, "cookies": false, "type": "", "deprecated": false, @@ -25580,12 +25598,12 @@ }, "userName": { "type": "string", - "description": "User name.", + "description": "User name. Hide this attribute by toggling membership privacy in the Console.", "x-example": "John Doe" }, "userEmail": { "type": "string", - "description": "User email address.", + "description": "User email address. Hide this attribute by toggling membership privacy in the Console.", "x-example": "john@appwrite.io" }, "teamId": { @@ -25615,7 +25633,7 @@ }, "mfa": { "type": "boolean", - "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise.", + "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise. Hide this attribute by toggling membership privacy in the Console.", "x-example": false }, "roles": { @@ -25780,7 +25798,7 @@ "specification": { "type": "string", "description": "Machine specification for builds and executions.", - "x-example": "s-0.5vcpu-512mb" + "x-example": "s-1vcpu-512mb" } }, "required": [ @@ -26592,7 +26610,7 @@ "slug": { "type": "string", "description": "Size slug.", - "x-example": "s-0.5vcpu-512mb" + "x-example": "s-1vcpu-512mb" } }, "required": [ @@ -27040,7 +27058,7 @@ "name": { "type": "string", "description": "Target Name.", - "x-example": "Aegon apple token" + "x-example": "Apple iPhone 12" }, "userId": { "type": "string", @@ -27062,6 +27080,11 @@ "type": "string", "description": "The target identifier.", "x-example": "token" + }, + "expired": { + "type": "boolean", + "description": "Is the target expired.", + "x-example": false } }, "required": [ @@ -27071,7 +27094,8 @@ "name", "userId", "providerType", - "identifier" + "identifier", + "expired" ] } }, diff --git a/app/config/specs/swagger2-1.6.x-client.json b/app/config/specs/swagger2-1.6.x-client.json index f070b1a4b0..b1b9ce8dca 100644 --- a/app/config/specs/swagger2-1.6.x-client.json +++ b/app/config/specs/swagger2-1.6.x-client.json @@ -5101,7 +5101,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 305, + "weight": 306, "cookies": false, "type": "", "deprecated": false, @@ -5186,7 +5186,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 304, + "weight": 305, "cookies": false, "type": "", "deprecated": false, @@ -5307,7 +5307,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 306, + "weight": 307, "cookies": false, "type": "", "deprecated": false, @@ -5381,7 +5381,7 @@ }, "x-appwrite": { "method": "query", - "weight": 330, + "weight": 331, "cookies": false, "type": "graphql", "deprecated": false, @@ -5457,7 +5457,7 @@ }, "x-appwrite": { "method": "mutation", - "weight": 329, + "weight": 330, "cookies": false, "type": "graphql", "deprecated": false, @@ -5981,7 +5981,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 381, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -6070,7 +6070,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 385, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -6145,7 +6145,7 @@ }, "x-appwrite": { "method": "listFiles", - "weight": 207, + "weight": 208, "cookies": false, "type": "", "deprecated": false, @@ -6230,7 +6230,7 @@ }, "x-appwrite": { "method": "createFile", - "weight": 206, + "weight": 207, "cookies": false, "type": "upload", "deprecated": false, @@ -6324,7 +6324,7 @@ }, "x-appwrite": { "method": "getFile", - "weight": 208, + "weight": 209, "cookies": false, "type": "", "deprecated": false, @@ -6396,7 +6396,7 @@ }, "x-appwrite": { "method": "updateFile", - "weight": 213, + "weight": 214, "cookies": false, "type": "", "deprecated": false, @@ -6487,7 +6487,7 @@ }, "x-appwrite": { "method": "deleteFile", - "weight": 214, + "weight": 215, "cookies": false, "type": "", "deprecated": false, @@ -6561,7 +6561,7 @@ }, "x-appwrite": { "method": "getFileDownload", - "weight": 210, + "weight": 211, "cookies": false, "type": "location", "deprecated": false, @@ -6635,7 +6635,7 @@ }, "x-appwrite": { "method": "getFilePreview", - "weight": 209, + "weight": 210, "cookies": false, "type": "location", "deprecated": false, @@ -6836,7 +6836,7 @@ }, "x-appwrite": { "method": "getFileView", - "weight": 211, + "weight": 212, "cookies": false, "type": "location", "deprecated": false, @@ -6910,7 +6910,7 @@ }, "x-appwrite": { "method": "list", - "weight": 218, + "weight": 219, "cookies": false, "type": "", "deprecated": false, @@ -6987,7 +6987,7 @@ }, "x-appwrite": { "method": "create", - "weight": 217, + "weight": 218, "cookies": false, "type": "", "deprecated": false, @@ -7081,7 +7081,7 @@ }, "x-appwrite": { "method": "get", - "weight": 219, + "weight": 220, "cookies": false, "type": "", "deprecated": false, @@ -7145,7 +7145,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 221, + "weight": 222, "cookies": false, "type": "", "deprecated": false, @@ -7222,7 +7222,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 223, + "weight": 224, "cookies": false, "type": "", "deprecated": false, @@ -7277,7 +7277,7 @@ "tags": [ "teams" ], - "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint.", + "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Memberships List", @@ -7288,7 +7288,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 225, + "weight": 226, "cookies": false, "type": "", "deprecated": false, @@ -7373,7 +7373,7 @@ }, "x-appwrite": { "method": "createMembership", - "weight": 224, + "weight": 225, "cookies": false, "type": "", "deprecated": false, @@ -7479,7 +7479,7 @@ "tags": [ "teams" ], - "description": "Get a team member by the membership unique id. All team members have read access for this resource.", + "description": "Get a team member by the membership unique id. All team members have read access for this resource. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Membership", @@ -7490,7 +7490,7 @@ }, "x-appwrite": { "method": "getMembership", - "weight": 226, + "weight": 227, "cookies": false, "type": "", "deprecated": false, @@ -7562,7 +7562,7 @@ }, "x-appwrite": { "method": "updateMembership", - "weight": 227, + "weight": 228, "cookies": false, "type": "", "deprecated": false, @@ -7650,7 +7650,7 @@ }, "x-appwrite": { "method": "deleteMembership", - "weight": 229, + "weight": 230, "cookies": false, "type": "", "deprecated": false, @@ -7724,7 +7724,7 @@ }, "x-appwrite": { "method": "updateMembershipStatus", - "weight": 228, + "weight": 229, "cookies": false, "type": "", "deprecated": false, @@ -7822,7 +7822,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 220, + "weight": 221, "cookies": false, "type": "", "deprecated": false, @@ -7885,7 +7885,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 222, + "weight": 223, "cookies": false, "type": "", "deprecated": false, @@ -9445,12 +9445,12 @@ }, "userName": { "type": "string", - "description": "User name.", + "description": "User name. Hide this attribute by toggling membership privacy in the Console.", "x-example": "John Doe" }, "userEmail": { "type": "string", - "description": "User email address.", + "description": "User email address. Hide this attribute by toggling membership privacy in the Console.", "x-example": "john@appwrite.io" }, "teamId": { @@ -9480,7 +9480,7 @@ }, "mfa": { "type": "boolean", - "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise.", + "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise. Hide this attribute by toggling membership privacy in the Console.", "x-example": false }, "roles": { @@ -10008,7 +10008,7 @@ "name": { "type": "string", "description": "Target Name.", - "x-example": "Aegon apple token" + "x-example": "Apple iPhone 12" }, "userId": { "type": "string", @@ -10030,6 +10030,11 @@ "type": "string", "description": "The target identifier.", "x-example": "token" + }, + "expired": { + "type": "boolean", + "description": "Is the target expired.", + "x-example": false } }, "required": [ @@ -10039,7 +10044,8 @@ "name", "userId", "providerType", - "identifier" + "identifier", + "expired" ] } }, diff --git a/app/config/specs/swagger2-1.6.x-console.json b/app/config/specs/swagger2-1.6.x-console.json index ef651e4723..9ce9d5f60d 100644 --- a/app/config/specs/swagger2-1.6.x-console.json +++ b/app/config/specs/swagger2-1.6.x-console.json @@ -4659,7 +4659,7 @@ }, "x-appwrite": { "method": "chat", - "weight": 332, + "weight": 333, "cookies": false, "type": "", "deprecated": false, @@ -4731,7 +4731,7 @@ }, "x-appwrite": { "method": "variables", - "weight": 331, + "weight": 332, "cookies": false, "type": "", "deprecated": false, @@ -7650,7 +7650,7 @@ "type": "integer", "description": "Maximum size of the string attribute.", "default": null, - "x-example": null + "x-example": 1 }, "newKey": { "type": "string", @@ -9427,7 +9427,7 @@ }, "x-appwrite": { "method": "list", - "weight": 288, + "weight": 289, "cookies": false, "type": "", "deprecated": false, @@ -9501,7 +9501,7 @@ }, "x-appwrite": { "method": "create", - "weight": 287, + "weight": 288, "cookies": false, "type": "", "deprecated": false, @@ -9609,7 +9609,8 @@ "cpp-20", "bun-1.0", "bun-1.1", - "go-1.23" + "go-1.23", + "static-1" ], "x-enum-name": null, "x-enum-keys": [] @@ -9734,7 +9735,7 @@ "specification": { "type": "string", "description": "Runtime specification for the function and builds.", - "default": "s-0.5vcpu-512mb", + "default": "s-1vcpu-512mb", "x-example": null } }, @@ -9772,7 +9773,7 @@ }, "x-appwrite": { "method": "listRuntimes", - "weight": 289, + "weight": 290, "cookies": false, "type": "", "deprecated": false, @@ -9825,7 +9826,7 @@ }, "x-appwrite": { "method": "listSpecifications", - "weight": 290, + "weight": 291, "cookies": false, "type": "", "deprecated": false, @@ -9879,7 +9880,7 @@ }, "x-appwrite": { "method": "listTemplates", - "weight": 313, + "weight": 314, "cookies": false, "type": "", "deprecated": false, @@ -9977,7 +9978,7 @@ }, "x-appwrite": { "method": "getTemplate", - "weight": 314, + "weight": 315, "cookies": false, "type": "", "deprecated": false, @@ -10039,7 +10040,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 293, + "weight": 294, "cookies": false, "type": "", "deprecated": false, @@ -10113,7 +10114,7 @@ }, "x-appwrite": { "method": "get", - "weight": 291, + "weight": 292, "cookies": false, "type": "", "deprecated": false, @@ -10174,7 +10175,7 @@ }, "x-appwrite": { "method": "update", - "weight": 294, + "weight": 295, "cookies": false, "type": "", "deprecated": false, @@ -10284,7 +10285,8 @@ "cpp-20", "bun-1.0", "bun-1.1", - "go-1.23" + "go-1.23", + "static-1" ], "x-enum-name": null, "x-enum-keys": [] @@ -10386,7 +10388,7 @@ "specification": { "type": "string", "description": "Runtime specification for the function and builds.", - "default": "s-0.5vcpu-512mb", + "default": "s-1vcpu-512mb", "x-example": null } }, @@ -10415,7 +10417,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 297, + "weight": 298, "cookies": false, "type": "", "deprecated": false, @@ -10478,7 +10480,7 @@ }, "x-appwrite": { "method": "listDeployments", - "weight": 299, + "weight": 300, "cookies": false, "type": "", "deprecated": false, @@ -10560,7 +10562,7 @@ }, "x-appwrite": { "method": "createDeployment", - "weight": 298, + "weight": 299, "cookies": false, "type": "upload", "deprecated": false, @@ -10654,7 +10656,7 @@ }, "x-appwrite": { "method": "getDeployment", - "weight": 300, + "weight": 301, "cookies": false, "type": "", "deprecated": false, @@ -10723,7 +10725,7 @@ }, "x-appwrite": { "method": "updateDeployment", - "weight": 296, + "weight": 297, "cookies": false, "type": "", "deprecated": false, @@ -10787,7 +10789,7 @@ }, "x-appwrite": { "method": "deleteDeployment", - "weight": 301, + "weight": 302, "cookies": false, "type": "", "deprecated": false, @@ -10853,7 +10855,7 @@ }, "x-appwrite": { "method": "createBuild", - "weight": 302, + "weight": 303, "cookies": false, "type": "", "deprecated": false, @@ -10937,7 +10939,7 @@ }, "x-appwrite": { "method": "updateDeploymentBuild", - "weight": 303, + "weight": 304, "cookies": false, "type": "", "deprecated": false, @@ -11008,7 +11010,7 @@ }, "x-appwrite": { "method": "getDeploymentDownload", - "weight": 295, + "weight": 296, "cookies": false, "type": "location", "deprecated": false, @@ -11081,7 +11083,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 305, + "weight": 306, "cookies": false, "type": "", "deprecated": false, @@ -11166,7 +11168,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 304, + "weight": 305, "cookies": false, "type": "", "deprecated": false, @@ -11287,7 +11289,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 306, + "weight": 307, "cookies": false, "type": "", "deprecated": false, @@ -11354,7 +11356,7 @@ }, "x-appwrite": { "method": "deleteExecution", - "weight": 307, + "weight": 308, "cookies": false, "type": "", "deprecated": false, @@ -11425,7 +11427,7 @@ }, "x-appwrite": { "method": "getFunctionUsage", - "weight": 292, + "weight": 293, "cookies": false, "type": "", "deprecated": false, @@ -11507,7 +11509,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 309, + "weight": 310, "cookies": false, "type": "", "deprecated": false, @@ -11568,7 +11570,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 308, + "weight": 309, "cookies": false, "type": "", "deprecated": false, @@ -11656,7 +11658,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 310, + "weight": 311, "cookies": false, "type": "", "deprecated": false, @@ -11725,7 +11727,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 311, + "weight": 312, "cookies": false, "type": "", "deprecated": false, @@ -11813,7 +11815,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 312, + "weight": 313, "cookies": false, "type": "", "deprecated": false, @@ -11884,7 +11886,7 @@ }, "x-appwrite": { "method": "query", - "weight": 330, + "weight": 331, "cookies": false, "type": "graphql", "deprecated": false, @@ -11960,7 +11962,7 @@ }, "x-appwrite": { "method": "mutation", - "weight": 329, + "weight": 330, "cookies": false, "type": "graphql", "deprecated": false, @@ -13888,7 +13890,7 @@ }, "x-appwrite": { "method": "listMessages", - "weight": 389, + "weight": 390, "cookies": false, "type": "", "deprecated": false, @@ -13965,7 +13967,7 @@ }, "x-appwrite": { "method": "createEmail", - "weight": 386, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -14125,7 +14127,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 393, + "weight": 394, "cookies": false, "type": "", "deprecated": false, @@ -14282,7 +14284,7 @@ }, "x-appwrite": { "method": "createPush", - "weight": 388, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -14457,7 +14459,7 @@ }, "x-appwrite": { "method": "updatePush", - "weight": 395, + "weight": 396, "cookies": false, "type": "", "deprecated": false, @@ -14629,7 +14631,7 @@ }, "x-appwrite": { "method": "createSms", - "weight": 387, + "weight": 388, "cookies": false, "type": "", "deprecated": false, @@ -14749,7 +14751,7 @@ }, "x-appwrite": { "method": "updateSms", - "weight": 394, + "weight": 395, "cookies": false, "type": "", "deprecated": false, @@ -14867,7 +14869,7 @@ }, "x-appwrite": { "method": "getMessage", - "weight": 392, + "weight": 393, "cookies": false, "type": "", "deprecated": false, @@ -14926,7 +14928,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 396, + "weight": 397, "cookies": false, "type": "", "deprecated": false, @@ -14990,7 +14992,7 @@ }, "x-appwrite": { "method": "listMessageLogs", - "weight": 390, + "weight": 391, "cookies": false, "type": "", "deprecated": false, @@ -15066,7 +15068,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 391, + "weight": 392, "cookies": false, "type": "", "deprecated": false, @@ -15142,7 +15144,7 @@ }, "x-appwrite": { "method": "listProviders", - "weight": 361, + "weight": 362, "cookies": false, "type": "", "deprecated": false, @@ -15219,7 +15221,7 @@ }, "x-appwrite": { "method": "createApnsProvider", - "weight": 360, + "weight": 361, "cookies": false, "type": "", "deprecated": false, @@ -15336,7 +15338,7 @@ }, "x-appwrite": { "method": "updateApnsProvider", - "weight": 373, + "weight": 374, "cookies": false, "type": "", "deprecated": false, @@ -15451,7 +15453,7 @@ }, "x-appwrite": { "method": "createFcmProvider", - "weight": 359, + "weight": 360, "cookies": false, "type": "", "deprecated": false, @@ -15544,7 +15546,7 @@ }, "x-appwrite": { "method": "updateFcmProvider", - "weight": 372, + "weight": 373, "cookies": false, "type": "", "deprecated": false, @@ -15635,7 +15637,7 @@ }, "x-appwrite": { "method": "createMailgunProvider", - "weight": 351, + "weight": 352, "cookies": false, "type": "", "deprecated": false, @@ -15764,7 +15766,7 @@ }, "x-appwrite": { "method": "updateMailgunProvider", - "weight": 364, + "weight": 365, "cookies": false, "type": "", "deprecated": false, @@ -15891,7 +15893,7 @@ }, "x-appwrite": { "method": "createMsg91Provider", - "weight": 354, + "weight": 355, "cookies": false, "type": "", "deprecated": false, @@ -15996,7 +15998,7 @@ }, "x-appwrite": { "method": "updateMsg91Provider", - "weight": 367, + "weight": 368, "cookies": false, "type": "", "deprecated": false, @@ -16099,7 +16101,7 @@ }, "x-appwrite": { "method": "createSendgridProvider", - "weight": 352, + "weight": 353, "cookies": false, "type": "", "deprecated": false, @@ -16216,7 +16218,7 @@ }, "x-appwrite": { "method": "updateSendgridProvider", - "weight": 365, + "weight": 366, "cookies": false, "type": "", "deprecated": false, @@ -16331,7 +16333,7 @@ }, "x-appwrite": { "method": "createSmtpProvider", - "weight": 353, + "weight": 354, "cookies": false, "type": "", "deprecated": false, @@ -16492,7 +16494,7 @@ }, "x-appwrite": { "method": "updateSmtpProvider", - "weight": 366, + "weight": 367, "cookies": false, "type": "", "deprecated": false, @@ -16650,7 +16652,7 @@ }, "x-appwrite": { "method": "createTelesignProvider", - "weight": 355, + "weight": 356, "cookies": false, "type": "", "deprecated": false, @@ -16755,7 +16757,7 @@ }, "x-appwrite": { "method": "updateTelesignProvider", - "weight": 368, + "weight": 369, "cookies": false, "type": "", "deprecated": false, @@ -16858,7 +16860,7 @@ }, "x-appwrite": { "method": "createTextmagicProvider", - "weight": 356, + "weight": 357, "cookies": false, "type": "", "deprecated": false, @@ -16963,7 +16965,7 @@ }, "x-appwrite": { "method": "updateTextmagicProvider", - "weight": 369, + "weight": 370, "cookies": false, "type": "", "deprecated": false, @@ -17066,7 +17068,7 @@ }, "x-appwrite": { "method": "createTwilioProvider", - "weight": 357, + "weight": 358, "cookies": false, "type": "", "deprecated": false, @@ -17171,7 +17173,7 @@ }, "x-appwrite": { "method": "updateTwilioProvider", - "weight": 370, + "weight": 371, "cookies": false, "type": "", "deprecated": false, @@ -17274,7 +17276,7 @@ }, "x-appwrite": { "method": "createVonageProvider", - "weight": 358, + "weight": 359, "cookies": false, "type": "", "deprecated": false, @@ -17379,7 +17381,7 @@ }, "x-appwrite": { "method": "updateVonageProvider", - "weight": 371, + "weight": 372, "cookies": false, "type": "", "deprecated": false, @@ -17482,7 +17484,7 @@ }, "x-appwrite": { "method": "getProvider", - "weight": 363, + "weight": 364, "cookies": false, "type": "", "deprecated": false, @@ -17541,7 +17543,7 @@ }, "x-appwrite": { "method": "deleteProvider", - "weight": 374, + "weight": 375, "cookies": false, "type": "", "deprecated": false, @@ -17605,7 +17607,7 @@ }, "x-appwrite": { "method": "listProviderLogs", - "weight": 362, + "weight": 363, "cookies": false, "type": "", "deprecated": false, @@ -17681,7 +17683,7 @@ }, "x-appwrite": { "method": "listSubscriberLogs", - "weight": 383, + "weight": 384, "cookies": false, "type": "", "deprecated": false, @@ -17757,7 +17759,7 @@ }, "x-appwrite": { "method": "listTopics", - "weight": 376, + "weight": 377, "cookies": false, "type": "", "deprecated": false, @@ -17832,7 +17834,7 @@ }, "x-appwrite": { "method": "createTopic", - "weight": 375, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -17924,7 +17926,7 @@ }, "x-appwrite": { "method": "getTopic", - "weight": 378, + "weight": 379, "cookies": false, "type": "", "deprecated": false, @@ -17986,7 +17988,7 @@ }, "x-appwrite": { "method": "updateTopic", - "weight": 379, + "weight": 380, "cookies": false, "type": "", "deprecated": false, @@ -18069,7 +18071,7 @@ }, "x-appwrite": { "method": "deleteTopic", - "weight": 380, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -18133,7 +18135,7 @@ }, "x-appwrite": { "method": "listTopicLogs", - "weight": 377, + "weight": 378, "cookies": false, "type": "", "deprecated": false, @@ -18209,7 +18211,7 @@ }, "x-appwrite": { "method": "listSubscribers", - "weight": 382, + "weight": 383, "cookies": false, "type": "", "deprecated": false, @@ -18292,7 +18294,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 381, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -18384,7 +18386,7 @@ }, "x-appwrite": { "method": "getSubscriber", - "weight": 384, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -18451,7 +18453,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 385, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -18526,7 +18528,7 @@ }, "x-appwrite": { "method": "list", - "weight": 338, + "weight": 339, "cookies": false, "type": "", "deprecated": false, @@ -18555,7 +18557,7 @@ "parameters": [ { "name": "queries", - "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/databases#querying-documents). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: status, stage, source, resources, statusCounters, resourceData, errors", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/databases#querying-documents). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: status, stage, source, destination, resources, statusCounters, resourceData, errors", "required": false, "type": "array", "collectionFormat": "multi", @@ -18601,7 +18603,7 @@ }, "x-appwrite": { "method": "createAppwriteMigration", - "weight": 333, + "weight": 334, "cookies": false, "type": "", "deprecated": false, @@ -18697,7 +18699,7 @@ }, "x-appwrite": { "method": "getAppwriteReport", - "weight": 340, + "weight": 341, "cookies": false, "type": "", "deprecated": false, @@ -18787,7 +18789,7 @@ }, "x-appwrite": { "method": "createFirebaseMigration", - "weight": 335, + "weight": 336, "cookies": false, "type": "", "deprecated": false, @@ -18869,7 +18871,7 @@ }, "x-appwrite": { "method": "deleteFirebaseAuth", - "weight": 346, + "weight": 347, "cookies": false, "type": "", "deprecated": false, @@ -18921,7 +18923,7 @@ }, "x-appwrite": { "method": "createFirebaseOAuthMigration", - "weight": 334, + "weight": 335, "cookies": false, "type": "", "deprecated": false, @@ -19003,7 +19005,7 @@ }, "x-appwrite": { "method": "listFirebaseProjects", - "weight": 345, + "weight": 346, "cookies": false, "type": "", "deprecated": false, @@ -19055,7 +19057,7 @@ }, "x-appwrite": { "method": "getFirebaseReport", - "weight": 341, + "weight": 342, "cookies": false, "type": "", "deprecated": false, @@ -19128,7 +19130,7 @@ }, "x-appwrite": { "method": "getFirebaseReportOAuth", - "weight": 342, + "weight": 343, "cookies": false, "type": "", "deprecated": false, @@ -19201,7 +19203,7 @@ }, "x-appwrite": { "method": "createNHostMigration", - "weight": 337, + "weight": 338, "cookies": false, "type": "", "deprecated": false, @@ -19324,7 +19326,7 @@ }, "x-appwrite": { "method": "getNHostReport", - "weight": 348, + "weight": 349, "cookies": false, "type": "", "deprecated": false, @@ -19446,7 +19448,7 @@ }, "x-appwrite": { "method": "createSupabaseMigration", - "weight": 336, + "weight": 337, "cookies": false, "type": "", "deprecated": false, @@ -19562,7 +19564,7 @@ }, "x-appwrite": { "method": "getSupabaseReport", - "weight": 347, + "weight": 348, "cookies": false, "type": "", "deprecated": false, @@ -19677,7 +19679,7 @@ }, "x-appwrite": { "method": "get", - "weight": 339, + "weight": 340, "cookies": false, "type": "", "deprecated": false, @@ -19737,7 +19739,7 @@ }, "x-appwrite": { "method": "retry", - "weight": 349, + "weight": 350, "cookies": false, "type": "", "deprecated": false, @@ -19792,7 +19794,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 350, + "weight": 351, "cookies": false, "type": "", "deprecated": false, @@ -19854,7 +19856,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 195, + "weight": 196, "cookies": false, "type": "", "deprecated": false, @@ -19940,7 +19942,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 197, + "weight": 198, "cookies": false, "type": "", "deprecated": false, @@ -19990,7 +19992,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 196, + "weight": 197, "cookies": false, "type": "", "deprecated": false, @@ -20069,7 +20071,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 198, + "weight": 199, "cookies": false, "type": "", "deprecated": false, @@ -20129,7 +20131,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 199, + "weight": 200, "cookies": false, "type": "", "deprecated": false, @@ -20208,7 +20210,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 200, + "weight": 201, "cookies": false, "type": "", "deprecated": false, @@ -20682,7 +20684,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 169, + "weight": 170, "cookies": false, "type": "", "deprecated": false, @@ -20918,7 +20920,7 @@ }, "x-appwrite": { "method": "updateAuthDuration", - "weight": 162, + "weight": 163, "cookies": false, "type": "", "deprecated": false, @@ -20998,7 +21000,7 @@ }, "x-appwrite": { "method": "updateAuthLimit", - "weight": 161, + "weight": 162, "cookies": false, "type": "", "deprecated": false, @@ -21078,7 +21080,7 @@ }, "x-appwrite": { "method": "updateAuthSessionsLimit", - "weight": 167, + "weight": 168, "cookies": false, "type": "", "deprecated": false, @@ -21134,6 +21136,100 @@ ] } }, + "\/projects\/{projectId}\/auth\/memberships-privacy": { + "patch": { + "summary": "Update project memberships privacy attributes", + "operationId": "projectsUpdateMembershipsPrivacy", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "projects" + ], + "description": "", + "responses": { + "200": { + "description": "Project", + "schema": { + "$ref": "#\/definitions\/project" + } + } + }, + "x-appwrite": { + "method": "updateMembershipsPrivacy", + "weight": 161, + "cookies": false, + "type": "", + "deprecated": false, + "demo": "projects\/update-memberships-privacy.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "projects.write", + "platforms": [ + "console" + ], + "packaging": false, + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [] + } + ], + "parameters": [ + { + "name": "projectId", + "description": "Project unique ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "userName": { + "type": "boolean", + "description": "Set to true to show userName to members of a team.", + "default": null, + "x-example": false + }, + "userEmail": { + "type": "boolean", + "description": "Set to true to show email to members of a team.", + "default": null, + "x-example": false + }, + "mfa": { + "type": "boolean", + "description": "Set to true to show mfa to members of a team.", + "default": null, + "x-example": false + } + }, + "required": [ + "userName", + "userEmail", + "mfa" + ] + } + } + ] + } + }, "\/projects\/{projectId}\/auth\/mock-numbers": { "patch": { "summary": "Update the mock numbers for the project", @@ -21158,7 +21254,7 @@ }, "x-appwrite": { "method": "updateMockNumbers", - "weight": 168, + "weight": 169, "cookies": false, "type": "", "deprecated": false, @@ -21241,7 +21337,7 @@ }, "x-appwrite": { "method": "updateAuthPasswordDictionary", - "weight": 165, + "weight": 166, "cookies": false, "type": "", "deprecated": false, @@ -21321,7 +21417,7 @@ }, "x-appwrite": { "method": "updateAuthPasswordHistory", - "weight": 164, + "weight": 165, "cookies": false, "type": "", "deprecated": false, @@ -21401,7 +21497,7 @@ }, "x-appwrite": { "method": "updatePersonalDataCheck", - "weight": 166, + "weight": 167, "cookies": false, "type": "", "deprecated": false, @@ -21561,7 +21657,7 @@ }, "x-appwrite": { "method": "updateAuthStatus", - "weight": 163, + "weight": 164, "cookies": false, "type": "", "deprecated": false, @@ -21660,7 +21756,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 181, + "weight": 182, "cookies": false, "type": "", "deprecated": false, @@ -21749,7 +21845,7 @@ }, "x-appwrite": { "method": "listKeys", - "weight": 177, + "weight": 178, "cookies": false, "type": "", "deprecated": false, @@ -21809,7 +21905,7 @@ }, "x-appwrite": { "method": "createKey", - "weight": 176, + "weight": 177, "cookies": false, "type": "", "deprecated": false, @@ -21905,7 +22001,7 @@ }, "x-appwrite": { "method": "getKey", - "weight": 178, + "weight": 179, "cookies": false, "type": "", "deprecated": false, @@ -21973,7 +22069,7 @@ }, "x-appwrite": { "method": "updateKey", - "weight": 179, + "weight": 180, "cookies": false, "type": "", "deprecated": false, @@ -22070,7 +22166,7 @@ }, "x-appwrite": { "method": "deleteKey", - "weight": 180, + "weight": 181, "cookies": false, "type": "", "deprecated": false, @@ -22281,7 +22377,7 @@ }, "x-appwrite": { "method": "listPlatforms", - "weight": 183, + "weight": 184, "cookies": false, "type": "", "deprecated": false, @@ -22341,7 +22437,7 @@ }, "x-appwrite": { "method": "createPlatform", - "weight": 182, + "weight": 183, "cookies": false, "type": "", "deprecated": false, @@ -22465,7 +22561,7 @@ }, "x-appwrite": { "method": "getPlatform", - "weight": 184, + "weight": 185, "cookies": false, "type": "", "deprecated": false, @@ -22533,7 +22629,7 @@ }, "x-appwrite": { "method": "updatePlatform", - "weight": 185, + "weight": 186, "cookies": false, "type": "", "deprecated": false, @@ -22632,7 +22728,7 @@ }, "x-appwrite": { "method": "deletePlatform", - "weight": 186, + "weight": 187, "cookies": false, "type": "", "deprecated": false, @@ -22884,7 +22980,7 @@ }, "x-appwrite": { "method": "updateSmtp", - "weight": 187, + "weight": 188, "cookies": false, "type": "", "deprecated": false, @@ -23013,7 +23109,7 @@ }, "x-appwrite": { "method": "createSmtpTest", - "weight": 188, + "weight": 189, "cookies": false, "type": "", "deprecated": false, @@ -23233,7 +23329,7 @@ }, "x-appwrite": { "method": "getEmailTemplate", - "weight": 190, + "weight": 191, "cookies": false, "type": "", "deprecated": false, @@ -23455,7 +23551,7 @@ }, "x-appwrite": { "method": "updateEmailTemplate", - "weight": 192, + "weight": 193, "cookies": false, "type": "", "deprecated": false, @@ -23720,7 +23816,7 @@ }, "x-appwrite": { "method": "deleteEmailTemplate", - "weight": 194, + "weight": 195, "cookies": false, "type": "", "deprecated": false, @@ -23944,7 +24040,7 @@ }, "x-appwrite": { "method": "getSmsTemplate", - "weight": 189, + "weight": 190, "cookies": false, "type": "", "deprecated": false, @@ -24163,7 +24259,7 @@ }, "x-appwrite": { "method": "updateSmsTemplate", - "weight": 191, + "weight": 192, "cookies": false, "type": "", "deprecated": false, @@ -24400,7 +24496,7 @@ }, "x-appwrite": { "method": "deleteSmsTemplate", - "weight": 193, + "weight": 194, "cookies": false, "type": "", "deprecated": false, @@ -24621,7 +24717,7 @@ }, "x-appwrite": { "method": "listWebhooks", - "weight": 171, + "weight": 172, "cookies": false, "type": "", "deprecated": false, @@ -24681,7 +24777,7 @@ }, "x-appwrite": { "method": "createWebhook", - "weight": 170, + "weight": 171, "cookies": false, "type": "", "deprecated": false, @@ -24803,7 +24899,7 @@ }, "x-appwrite": { "method": "getWebhook", - "weight": 172, + "weight": 173, "cookies": false, "type": "", "deprecated": false, @@ -24871,7 +24967,7 @@ }, "x-appwrite": { "method": "updateWebhook", - "weight": 173, + "weight": 174, "cookies": false, "type": "", "deprecated": false, @@ -24994,7 +25090,7 @@ }, "x-appwrite": { "method": "deleteWebhook", - "weight": 175, + "weight": 176, "cookies": false, "type": "", "deprecated": false, @@ -25064,7 +25160,7 @@ }, "x-appwrite": { "method": "updateWebhookSignature", - "weight": 174, + "weight": 175, "cookies": false, "type": "", "deprecated": false, @@ -25134,7 +25230,7 @@ }, "x-appwrite": { "method": "listRules", - "weight": 316, + "weight": 317, "cookies": false, "type": "", "deprecated": false, @@ -25207,7 +25303,7 @@ }, "x-appwrite": { "method": "createRule", - "weight": 315, + "weight": 316, "cookies": false, "type": "", "deprecated": false, @@ -25298,7 +25394,7 @@ }, "x-appwrite": { "method": "getRule", - "weight": 317, + "weight": 318, "cookies": false, "type": "", "deprecated": false, @@ -25353,7 +25449,7 @@ }, "x-appwrite": { "method": "deleteRule", - "weight": 318, + "weight": 319, "cookies": false, "type": "", "deprecated": false, @@ -25415,7 +25511,7 @@ }, "x-appwrite": { "method": "updateRuleVerification", - "weight": 319, + "weight": 320, "cookies": false, "type": "", "deprecated": false, @@ -25477,7 +25573,7 @@ }, "x-appwrite": { "method": "listBuckets", - "weight": 202, + "weight": 203, "cookies": false, "type": "", "deprecated": false, @@ -25551,7 +25647,7 @@ }, "x-appwrite": { "method": "createBucket", - "weight": 201, + "weight": 202, "cookies": false, "type": "", "deprecated": false, @@ -25692,7 +25788,7 @@ }, "x-appwrite": { "method": "getBucket", - "weight": 203, + "weight": 204, "cookies": false, "type": "", "deprecated": false, @@ -25753,7 +25849,7 @@ }, "x-appwrite": { "method": "updateBucket", - "weight": 204, + "weight": 205, "cookies": false, "type": "", "deprecated": false, @@ -25888,7 +25984,7 @@ }, "x-appwrite": { "method": "deleteBucket", - "weight": 205, + "weight": 206, "cookies": false, "type": "", "deprecated": false, @@ -25951,7 +26047,7 @@ }, "x-appwrite": { "method": "listFiles", - "weight": 207, + "weight": 208, "cookies": false, "type": "", "deprecated": false, @@ -26036,7 +26132,7 @@ }, "x-appwrite": { "method": "createFile", - "weight": 206, + "weight": 207, "cookies": false, "type": "upload", "deprecated": false, @@ -26130,7 +26226,7 @@ }, "x-appwrite": { "method": "getFile", - "weight": 208, + "weight": 209, "cookies": false, "type": "", "deprecated": false, @@ -26202,7 +26298,7 @@ }, "x-appwrite": { "method": "updateFile", - "weight": 213, + "weight": 214, "cookies": false, "type": "", "deprecated": false, @@ -26293,7 +26389,7 @@ }, "x-appwrite": { "method": "deleteFile", - "weight": 214, + "weight": 215, "cookies": false, "type": "", "deprecated": false, @@ -26367,7 +26463,7 @@ }, "x-appwrite": { "method": "getFileDownload", - "weight": 210, + "weight": 211, "cookies": false, "type": "location", "deprecated": false, @@ -26441,7 +26537,7 @@ }, "x-appwrite": { "method": "getFilePreview", - "weight": 209, + "weight": 210, "cookies": false, "type": "location", "deprecated": false, @@ -26642,7 +26738,7 @@ }, "x-appwrite": { "method": "getFileView", - "weight": 211, + "weight": 212, "cookies": false, "type": "location", "deprecated": false, @@ -26716,7 +26812,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 215, + "weight": 216, "cookies": false, "type": "", "deprecated": false, @@ -26790,7 +26886,7 @@ }, "x-appwrite": { "method": "getBucketUsage", - "weight": 216, + "weight": 217, "cookies": false, "type": "", "deprecated": false, @@ -26872,7 +26968,7 @@ }, "x-appwrite": { "method": "list", - "weight": 218, + "weight": 219, "cookies": false, "type": "", "deprecated": false, @@ -26949,7 +27045,7 @@ }, "x-appwrite": { "method": "create", - "weight": 217, + "weight": 218, "cookies": false, "type": "", "deprecated": false, @@ -27043,7 +27139,7 @@ }, "x-appwrite": { "method": "get", - "weight": 219, + "weight": 220, "cookies": false, "type": "", "deprecated": false, @@ -27107,7 +27203,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 221, + "weight": 222, "cookies": false, "type": "", "deprecated": false, @@ -27184,7 +27280,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 223, + "weight": 224, "cookies": false, "type": "", "deprecated": false, @@ -27250,7 +27346,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 230, + "weight": 231, "cookies": false, "type": "", "deprecated": false, @@ -27313,7 +27409,7 @@ "tags": [ "teams" ], - "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint.", + "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Memberships List", @@ -27324,7 +27420,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 225, + "weight": 226, "cookies": false, "type": "", "deprecated": false, @@ -27409,7 +27505,7 @@ }, "x-appwrite": { "method": "createMembership", - "weight": 224, + "weight": 225, "cookies": false, "type": "", "deprecated": false, @@ -27515,7 +27611,7 @@ "tags": [ "teams" ], - "description": "Get a team member by the membership unique id. All team members have read access for this resource.", + "description": "Get a team member by the membership unique id. All team members have read access for this resource. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Membership", @@ -27526,7 +27622,7 @@ }, "x-appwrite": { "method": "getMembership", - "weight": 226, + "weight": 227, "cookies": false, "type": "", "deprecated": false, @@ -27598,7 +27694,7 @@ }, "x-appwrite": { "method": "updateMembership", - "weight": 227, + "weight": 228, "cookies": false, "type": "", "deprecated": false, @@ -27686,7 +27782,7 @@ }, "x-appwrite": { "method": "deleteMembership", - "weight": 229, + "weight": 230, "cookies": false, "type": "", "deprecated": false, @@ -27760,7 +27856,7 @@ }, "x-appwrite": { "method": "updateMembershipStatus", - "weight": 228, + "weight": 229, "cookies": false, "type": "", "deprecated": false, @@ -27857,7 +27953,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 220, + "weight": 221, "cookies": false, "type": "", "deprecated": false, @@ -27919,7 +28015,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 222, + "weight": 223, "cookies": false, "type": "", "deprecated": false, @@ -28001,7 +28097,7 @@ }, "x-appwrite": { "method": "list", - "weight": 240, + "weight": 241, "cookies": false, "type": "", "deprecated": false, @@ -28075,7 +28171,7 @@ }, "x-appwrite": { "method": "create", - "weight": 231, + "weight": 232, "cookies": false, "type": "", "deprecated": false, @@ -28172,7 +28268,7 @@ }, "x-appwrite": { "method": "createArgon2User", - "weight": 234, + "weight": 235, "cookies": false, "type": "", "deprecated": false, @@ -28265,7 +28361,7 @@ }, "x-appwrite": { "method": "createBcryptUser", - "weight": 232, + "weight": 233, "cookies": false, "type": "", "deprecated": false, @@ -28358,7 +28454,7 @@ }, "x-appwrite": { "method": "listIdentities", - "weight": 248, + "weight": 249, "cookies": false, "type": "", "deprecated": false, @@ -28429,7 +28525,7 @@ }, "x-appwrite": { "method": "deleteIdentity", - "weight": 271, + "weight": 272, "cookies": false, "type": "", "deprecated": false, @@ -28492,7 +28588,7 @@ }, "x-appwrite": { "method": "createMD5User", - "weight": 233, + "weight": 234, "cookies": false, "type": "", "deprecated": false, @@ -28585,7 +28681,7 @@ }, "x-appwrite": { "method": "createPHPassUser", - "weight": 236, + "weight": 237, "cookies": false, "type": "", "deprecated": false, @@ -28678,7 +28774,7 @@ }, "x-appwrite": { "method": "createScryptUser", - "weight": 237, + "weight": 238, "cookies": false, "type": "", "deprecated": false, @@ -28806,7 +28902,7 @@ }, "x-appwrite": { "method": "createScryptModifiedUser", - "weight": 238, + "weight": 239, "cookies": false, "type": "", "deprecated": false, @@ -28920,7 +29016,7 @@ }, "x-appwrite": { "method": "createSHAUser", - "weight": 235, + "weight": 236, "cookies": false, "type": "", "deprecated": false, @@ -29034,7 +29130,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 273, + "weight": 274, "cookies": false, "type": "", "deprecated": false, @@ -29108,7 +29204,7 @@ }, "x-appwrite": { "method": "get", - "weight": 241, + "weight": 242, "cookies": false, "type": "", "deprecated": false, @@ -29164,7 +29260,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 269, + "weight": 270, "cookies": false, "type": "", "deprecated": false, @@ -29227,7 +29323,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 254, + "weight": 255, "cookies": false, "type": "", "deprecated": false, @@ -29308,7 +29404,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 272, + "weight": 273, "cookies": false, "type": "", "deprecated": false, @@ -29392,7 +29488,7 @@ }, "x-appwrite": { "method": "updateLabels", - "weight": 250, + "weight": 251, "cookies": false, "type": "", "deprecated": false, @@ -29476,7 +29572,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 246, + "weight": 247, "cookies": false, "type": "", "deprecated": false, @@ -29551,7 +29647,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 245, + "weight": 246, "cookies": false, "type": "", "deprecated": false, @@ -29614,7 +29710,7 @@ }, "x-appwrite": { "method": "updateMfa", - "weight": 259, + "weight": 260, "cookies": false, "type": "", "deprecated": false, @@ -29695,7 +29791,7 @@ }, "x-appwrite": { "method": "deleteMfaAuthenticator", - "weight": 264, + "weight": 265, "cookies": false, "type": "", "deprecated": false, @@ -29771,7 +29867,7 @@ }, "x-appwrite": { "method": "listMfaFactors", - "weight": 260, + "weight": 261, "cookies": false, "type": "", "deprecated": false, @@ -29834,7 +29930,7 @@ }, "x-appwrite": { "method": "getMfaRecoveryCodes", - "weight": 261, + "weight": 262, "cookies": false, "type": "", "deprecated": false, @@ -29895,7 +29991,7 @@ }, "x-appwrite": { "method": "updateMfaRecoveryCodes", - "weight": 263, + "weight": 264, "cookies": false, "type": "", "deprecated": false, @@ -29956,7 +30052,7 @@ }, "x-appwrite": { "method": "createMfaRecoveryCodes", - "weight": 262, + "weight": 263, "cookies": false, "type": "", "deprecated": false, @@ -30019,7 +30115,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 252, + "weight": 253, "cookies": false, "type": "", "deprecated": false, @@ -30100,7 +30196,7 @@ }, "x-appwrite": { "method": "updatePassword", - "weight": 253, + "weight": 254, "cookies": false, "type": "", "deprecated": false, @@ -30181,7 +30277,7 @@ }, "x-appwrite": { "method": "updatePhone", - "weight": 255, + "weight": 256, "cookies": false, "type": "", "deprecated": false, @@ -30262,7 +30358,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 242, + "weight": 243, "cookies": false, "type": "", "deprecated": false, @@ -30323,7 +30419,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 257, + "weight": 258, "cookies": false, "type": "", "deprecated": false, @@ -30404,7 +30500,7 @@ }, "x-appwrite": { "method": "listSessions", - "weight": 244, + "weight": 245, "cookies": false, "type": "", "deprecated": false, @@ -30465,7 +30561,7 @@ }, "x-appwrite": { "method": "createSession", - "weight": 265, + "weight": 266, "cookies": false, "type": "", "deprecated": false, @@ -30521,7 +30617,7 @@ }, "x-appwrite": { "method": "deleteSessions", - "weight": 268, + "weight": 269, "cookies": false, "type": "", "deprecated": false, @@ -30579,7 +30675,7 @@ }, "x-appwrite": { "method": "deleteSession", - "weight": 267, + "weight": 268, "cookies": false, "type": "", "deprecated": false, @@ -30650,7 +30746,7 @@ }, "x-appwrite": { "method": "updateStatus", - "weight": 249, + "weight": 250, "cookies": false, "type": "", "deprecated": false, @@ -30731,7 +30827,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 247, + "weight": 248, "cookies": false, "type": "", "deprecated": false, @@ -30805,7 +30901,7 @@ }, "x-appwrite": { "method": "createTarget", - "weight": 239, + "weight": 240, "cookies": false, "type": "", "deprecated": false, @@ -30920,7 +31016,7 @@ }, "x-appwrite": { "method": "getTarget", - "weight": 243, + "weight": 244, "cookies": false, "type": "", "deprecated": false, @@ -30990,7 +31086,7 @@ }, "x-appwrite": { "method": "updateTarget", - "weight": 258, + "weight": 259, "cookies": false, "type": "", "deprecated": false, @@ -31084,7 +31180,7 @@ }, "x-appwrite": { "method": "deleteTarget", - "weight": 270, + "weight": 271, "cookies": false, "type": "", "deprecated": false, @@ -31156,7 +31252,7 @@ }, "x-appwrite": { "method": "createToken", - "weight": 266, + "weight": 267, "cookies": false, "type": "", "deprecated": false, @@ -31240,7 +31336,7 @@ }, "x-appwrite": { "method": "updateEmailVerification", - "weight": 256, + "weight": 257, "cookies": false, "type": "", "deprecated": false, @@ -31321,7 +31417,7 @@ }, "x-appwrite": { "method": "updatePhoneVerification", - "weight": 251, + "weight": 252, "cookies": false, "type": "", "deprecated": false, @@ -31402,7 +31498,7 @@ }, "x-appwrite": { "method": "listRepositories", - "weight": 278, + "weight": 279, "cookies": false, "type": "", "deprecated": false, @@ -31471,7 +31567,7 @@ }, "x-appwrite": { "method": "createRepository", - "weight": 279, + "weight": 280, "cookies": false, "type": "", "deprecated": false, @@ -31558,7 +31654,7 @@ }, "x-appwrite": { "method": "getRepository", - "weight": 280, + "weight": 281, "cookies": false, "type": "", "deprecated": false, @@ -31628,7 +31724,7 @@ }, "x-appwrite": { "method": "listRepositoryBranches", - "weight": 281, + "weight": 282, "cookies": false, "type": "", "deprecated": false, @@ -31698,7 +31794,7 @@ }, "x-appwrite": { "method": "getRepositoryContents", - "weight": 276, + "weight": 277, "cookies": false, "type": "", "deprecated": false, @@ -31777,7 +31873,7 @@ }, "x-appwrite": { "method": "createRepositoryDetection", - "weight": 277, + "weight": 278, "cookies": false, "type": "", "deprecated": false, @@ -31857,7 +31953,7 @@ }, "x-appwrite": { "method": "updateExternalDeployments", - "weight": 286, + "weight": 287, "cookies": false, "type": "", "deprecated": false, @@ -31945,7 +32041,7 @@ }, "x-appwrite": { "method": "listInstallations", - "weight": 283, + "weight": 284, "cookies": false, "type": "", "deprecated": false, @@ -32020,7 +32116,7 @@ }, "x-appwrite": { "method": "getInstallation", - "weight": 284, + "weight": 285, "cookies": false, "type": "", "deprecated": false, @@ -32075,7 +32171,7 @@ }, "x-appwrite": { "method": "deleteInstallation", - "weight": 285, + "weight": 286, "cookies": false, "type": "", "deprecated": false, @@ -35373,12 +35469,12 @@ }, "userName": { "type": "string", - "description": "User name.", + "description": "User name. Hide this attribute by toggling membership privacy in the Console.", "x-example": "John Doe" }, "userEmail": { "type": "string", - "description": "User email address.", + "description": "User email address. Hide this attribute by toggling membership privacy in the Console.", "x-example": "john@appwrite.io" }, "teamId": { @@ -35408,7 +35504,7 @@ }, "mfa": { "type": "boolean", - "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise.", + "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise. Hide this attribute by toggling membership privacy in the Console.", "x-example": false }, "roles": { @@ -35574,7 +35670,7 @@ "specification": { "type": "string", "description": "Machine specification for builds and executions.", - "x-example": "s-0.5vcpu-512mb" + "x-example": "s-1vcpu-512mb" } }, "required": [ @@ -36495,6 +36591,21 @@ "description": "Whether or not to send session alert emails to users.", "x-example": true }, + "authMembershipsUserName": { + "type": "boolean", + "description": "Whether or not to show user names in the teams membership response.", + "x-example": true + }, + "authMembershipsUserEmail": { + "type": "boolean", + "description": "Whether or not to show user emails in the teams membership response.", + "x-example": true + }, + "authMembershipsMfa": { + "type": "boolean", + "description": "Whether or not to show user MFA status in the teams membership response.", + "x-example": true + }, "oAuthProviders": { "type": "array", "description": "List of Auth Providers.", @@ -36704,6 +36815,9 @@ "authPersonalDataCheck", "authMockNumbers", "authSessionAlerts", + "authMembershipsUserName", + "authMembershipsUserEmail", + "authMembershipsMfa", "oAuthProviders", "platforms", "webhooks", @@ -38326,7 +38440,7 @@ "slug": { "type": "string", "description": "Size slug.", - "x-example": "s-0.5vcpu-512mb" + "x-example": "s-1vcpu-512mb" } }, "required": [ @@ -38967,7 +39081,7 @@ "name": { "type": "string", "description": "Target Name.", - "x-example": "Aegon apple token" + "x-example": "Apple iPhone 12" }, "userId": { "type": "string", @@ -38989,6 +39103,11 @@ "type": "string", "description": "The target identifier.", "x-example": "token" + }, + "expired": { + "type": "boolean", + "description": "Is the target expired.", + "x-example": false } }, "required": [ @@ -38998,7 +39117,8 @@ "name", "userId", "providerType", - "identifier" + "identifier", + "expired" ] }, "migration": { @@ -39035,9 +39155,14 @@ "description": "A string containing the type of source of the migration.", "x-example": "Appwrite" }, + "destination": { + "type": "string", + "description": "A string containing the type of destination of the migration.", + "x-example": "Appwrite" + }, "resources": { "type": "array", - "description": "Resources to migration.", + "description": "Resources to migrate.", "items": { "type": "string" }, @@ -39073,6 +39198,7 @@ "status", "stage", "source", + "destination", "resources", "statusCounters", "resourceData", diff --git a/app/config/specs/swagger2-1.6.x-server.json b/app/config/specs/swagger2-1.6.x-server.json index 37018916fa..2c8e80c65e 100644 --- a/app/config/specs/swagger2-1.6.x-server.json +++ b/app/config/specs/swagger2-1.6.x-server.json @@ -7157,7 +7157,7 @@ "type": "integer", "description": "Maximum size of the string attribute.", "default": null, - "x-example": null + "x-example": 1 }, "newKey": { "type": "string", @@ -8535,7 +8535,7 @@ }, "x-appwrite": { "method": "list", - "weight": 288, + "weight": 289, "cookies": false, "type": "", "deprecated": false, @@ -8610,7 +8610,7 @@ }, "x-appwrite": { "method": "create", - "weight": 287, + "weight": 288, "cookies": false, "type": "", "deprecated": false, @@ -8719,7 +8719,8 @@ "cpp-20", "bun-1.0", "bun-1.1", - "go-1.23" + "go-1.23", + "static-1" ], "x-enum-name": null, "x-enum-keys": [] @@ -8844,7 +8845,7 @@ "specification": { "type": "string", "description": "Runtime specification for the function and builds.", - "default": "s-0.5vcpu-512mb", + "default": "s-1vcpu-512mb", "x-example": null } }, @@ -8882,7 +8883,7 @@ }, "x-appwrite": { "method": "listRuntimes", - "weight": 289, + "weight": 290, "cookies": false, "type": "", "deprecated": false, @@ -8936,7 +8937,7 @@ }, "x-appwrite": { "method": "listSpecifications", - "weight": 290, + "weight": 291, "cookies": false, "type": "", "deprecated": false, @@ -8991,7 +8992,7 @@ }, "x-appwrite": { "method": "get", - "weight": 291, + "weight": 292, "cookies": false, "type": "", "deprecated": false, @@ -9053,7 +9054,7 @@ }, "x-appwrite": { "method": "update", - "weight": 294, + "weight": 295, "cookies": false, "type": "", "deprecated": false, @@ -9164,7 +9165,8 @@ "cpp-20", "bun-1.0", "bun-1.1", - "go-1.23" + "go-1.23", + "static-1" ], "x-enum-name": null, "x-enum-keys": [] @@ -9266,7 +9268,7 @@ "specification": { "type": "string", "description": "Runtime specification for the function and builds.", - "default": "s-0.5vcpu-512mb", + "default": "s-1vcpu-512mb", "x-example": null } }, @@ -9295,7 +9297,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 297, + "weight": 298, "cookies": false, "type": "", "deprecated": false, @@ -9359,7 +9361,7 @@ }, "x-appwrite": { "method": "listDeployments", - "weight": 299, + "weight": 300, "cookies": false, "type": "", "deprecated": false, @@ -9442,7 +9444,7 @@ }, "x-appwrite": { "method": "createDeployment", - "weight": 298, + "weight": 299, "cookies": false, "type": "upload", "deprecated": false, @@ -9537,7 +9539,7 @@ }, "x-appwrite": { "method": "getDeployment", - "weight": 300, + "weight": 301, "cookies": false, "type": "", "deprecated": false, @@ -9607,7 +9609,7 @@ }, "x-appwrite": { "method": "updateDeployment", - "weight": 296, + "weight": 297, "cookies": false, "type": "", "deprecated": false, @@ -9672,7 +9674,7 @@ }, "x-appwrite": { "method": "deleteDeployment", - "weight": 301, + "weight": 302, "cookies": false, "type": "", "deprecated": false, @@ -9739,7 +9741,7 @@ }, "x-appwrite": { "method": "createBuild", - "weight": 302, + "weight": 303, "cookies": false, "type": "", "deprecated": false, @@ -9824,7 +9826,7 @@ }, "x-appwrite": { "method": "updateDeploymentBuild", - "weight": 303, + "weight": 304, "cookies": false, "type": "", "deprecated": false, @@ -9896,7 +9898,7 @@ }, "x-appwrite": { "method": "getDeploymentDownload", - "weight": 295, + "weight": 296, "cookies": false, "type": "location", "deprecated": false, @@ -9970,7 +9972,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 305, + "weight": 306, "cookies": false, "type": "", "deprecated": false, @@ -10057,7 +10059,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 304, + "weight": 305, "cookies": false, "type": "", "deprecated": false, @@ -10180,7 +10182,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 306, + "weight": 307, "cookies": false, "type": "", "deprecated": false, @@ -10249,7 +10251,7 @@ }, "x-appwrite": { "method": "deleteExecution", - "weight": 307, + "weight": 308, "cookies": false, "type": "", "deprecated": false, @@ -10321,7 +10323,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 309, + "weight": 310, "cookies": false, "type": "", "deprecated": false, @@ -10383,7 +10385,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 308, + "weight": 309, "cookies": false, "type": "", "deprecated": false, @@ -10472,7 +10474,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 310, + "weight": 311, "cookies": false, "type": "", "deprecated": false, @@ -10542,7 +10544,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 311, + "weight": 312, "cookies": false, "type": "", "deprecated": false, @@ -10631,7 +10633,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 312, + "weight": 313, "cookies": false, "type": "", "deprecated": false, @@ -10703,7 +10705,7 @@ }, "x-appwrite": { "method": "query", - "weight": 330, + "weight": 331, "cookies": false, "type": "graphql", "deprecated": false, @@ -10781,7 +10783,7 @@ }, "x-appwrite": { "method": "mutation", - "weight": 329, + "weight": 330, "cookies": false, "type": "graphql", "deprecated": false, @@ -12750,7 +12752,7 @@ }, "x-appwrite": { "method": "listMessages", - "weight": 389, + "weight": 390, "cookies": false, "type": "", "deprecated": false, @@ -12828,7 +12830,7 @@ }, "x-appwrite": { "method": "createEmail", - "weight": 386, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -12989,7 +12991,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 393, + "weight": 394, "cookies": false, "type": "", "deprecated": false, @@ -13147,7 +13149,7 @@ }, "x-appwrite": { "method": "createPush", - "weight": 388, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -13323,7 +13325,7 @@ }, "x-appwrite": { "method": "updatePush", - "weight": 395, + "weight": 396, "cookies": false, "type": "", "deprecated": false, @@ -13496,7 +13498,7 @@ }, "x-appwrite": { "method": "createSms", - "weight": 387, + "weight": 388, "cookies": false, "type": "", "deprecated": false, @@ -13617,7 +13619,7 @@ }, "x-appwrite": { "method": "updateSms", - "weight": 394, + "weight": 395, "cookies": false, "type": "", "deprecated": false, @@ -13736,7 +13738,7 @@ }, "x-appwrite": { "method": "getMessage", - "weight": 392, + "weight": 393, "cookies": false, "type": "", "deprecated": false, @@ -13796,7 +13798,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 396, + "weight": 397, "cookies": false, "type": "", "deprecated": false, @@ -13861,7 +13863,7 @@ }, "x-appwrite": { "method": "listMessageLogs", - "weight": 390, + "weight": 391, "cookies": false, "type": "", "deprecated": false, @@ -13938,7 +13940,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 391, + "weight": 392, "cookies": false, "type": "", "deprecated": false, @@ -14015,7 +14017,7 @@ }, "x-appwrite": { "method": "listProviders", - "weight": 361, + "weight": 362, "cookies": false, "type": "", "deprecated": false, @@ -14093,7 +14095,7 @@ }, "x-appwrite": { "method": "createApnsProvider", - "weight": 360, + "weight": 361, "cookies": false, "type": "", "deprecated": false, @@ -14211,7 +14213,7 @@ }, "x-appwrite": { "method": "updateApnsProvider", - "weight": 373, + "weight": 374, "cookies": false, "type": "", "deprecated": false, @@ -14327,7 +14329,7 @@ }, "x-appwrite": { "method": "createFcmProvider", - "weight": 359, + "weight": 360, "cookies": false, "type": "", "deprecated": false, @@ -14421,7 +14423,7 @@ }, "x-appwrite": { "method": "updateFcmProvider", - "weight": 372, + "weight": 373, "cookies": false, "type": "", "deprecated": false, @@ -14513,7 +14515,7 @@ }, "x-appwrite": { "method": "createMailgunProvider", - "weight": 351, + "weight": 352, "cookies": false, "type": "", "deprecated": false, @@ -14643,7 +14645,7 @@ }, "x-appwrite": { "method": "updateMailgunProvider", - "weight": 364, + "weight": 365, "cookies": false, "type": "", "deprecated": false, @@ -14771,7 +14773,7 @@ }, "x-appwrite": { "method": "createMsg91Provider", - "weight": 354, + "weight": 355, "cookies": false, "type": "", "deprecated": false, @@ -14877,7 +14879,7 @@ }, "x-appwrite": { "method": "updateMsg91Provider", - "weight": 367, + "weight": 368, "cookies": false, "type": "", "deprecated": false, @@ -14981,7 +14983,7 @@ }, "x-appwrite": { "method": "createSendgridProvider", - "weight": 352, + "weight": 353, "cookies": false, "type": "", "deprecated": false, @@ -15099,7 +15101,7 @@ }, "x-appwrite": { "method": "updateSendgridProvider", - "weight": 365, + "weight": 366, "cookies": false, "type": "", "deprecated": false, @@ -15215,7 +15217,7 @@ }, "x-appwrite": { "method": "createSmtpProvider", - "weight": 353, + "weight": 354, "cookies": false, "type": "", "deprecated": false, @@ -15377,7 +15379,7 @@ }, "x-appwrite": { "method": "updateSmtpProvider", - "weight": 366, + "weight": 367, "cookies": false, "type": "", "deprecated": false, @@ -15536,7 +15538,7 @@ }, "x-appwrite": { "method": "createTelesignProvider", - "weight": 355, + "weight": 356, "cookies": false, "type": "", "deprecated": false, @@ -15642,7 +15644,7 @@ }, "x-appwrite": { "method": "updateTelesignProvider", - "weight": 368, + "weight": 369, "cookies": false, "type": "", "deprecated": false, @@ -15746,7 +15748,7 @@ }, "x-appwrite": { "method": "createTextmagicProvider", - "weight": 356, + "weight": 357, "cookies": false, "type": "", "deprecated": false, @@ -15852,7 +15854,7 @@ }, "x-appwrite": { "method": "updateTextmagicProvider", - "weight": 369, + "weight": 370, "cookies": false, "type": "", "deprecated": false, @@ -15956,7 +15958,7 @@ }, "x-appwrite": { "method": "createTwilioProvider", - "weight": 357, + "weight": 358, "cookies": false, "type": "", "deprecated": false, @@ -16062,7 +16064,7 @@ }, "x-appwrite": { "method": "updateTwilioProvider", - "weight": 370, + "weight": 371, "cookies": false, "type": "", "deprecated": false, @@ -16166,7 +16168,7 @@ }, "x-appwrite": { "method": "createVonageProvider", - "weight": 358, + "weight": 359, "cookies": false, "type": "", "deprecated": false, @@ -16272,7 +16274,7 @@ }, "x-appwrite": { "method": "updateVonageProvider", - "weight": 371, + "weight": 372, "cookies": false, "type": "", "deprecated": false, @@ -16376,7 +16378,7 @@ }, "x-appwrite": { "method": "getProvider", - "weight": 363, + "weight": 364, "cookies": false, "type": "", "deprecated": false, @@ -16436,7 +16438,7 @@ }, "x-appwrite": { "method": "deleteProvider", - "weight": 374, + "weight": 375, "cookies": false, "type": "", "deprecated": false, @@ -16501,7 +16503,7 @@ }, "x-appwrite": { "method": "listProviderLogs", - "weight": 362, + "weight": 363, "cookies": false, "type": "", "deprecated": false, @@ -16578,7 +16580,7 @@ }, "x-appwrite": { "method": "listSubscriberLogs", - "weight": 383, + "weight": 384, "cookies": false, "type": "", "deprecated": false, @@ -16655,7 +16657,7 @@ }, "x-appwrite": { "method": "listTopics", - "weight": 376, + "weight": 377, "cookies": false, "type": "", "deprecated": false, @@ -16731,7 +16733,7 @@ }, "x-appwrite": { "method": "createTopic", - "weight": 375, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -16824,7 +16826,7 @@ }, "x-appwrite": { "method": "getTopic", - "weight": 378, + "weight": 379, "cookies": false, "type": "", "deprecated": false, @@ -16887,7 +16889,7 @@ }, "x-appwrite": { "method": "updateTopic", - "weight": 379, + "weight": 380, "cookies": false, "type": "", "deprecated": false, @@ -16971,7 +16973,7 @@ }, "x-appwrite": { "method": "deleteTopic", - "weight": 380, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -17036,7 +17038,7 @@ }, "x-appwrite": { "method": "listTopicLogs", - "weight": 377, + "weight": 378, "cookies": false, "type": "", "deprecated": false, @@ -17113,7 +17115,7 @@ }, "x-appwrite": { "method": "listSubscribers", - "weight": 382, + "weight": 383, "cookies": false, "type": "", "deprecated": false, @@ -17197,7 +17199,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 381, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -17291,7 +17293,7 @@ }, "x-appwrite": { "method": "getSubscriber", - "weight": 384, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -17359,7 +17361,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 385, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -17436,7 +17438,7 @@ }, "x-appwrite": { "method": "listBuckets", - "weight": 202, + "weight": 203, "cookies": false, "type": "", "deprecated": false, @@ -17511,7 +17513,7 @@ }, "x-appwrite": { "method": "createBucket", - "weight": 201, + "weight": 202, "cookies": false, "type": "", "deprecated": false, @@ -17653,7 +17655,7 @@ }, "x-appwrite": { "method": "getBucket", - "weight": 203, + "weight": 204, "cookies": false, "type": "", "deprecated": false, @@ -17715,7 +17717,7 @@ }, "x-appwrite": { "method": "updateBucket", - "weight": 204, + "weight": 205, "cookies": false, "type": "", "deprecated": false, @@ -17851,7 +17853,7 @@ }, "x-appwrite": { "method": "deleteBucket", - "weight": 205, + "weight": 206, "cookies": false, "type": "", "deprecated": false, @@ -17915,7 +17917,7 @@ }, "x-appwrite": { "method": "listFiles", - "weight": 207, + "weight": 208, "cookies": false, "type": "", "deprecated": false, @@ -18002,7 +18004,7 @@ }, "x-appwrite": { "method": "createFile", - "weight": 206, + "weight": 207, "cookies": false, "type": "upload", "deprecated": false, @@ -18098,7 +18100,7 @@ }, "x-appwrite": { "method": "getFile", - "weight": 208, + "weight": 209, "cookies": false, "type": "", "deprecated": false, @@ -18172,7 +18174,7 @@ }, "x-appwrite": { "method": "updateFile", - "weight": 213, + "weight": 214, "cookies": false, "type": "", "deprecated": false, @@ -18265,7 +18267,7 @@ }, "x-appwrite": { "method": "deleteFile", - "weight": 214, + "weight": 215, "cookies": false, "type": "", "deprecated": false, @@ -18341,7 +18343,7 @@ }, "x-appwrite": { "method": "getFileDownload", - "weight": 210, + "weight": 211, "cookies": false, "type": "location", "deprecated": false, @@ -18417,7 +18419,7 @@ }, "x-appwrite": { "method": "getFilePreview", - "weight": 209, + "weight": 210, "cookies": false, "type": "location", "deprecated": false, @@ -18620,7 +18622,7 @@ }, "x-appwrite": { "method": "getFileView", - "weight": 211, + "weight": 212, "cookies": false, "type": "location", "deprecated": false, @@ -18696,7 +18698,7 @@ }, "x-appwrite": { "method": "list", - "weight": 218, + "weight": 219, "cookies": false, "type": "", "deprecated": false, @@ -18775,7 +18777,7 @@ }, "x-appwrite": { "method": "create", - "weight": 217, + "weight": 218, "cookies": false, "type": "", "deprecated": false, @@ -18871,7 +18873,7 @@ }, "x-appwrite": { "method": "get", - "weight": 219, + "weight": 220, "cookies": false, "type": "", "deprecated": false, @@ -18937,7 +18939,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 221, + "weight": 222, "cookies": false, "type": "", "deprecated": false, @@ -19016,7 +19018,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 223, + "weight": 224, "cookies": false, "type": "", "deprecated": false, @@ -19073,7 +19075,7 @@ "tags": [ "teams" ], - "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint.", + "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Memberships List", @@ -19084,7 +19086,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 225, + "weight": 226, "cookies": false, "type": "", "deprecated": false, @@ -19171,7 +19173,7 @@ }, "x-appwrite": { "method": "createMembership", - "weight": 224, + "weight": 225, "cookies": false, "type": "", "deprecated": false, @@ -19279,7 +19281,7 @@ "tags": [ "teams" ], - "description": "Get a team member by the membership unique id. All team members have read access for this resource.", + "description": "Get a team member by the membership unique id. All team members have read access for this resource. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Membership", @@ -19290,7 +19292,7 @@ }, "x-appwrite": { "method": "getMembership", - "weight": 226, + "weight": 227, "cookies": false, "type": "", "deprecated": false, @@ -19364,7 +19366,7 @@ }, "x-appwrite": { "method": "updateMembership", - "weight": 227, + "weight": 228, "cookies": false, "type": "", "deprecated": false, @@ -19454,7 +19456,7 @@ }, "x-appwrite": { "method": "deleteMembership", - "weight": 229, + "weight": 230, "cookies": false, "type": "", "deprecated": false, @@ -19530,7 +19532,7 @@ }, "x-appwrite": { "method": "updateMembershipStatus", - "weight": 228, + "weight": 229, "cookies": false, "type": "", "deprecated": false, @@ -19629,7 +19631,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 220, + "weight": 221, "cookies": false, "type": "", "deprecated": false, @@ -19693,7 +19695,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 222, + "weight": 223, "cookies": false, "type": "", "deprecated": false, @@ -19777,7 +19779,7 @@ }, "x-appwrite": { "method": "list", - "weight": 240, + "weight": 241, "cookies": false, "type": "", "deprecated": false, @@ -19852,7 +19854,7 @@ }, "x-appwrite": { "method": "create", - "weight": 231, + "weight": 232, "cookies": false, "type": "", "deprecated": false, @@ -19950,7 +19952,7 @@ }, "x-appwrite": { "method": "createArgon2User", - "weight": 234, + "weight": 235, "cookies": false, "type": "", "deprecated": false, @@ -20044,7 +20046,7 @@ }, "x-appwrite": { "method": "createBcryptUser", - "weight": 232, + "weight": 233, "cookies": false, "type": "", "deprecated": false, @@ -20138,7 +20140,7 @@ }, "x-appwrite": { "method": "listIdentities", - "weight": 248, + "weight": 249, "cookies": false, "type": "", "deprecated": false, @@ -20210,7 +20212,7 @@ }, "x-appwrite": { "method": "deleteIdentity", - "weight": 271, + "weight": 272, "cookies": false, "type": "", "deprecated": false, @@ -20274,7 +20276,7 @@ }, "x-appwrite": { "method": "createMD5User", - "weight": 233, + "weight": 234, "cookies": false, "type": "", "deprecated": false, @@ -20368,7 +20370,7 @@ }, "x-appwrite": { "method": "createPHPassUser", - "weight": 236, + "weight": 237, "cookies": false, "type": "", "deprecated": false, @@ -20462,7 +20464,7 @@ }, "x-appwrite": { "method": "createScryptUser", - "weight": 237, + "weight": 238, "cookies": false, "type": "", "deprecated": false, @@ -20591,7 +20593,7 @@ }, "x-appwrite": { "method": "createScryptModifiedUser", - "weight": 238, + "weight": 239, "cookies": false, "type": "", "deprecated": false, @@ -20706,7 +20708,7 @@ }, "x-appwrite": { "method": "createSHAUser", - "weight": 235, + "weight": 236, "cookies": false, "type": "", "deprecated": false, @@ -20821,7 +20823,7 @@ }, "x-appwrite": { "method": "get", - "weight": 241, + "weight": 242, "cookies": false, "type": "", "deprecated": false, @@ -20878,7 +20880,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 269, + "weight": 270, "cookies": false, "type": "", "deprecated": false, @@ -20942,7 +20944,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 254, + "weight": 255, "cookies": false, "type": "", "deprecated": false, @@ -21024,7 +21026,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 272, + "weight": 273, "cookies": false, "type": "", "deprecated": false, @@ -21109,7 +21111,7 @@ }, "x-appwrite": { "method": "updateLabels", - "weight": 250, + "weight": 251, "cookies": false, "type": "", "deprecated": false, @@ -21194,7 +21196,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 246, + "weight": 247, "cookies": false, "type": "", "deprecated": false, @@ -21270,7 +21272,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 245, + "weight": 246, "cookies": false, "type": "", "deprecated": false, @@ -21334,7 +21336,7 @@ }, "x-appwrite": { "method": "updateMfa", - "weight": 259, + "weight": 260, "cookies": false, "type": "", "deprecated": false, @@ -21416,7 +21418,7 @@ }, "x-appwrite": { "method": "deleteMfaAuthenticator", - "weight": 264, + "weight": 265, "cookies": false, "type": "", "deprecated": false, @@ -21493,7 +21495,7 @@ }, "x-appwrite": { "method": "listMfaFactors", - "weight": 260, + "weight": 261, "cookies": false, "type": "", "deprecated": false, @@ -21557,7 +21559,7 @@ }, "x-appwrite": { "method": "getMfaRecoveryCodes", - "weight": 261, + "weight": 262, "cookies": false, "type": "", "deprecated": false, @@ -21619,7 +21621,7 @@ }, "x-appwrite": { "method": "updateMfaRecoveryCodes", - "weight": 263, + "weight": 264, "cookies": false, "type": "", "deprecated": false, @@ -21681,7 +21683,7 @@ }, "x-appwrite": { "method": "createMfaRecoveryCodes", - "weight": 262, + "weight": 263, "cookies": false, "type": "", "deprecated": false, @@ -21745,7 +21747,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 252, + "weight": 253, "cookies": false, "type": "", "deprecated": false, @@ -21827,7 +21829,7 @@ }, "x-appwrite": { "method": "updatePassword", - "weight": 253, + "weight": 254, "cookies": false, "type": "", "deprecated": false, @@ -21909,7 +21911,7 @@ }, "x-appwrite": { "method": "updatePhone", - "weight": 255, + "weight": 256, "cookies": false, "type": "", "deprecated": false, @@ -21991,7 +21993,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 242, + "weight": 243, "cookies": false, "type": "", "deprecated": false, @@ -22053,7 +22055,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 257, + "weight": 258, "cookies": false, "type": "", "deprecated": false, @@ -22135,7 +22137,7 @@ }, "x-appwrite": { "method": "listSessions", - "weight": 244, + "weight": 245, "cookies": false, "type": "", "deprecated": false, @@ -22197,7 +22199,7 @@ }, "x-appwrite": { "method": "createSession", - "weight": 265, + "weight": 266, "cookies": false, "type": "", "deprecated": false, @@ -22254,7 +22256,7 @@ }, "x-appwrite": { "method": "deleteSessions", - "weight": 268, + "weight": 269, "cookies": false, "type": "", "deprecated": false, @@ -22313,7 +22315,7 @@ }, "x-appwrite": { "method": "deleteSession", - "weight": 267, + "weight": 268, "cookies": false, "type": "", "deprecated": false, @@ -22385,7 +22387,7 @@ }, "x-appwrite": { "method": "updateStatus", - "weight": 249, + "weight": 250, "cookies": false, "type": "", "deprecated": false, @@ -22467,7 +22469,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 247, + "weight": 248, "cookies": false, "type": "", "deprecated": false, @@ -22542,7 +22544,7 @@ }, "x-appwrite": { "method": "createTarget", - "weight": 239, + "weight": 240, "cookies": false, "type": "", "deprecated": false, @@ -22658,7 +22660,7 @@ }, "x-appwrite": { "method": "getTarget", - "weight": 243, + "weight": 244, "cookies": false, "type": "", "deprecated": false, @@ -22729,7 +22731,7 @@ }, "x-appwrite": { "method": "updateTarget", - "weight": 258, + "weight": 259, "cookies": false, "type": "", "deprecated": false, @@ -22824,7 +22826,7 @@ }, "x-appwrite": { "method": "deleteTarget", - "weight": 270, + "weight": 271, "cookies": false, "type": "", "deprecated": false, @@ -22897,7 +22899,7 @@ }, "x-appwrite": { "method": "createToken", - "weight": 266, + "weight": 267, "cookies": false, "type": "", "deprecated": false, @@ -22982,7 +22984,7 @@ }, "x-appwrite": { "method": "updateEmailVerification", - "weight": 256, + "weight": 257, "cookies": false, "type": "", "deprecated": false, @@ -23064,7 +23066,7 @@ }, "x-appwrite": { "method": "updatePhoneVerification", - "weight": 251, + "weight": 252, "cookies": false, "type": "", "deprecated": false, @@ -26082,12 +26084,12 @@ }, "userName": { "type": "string", - "description": "User name.", + "description": "User name. Hide this attribute by toggling membership privacy in the Console.", "x-example": "John Doe" }, "userEmail": { "type": "string", - "description": "User email address.", + "description": "User email address. Hide this attribute by toggling membership privacy in the Console.", "x-example": "john@appwrite.io" }, "teamId": { @@ -26117,7 +26119,7 @@ }, "mfa": { "type": "boolean", - "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise.", + "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise. Hide this attribute by toggling membership privacy in the Console.", "x-example": false }, "roles": { @@ -26283,7 +26285,7 @@ "specification": { "type": "string", "description": "Machine specification for builds and executions.", - "x-example": "s-0.5vcpu-512mb" + "x-example": "s-1vcpu-512mb" } }, "required": [ @@ -27097,7 +27099,7 @@ "slug": { "type": "string", "description": "Size slug.", - "x-example": "s-0.5vcpu-512mb" + "x-example": "s-1vcpu-512mb" } }, "required": [ @@ -27548,7 +27550,7 @@ "name": { "type": "string", "description": "Target Name.", - "x-example": "Aegon apple token" + "x-example": "Apple iPhone 12" }, "userId": { "type": "string", @@ -27570,6 +27572,11 @@ "type": "string", "description": "The target identifier.", "x-example": "token" + }, + "expired": { + "type": "boolean", + "description": "Is the target expired.", + "x-example": false } }, "required": [ @@ -27579,7 +27586,8 @@ "name", "userId", "providerType", - "identifier" + "identifier", + "expired" ] } }, diff --git a/app/config/specs/swagger2-latest-client.json b/app/config/specs/swagger2-latest-client.json index fce6a871a3..3ac70ffe28 100644 --- a/app/config/specs/swagger2-latest-client.json +++ b/app/config/specs/swagger2-latest-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, @@ -222,7 +222,7 @@ "tags": [ "account" ], - "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n", + "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n", "responses": { "200": { "description": "User", @@ -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, @@ -1670,7 +1670,7 @@ "tags": [ "account" ], - "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.", + "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.", "responses": { "200": { "description": "Token", @@ -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, @@ -1915,7 +1915,7 @@ "tags": [ "account" ], - "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Session", @@ -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, @@ -2075,7 +2075,7 @@ "tags": [ "account" ], - "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", + "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", "responses": { "301": { "description": "No content" @@ -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, @@ -2836,7 +2836,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2847,7 +2847,7 @@ }, "x-appwrite": { "method": "createEmailToken", - "weight": 24, + "weight": 25, "cookies": false, "type": "", "deprecated": false, @@ -2922,7 +2922,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", "responses": { "201": { "description": "Token", @@ -2933,7 +2933,7 @@ }, "x-appwrite": { "method": "createMagicURLToken", - "weight": 23, + "weight": 24, "cookies": false, "type": "", "deprecated": false, @@ -3017,7 +3017,7 @@ "tags": [ "account" ], - "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "301": { "description": "No content" @@ -3025,7 +3025,7 @@ }, "x-appwrite": { "method": "createOAuth2Token", - "weight": 22, + "weight": 23, "cookies": false, "type": "webAuth", "deprecated": false, @@ -3152,7 +3152,7 @@ "tags": [ "account" ], - "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -3163,7 +3163,7 @@ }, "x-appwrite": { "method": "createPhoneToken", - "weight": 27, + "weight": 28, "cookies": false, "type": "", "deprecated": false, @@ -3235,7 +3235,7 @@ "tags": [ "account" ], - "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n", + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", "responses": { "201": { "description": "Token", @@ -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, @@ -3528,7 +3528,7 @@ "tags": [ "avatars" ], - "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", + "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", "responses": { "200": { "description": "Image", @@ -3539,7 +3539,7 @@ }, "x-appwrite": { "method": "getBrowser", - "weight": 59, + "weight": 60, "cookies": false, "type": "location", "deprecated": false, @@ -3657,7 +3657,7 @@ "tags": [ "avatars" ], - "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", + "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", "responses": { "200": { "description": "Image", @@ -3668,7 +3668,7 @@ }, "x-appwrite": { "method": "getCreditCard", - "weight": 58, + "weight": 59, "cookies": false, "type": "location", "deprecated": false, @@ -3790,7 +3790,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image", @@ -3801,7 +3801,7 @@ }, "x-appwrite": { "method": "getFavicon", - "weight": 62, + "weight": 63, "cookies": false, "type": "location", "deprecated": false, @@ -3857,7 +3857,7 @@ "tags": [ "avatars" ], - "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", + "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", "responses": { "200": { "description": "Image", @@ -3868,7 +3868,7 @@ }, "x-appwrite": { "method": "getFlag", - "weight": 60, + "weight": 61, "cookies": false, "type": "location", "deprecated": false, @@ -4348,7 +4348,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image", @@ -4359,7 +4359,7 @@ }, "x-appwrite": { "method": "getImage", - "weight": 61, + "weight": 62, "cookies": false, "type": "location", "deprecated": false, @@ -4435,7 +4435,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", + "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", "responses": { "200": { "description": "Image", @@ -4446,7 +4446,7 @@ }, "x-appwrite": { "method": "getInitials", - "weight": 64, + "weight": 65, "cookies": false, "type": "location", "deprecated": false, @@ -4530,7 +4530,7 @@ "tags": [ "avatars" ], - "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n", + "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n", "responses": { "200": { "description": "Image", @@ -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": 306, "cookies": false, "type": "", "deprecated": false, @@ -5167,7 +5167,7 @@ "summary": "Create execution", "operationId": "functionsCreateExecution", "consumes": [ - "multipart\/form-data" + "application\/json" ], "produces": [ "multipart\/form-data" @@ -5186,7 +5186,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 303, + "weight": 305, "cookies": false, "type": "", "deprecated": false, @@ -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 + } + } + } } ] } @@ -5313,7 +5307,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 305, + "weight": 307, "cookies": false, "type": "", "deprecated": false, @@ -5387,7 +5381,7 @@ }, "x-appwrite": { "method": "query", - "weight": 329, + "weight": 331, "cookies": false, "type": "graphql", "deprecated": false, @@ -5463,7 +5457,7 @@ }, "x-appwrite": { "method": "mutation", - "weight": 328, + "weight": 330, "cookies": false, "type": "graphql", "deprecated": false, @@ -5528,7 +5522,7 @@ "tags": [ "locale" ], - "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", + "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", "responses": { "200": { "description": "Locale", @@ -5539,7 +5533,7 @@ }, "x-appwrite": { "method": "get", - "weight": 116, + "weight": 117, "cookies": false, "type": "", "deprecated": false, @@ -5595,7 +5589,7 @@ }, "x-appwrite": { "method": "listCodes", - "weight": 117, + "weight": 118, "cookies": false, "type": "", "deprecated": false, @@ -5651,7 +5645,7 @@ }, "x-appwrite": { "method": "listContinents", - "weight": 121, + "weight": 122, "cookies": false, "type": "", "deprecated": false, @@ -5707,7 +5701,7 @@ }, "x-appwrite": { "method": "listCountries", - "weight": 118, + "weight": 119, "cookies": false, "type": "", "deprecated": false, @@ -5763,7 +5757,7 @@ }, "x-appwrite": { "method": "listCountriesEU", - "weight": 119, + "weight": 120, "cookies": false, "type": "", "deprecated": false, @@ -5819,7 +5813,7 @@ }, "x-appwrite": { "method": "listCountriesPhones", - "weight": 120, + "weight": 121, "cookies": false, "type": "", "deprecated": false, @@ -5875,7 +5869,7 @@ }, "x-appwrite": { "method": "listCurrencies", - "weight": 122, + "weight": 123, "cookies": false, "type": "", "deprecated": false, @@ -5931,7 +5925,7 @@ }, "x-appwrite": { "method": "listLanguages", - "weight": 123, + "weight": 124, "cookies": false, "type": "", "deprecated": false, @@ -5987,7 +5981,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 380, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -6076,7 +6070,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 384, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -6151,7 +6145,7 @@ }, "x-appwrite": { "method": "listFiles", - "weight": 206, + "weight": 208, "cookies": false, "type": "", "deprecated": false, @@ -6225,7 +6219,7 @@ "tags": [ "storage" ], - "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n", + "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n", "responses": { "201": { "description": "File", @@ -6236,7 +6230,7 @@ }, "x-appwrite": { "method": "createFile", - "weight": 205, + "weight": 207, "cookies": false, "type": "upload", "deprecated": false, @@ -6330,7 +6324,7 @@ }, "x-appwrite": { "method": "getFile", - "weight": 207, + "weight": 209, "cookies": false, "type": "", "deprecated": false, @@ -6402,7 +6396,7 @@ }, "x-appwrite": { "method": "updateFile", - "weight": 212, + "weight": 214, "cookies": false, "type": "", "deprecated": false, @@ -6493,7 +6487,7 @@ }, "x-appwrite": { "method": "deleteFile", - "weight": 213, + "weight": 215, "cookies": false, "type": "", "deprecated": false, @@ -6567,7 +6561,7 @@ }, "x-appwrite": { "method": "getFileDownload", - "weight": 209, + "weight": 211, "cookies": false, "type": "location", "deprecated": false, @@ -6641,7 +6635,7 @@ }, "x-appwrite": { "method": "getFilePreview", - "weight": 208, + "weight": 210, "cookies": false, "type": "location", "deprecated": false, @@ -6842,7 +6836,7 @@ }, "x-appwrite": { "method": "getFileView", - "weight": 210, + "weight": 212, "cookies": false, "type": "location", "deprecated": false, @@ -6916,7 +6910,7 @@ }, "x-appwrite": { "method": "list", - "weight": 217, + "weight": 219, "cookies": false, "type": "", "deprecated": false, @@ -6993,7 +6987,7 @@ }, "x-appwrite": { "method": "create", - "weight": 216, + "weight": 218, "cookies": false, "type": "", "deprecated": false, @@ -7087,7 +7081,7 @@ }, "x-appwrite": { "method": "get", - "weight": 218, + "weight": 220, "cookies": false, "type": "", "deprecated": false, @@ -7151,7 +7145,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 220, + "weight": 222, "cookies": false, "type": "", "deprecated": false, @@ -7228,7 +7222,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 222, + "weight": 224, "cookies": false, "type": "", "deprecated": false, @@ -7283,7 +7277,7 @@ "tags": [ "teams" ], - "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint.", + "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Memberships List", @@ -7294,7 +7288,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 224, + "weight": 226, "cookies": false, "type": "", "deprecated": false, @@ -7368,7 +7362,7 @@ "tags": [ "teams" ], - "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n", + "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n", "responses": { "201": { "description": "Membership", @@ -7379,7 +7373,7 @@ }, "x-appwrite": { "method": "createMembership", - "weight": 223, + "weight": 225, "cookies": false, "type": "", "deprecated": false, @@ -7485,7 +7479,7 @@ "tags": [ "teams" ], - "description": "Get a team member by the membership unique id. All team members have read access for this resource.", + "description": "Get a team member by the membership unique id. All team members have read access for this resource. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Membership", @@ -7496,7 +7490,7 @@ }, "x-appwrite": { "method": "getMembership", - "weight": 225, + "weight": 227, "cookies": false, "type": "", "deprecated": false, @@ -7557,7 +7551,7 @@ "tags": [ "teams" ], - "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n", + "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n", "responses": { "200": { "description": "Membership", @@ -7568,7 +7562,7 @@ }, "x-appwrite": { "method": "updateMembership", - "weight": 226, + "weight": 228, "cookies": false, "type": "", "deprecated": false, @@ -7656,7 +7650,7 @@ }, "x-appwrite": { "method": "deleteMembership", - "weight": 228, + "weight": 230, "cookies": false, "type": "", "deprecated": false, @@ -7719,7 +7713,7 @@ "tags": [ "teams" ], - "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n", + "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n", "responses": { "200": { "description": "Membership", @@ -7730,7 +7724,7 @@ }, "x-appwrite": { "method": "updateMembershipStatus", - "weight": 227, + "weight": 229, "cookies": false, "type": "", "deprecated": false, @@ -7828,7 +7822,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 219, + "weight": 221, "cookies": false, "type": "", "deprecated": false, @@ -7891,7 +7885,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 221, + "weight": 223, "cookies": false, "type": "", "deprecated": false, @@ -9451,12 +9445,12 @@ }, "userName": { "type": "string", - "description": "User name.", + "description": "User name. Hide this attribute by toggling membership privacy in the Console.", "x-example": "John Doe" }, "userEmail": { "type": "string", - "description": "User email address.", + "description": "User email address. Hide this attribute by toggling membership privacy in the Console.", "x-example": "john@appwrite.io" }, "teamId": { @@ -9486,7 +9480,7 @@ }, "mfa": { "type": "boolean", - "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise.", + "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise. Hide this attribute by toggling membership privacy in the Console.", "x-example": false }, "roles": { @@ -9590,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": "" }, @@ -10014,7 +10008,7 @@ "name": { "type": "string", "description": "Target Name.", - "x-example": "Aegon apple token" + "x-example": "Apple iPhone 12" }, "userId": { "type": "string", @@ -10036,6 +10030,11 @@ "type": "string", "description": "The target identifier.", "x-example": "token" + }, + "expired": { + "type": "boolean", + "description": "Is the target expired.", + "x-example": false } }, "required": [ @@ -10045,7 +10044,8 @@ "name", "userId", "providerType", - "identifier" + "identifier", + "expired" ] } }, diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index 9200c80593..25c0c289b3 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-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, @@ -278,7 +278,7 @@ "tags": [ "account" ], - "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n", + "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n", "responses": { "200": { "description": "User", @@ -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, @@ -1707,7 +1707,7 @@ "tags": [ "account" ], - "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.", + "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.", "responses": { "200": { "description": "Token", @@ -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, @@ -1949,7 +1949,7 @@ "tags": [ "account" ], - "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Session", @@ -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, @@ -2109,7 +2109,7 @@ "tags": [ "account" ], - "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", + "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", "responses": { "301": { "description": "No content" @@ -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, @@ -2863,7 +2863,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2874,7 +2874,7 @@ }, "x-appwrite": { "method": "createEmailToken", - "weight": 24, + "weight": 25, "cookies": false, "type": "", "deprecated": false, @@ -2949,7 +2949,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", "responses": { "201": { "description": "Token", @@ -2960,7 +2960,7 @@ }, "x-appwrite": { "method": "createMagicURLToken", - "weight": 23, + "weight": 24, "cookies": false, "type": "", "deprecated": false, @@ -3044,7 +3044,7 @@ "tags": [ "account" ], - "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "301": { "description": "No content" @@ -3052,7 +3052,7 @@ }, "x-appwrite": { "method": "createOAuth2Token", - "weight": 22, + "weight": 23, "cookies": false, "type": "webAuth", "deprecated": false, @@ -3179,7 +3179,7 @@ "tags": [ "account" ], - "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -3190,7 +3190,7 @@ }, "x-appwrite": { "method": "createPhoneToken", - "weight": 27, + "weight": 28, "cookies": false, "type": "", "deprecated": false, @@ -3262,7 +3262,7 @@ "tags": [ "account" ], - "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n", + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", "responses": { "201": { "description": "Token", @@ -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, @@ -3551,7 +3551,7 @@ "tags": [ "avatars" ], - "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", + "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", "responses": { "200": { "description": "Image", @@ -3562,7 +3562,7 @@ }, "x-appwrite": { "method": "getBrowser", - "weight": 59, + "weight": 60, "cookies": false, "type": "location", "deprecated": false, @@ -3680,7 +3680,7 @@ "tags": [ "avatars" ], - "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", + "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", "responses": { "200": { "description": "Image", @@ -3691,7 +3691,7 @@ }, "x-appwrite": { "method": "getCreditCard", - "weight": 58, + "weight": 59, "cookies": false, "type": "location", "deprecated": false, @@ -3813,7 +3813,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image", @@ -3824,7 +3824,7 @@ }, "x-appwrite": { "method": "getFavicon", - "weight": 62, + "weight": 63, "cookies": false, "type": "location", "deprecated": false, @@ -3880,7 +3880,7 @@ "tags": [ "avatars" ], - "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", + "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", "responses": { "200": { "description": "Image", @@ -3891,7 +3891,7 @@ }, "x-appwrite": { "method": "getFlag", - "weight": 60, + "weight": 61, "cookies": false, "type": "location", "deprecated": false, @@ -4371,7 +4371,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image", @@ -4382,7 +4382,7 @@ }, "x-appwrite": { "method": "getImage", - "weight": 61, + "weight": 62, "cookies": false, "type": "location", "deprecated": false, @@ -4458,7 +4458,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", + "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", "responses": { "200": { "description": "Image", @@ -4469,7 +4469,7 @@ }, "x-appwrite": { "method": "getInitials", - "weight": 64, + "weight": 65, "cookies": false, "type": "location", "deprecated": false, @@ -4553,7 +4553,7 @@ "tags": [ "avatars" ], - "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n", + "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n", "responses": { "200": { "description": "Image", @@ -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": 333, "cookies": false, "type": "", "deprecated": false, @@ -4731,7 +4731,7 @@ }, "x-appwrite": { "method": "variables", - "weight": 330, + "weight": 332, "cookies": false, "type": "", "deprecated": false, @@ -4783,7 +4783,7 @@ }, "x-appwrite": { "method": "list", - "weight": 69, + "weight": 70, "cookies": false, "type": "", "deprecated": false, @@ -4846,7 +4846,7 @@ "tags": [ "databases" ], - "description": "Create a new Database.\r\n", + "description": "Create a new Database.\n", "responses": { "201": { "description": "Database", @@ -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, @@ -5727,7 +5727,7 @@ "tags": [ "databases" ], - "description": "Create a boolean attribute.\r\n", + "description": "Create a boolean attribute.\n", "responses": { "202": { "description": "AttributeBoolean", @@ -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, @@ -6159,7 +6159,7 @@ "tags": [ "databases" ], - "description": "Create an email attribute.\r\n", + "description": "Create an email attribute.\n", "responses": { "202": { "description": "AttributeEmail", @@ -6170,7 +6170,7 @@ }, "x-appwrite": { "method": "createEmailAttribute", - "weight": 81, + "weight": 82, "cookies": false, "type": "", "deprecated": false, @@ -6265,7 +6265,7 @@ "tags": [ "databases" ], - "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeEmail", @@ -6276,7 +6276,7 @@ }, "x-appwrite": { "method": "updateEmailAttribute", - "weight": 93, + "weight": 94, "cookies": false, "type": "", "deprecated": false, @@ -6375,7 +6375,7 @@ "tags": [ "databases" ], - "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n", + "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n", "responses": { "202": { "description": "AttributeEnum", @@ -6386,7 +6386,7 @@ }, "x-appwrite": { "method": "createEnumAttribute", - "weight": 82, + "weight": 83, "cookies": false, "type": "", "deprecated": false, @@ -6491,7 +6491,7 @@ "tags": [ "databases" ], - "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeEnum", @@ -6502,7 +6502,7 @@ }, "x-appwrite": { "method": "updateEnumAttribute", - "weight": 94, + "weight": 95, "cookies": false, "type": "", "deprecated": false, @@ -6611,7 +6611,7 @@ "tags": [ "databases" ], - "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n", + "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n", "responses": { "202": { "description": "AttributeFloat", @@ -6622,7 +6622,7 @@ }, "x-appwrite": { "method": "createFloatAttribute", - "weight": 86, + "weight": 87, "cookies": false, "type": "", "deprecated": false, @@ -6729,7 +6729,7 @@ "tags": [ "databases" ], - "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeFloat", @@ -6740,7 +6740,7 @@ }, "x-appwrite": { "method": "updateFloatAttribute", - "weight": 98, + "weight": 99, "cookies": false, "type": "", "deprecated": false, @@ -6853,7 +6853,7 @@ "tags": [ "databases" ], - "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n", + "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n", "responses": { "202": { "description": "AttributeInteger", @@ -6864,7 +6864,7 @@ }, "x-appwrite": { "method": "createIntegerAttribute", - "weight": 85, + "weight": 86, "cookies": false, "type": "", "deprecated": false, @@ -6971,7 +6971,7 @@ "tags": [ "databases" ], - "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeInteger", @@ -6982,7 +6982,7 @@ }, "x-appwrite": { "method": "updateIntegerAttribute", - "weight": 97, + "weight": 98, "cookies": false, "type": "", "deprecated": false, @@ -7095,7 +7095,7 @@ "tags": [ "databases" ], - "description": "Create IP address attribute.\r\n", + "description": "Create IP address attribute.\n", "responses": { "202": { "description": "AttributeIP", @@ -7106,7 +7106,7 @@ }, "x-appwrite": { "method": "createIpAttribute", - "weight": 83, + "weight": 84, "cookies": false, "type": "", "deprecated": false, @@ -7201,7 +7201,7 @@ "tags": [ "databases" ], - "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeIP", @@ -7212,7 +7212,7 @@ }, "x-appwrite": { "method": "updateIpAttribute", - "weight": 95, + "weight": 96, "cookies": false, "type": "", "deprecated": false, @@ -7311,7 +7311,7 @@ "tags": [ "databases" ], - "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", + "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", "responses": { "202": { "description": "AttributeRelationship", @@ -7322,7 +7322,7 @@ }, "x-appwrite": { "method": "createRelationshipAttribute", - "weight": 89, + "weight": 90, "cookies": false, "type": "", "deprecated": false, @@ -7446,7 +7446,7 @@ "tags": [ "databases" ], - "description": "Create a string attribute.\r\n", + "description": "Create a string attribute.\n", "responses": { "202": { "description": "AttributeString", @@ -7457,7 +7457,7 @@ }, "x-appwrite": { "method": "createStringAttribute", - "weight": 80, + "weight": 81, "cookies": false, "type": "", "deprecated": false, @@ -7565,7 +7565,7 @@ "tags": [ "databases" ], - "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeString", @@ -7576,7 +7576,7 @@ }, "x-appwrite": { "method": "updateStringAttribute", - "weight": 92, + "weight": 93, "cookies": false, "type": "", "deprecated": false, @@ -7650,7 +7650,7 @@ "type": "integer", "description": "Maximum size of the string attribute.", "default": null, - "x-example": null + "x-example": 1 }, "newKey": { "type": "string", @@ -7681,7 +7681,7 @@ "tags": [ "databases" ], - "description": "Create a URL attribute.\r\n", + "description": "Create a URL attribute.\n", "responses": { "202": { "description": "AttributeURL", @@ -7692,7 +7692,7 @@ }, "x-appwrite": { "method": "createUrlAttribute", - "weight": 84, + "weight": 85, "cookies": false, "type": "", "deprecated": false, @@ -7787,7 +7787,7 @@ "tags": [ "databases" ], - "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeURL", @@ -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, @@ -8075,7 +8075,7 @@ "tags": [ "databases" ], - "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", + "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", "responses": { "200": { "description": "AttributeRelationship", @@ -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, @@ -8817,7 +8817,7 @@ "tags": [ "databases" ], - "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.", + "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.", "responses": { "202": { "description": "Index", @@ -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": 289, "cookies": false, "type": "", "deprecated": false, @@ -9501,7 +9501,7 @@ }, "x-appwrite": { "method": "create", - "weight": 286, + "weight": 288, "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,24 +9587,30 @@ "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", - "go-1.23" + "bun-1.1", + "go-1.23", + "static-1" ], "x-enum-name": null, "x-enum-keys": [] @@ -9726,7 +9735,7 @@ "specification": { "type": "string", "description": "Runtime specification for the function and builds.", - "default": "s-0.5vcpu-512mb", + "default": "s-1vcpu-512mb", "x-example": null } }, @@ -9764,7 +9773,7 @@ }, "x-appwrite": { "method": "listRuntimes", - "weight": 288, + "weight": 290, "cookies": false, "type": "", "deprecated": false, @@ -9806,7 +9815,7 @@ "tags": [ "functions" ], - "description": "List allowed function specifications for this instance.\r\n", + "description": "List allowed function specifications for this instance.\n", "responses": { "200": { "description": "Specifications List", @@ -9817,7 +9826,7 @@ }, "x-appwrite": { "method": "listSpecifications", - "weight": 289, + "weight": 291, "cookies": false, "type": "", "deprecated": false, @@ -9871,7 +9880,7 @@ }, "x-appwrite": { "method": "listTemplates", - "weight": 312, + "weight": 314, "cookies": false, "type": "", "deprecated": false, @@ -9969,7 +9978,7 @@ }, "x-appwrite": { "method": "getTemplate", - "weight": 313, + "weight": 315, "cookies": false, "type": "", "deprecated": false, @@ -10031,7 +10040,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 292, + "weight": 294, "cookies": false, "type": "", "deprecated": false, @@ -10105,7 +10114,7 @@ }, "x-appwrite": { "method": "get", - "weight": 290, + "weight": 292, "cookies": false, "type": "", "deprecated": false, @@ -10166,7 +10175,7 @@ }, "x-appwrite": { "method": "update", - "weight": 293, + "weight": 295, "cookies": false, "type": "", "deprecated": false, @@ -10226,6 +10235,7 @@ "node-19.0", "node-20.0", "node-21.0", + "node-22", "php-8.0", "php-8.1", "php-8.2", @@ -10244,6 +10254,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,24 +10263,30 @@ "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", - "go-1.23" + "bun-1.1", + "go-1.23", + "static-1" ], "x-enum-name": null, "x-enum-keys": [] @@ -10370,7 +10388,7 @@ "specification": { "type": "string", "description": "Runtime specification for the function and builds.", - "default": "s-0.5vcpu-512mb", + "default": "s-1vcpu-512mb", "x-example": null } }, @@ -10399,7 +10417,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 296, + "weight": 298, "cookies": false, "type": "", "deprecated": false, @@ -10462,7 +10480,7 @@ }, "x-appwrite": { "method": "listDeployments", - "weight": 298, + "weight": 300, "cookies": false, "type": "", "deprecated": false, @@ -10533,7 +10551,7 @@ "tags": [ "functions" ], - "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\r\n\r\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.", + "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\n\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.", "responses": { "202": { "description": "Deployment", @@ -10544,7 +10562,7 @@ }, "x-appwrite": { "method": "createDeployment", - "weight": 297, + "weight": 299, "cookies": false, "type": "upload", "deprecated": false, @@ -10638,7 +10656,7 @@ }, "x-appwrite": { "method": "getDeployment", - "weight": 299, + "weight": 301, "cookies": false, "type": "", "deprecated": false, @@ -10707,7 +10725,7 @@ }, "x-appwrite": { "method": "updateDeployment", - "weight": 295, + "weight": 297, "cookies": false, "type": "", "deprecated": false, @@ -10771,7 +10789,7 @@ }, "x-appwrite": { "method": "deleteDeployment", - "weight": 300, + "weight": 302, "cookies": false, "type": "", "deprecated": false, @@ -10837,7 +10855,7 @@ }, "x-appwrite": { "method": "createBuild", - "weight": 301, + "weight": 303, "cookies": false, "type": "", "deprecated": false, @@ -10921,7 +10939,7 @@ }, "x-appwrite": { "method": "updateDeploymentBuild", - "weight": 302, + "weight": 304, "cookies": false, "type": "", "deprecated": false, @@ -10992,7 +11010,7 @@ }, "x-appwrite": { "method": "getDeploymentDownload", - "weight": 294, + "weight": 296, "cookies": false, "type": "location", "deprecated": false, @@ -11065,7 +11083,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 304, + "weight": 306, "cookies": false, "type": "", "deprecated": false, @@ -11131,7 +11149,7 @@ "summary": "Create execution", "operationId": "functionsCreateExecution", "consumes": [ - "multipart\/form-data" + "application\/json" ], "produces": [ "multipart\/form-data" @@ -11150,7 +11168,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 303, + "weight": 305, "cookies": false, "type": "", "deprecated": false, @@ -11190,65 +11208,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 + } + } + } } ] } @@ -11277,7 +11289,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 305, + "weight": 307, "cookies": false, "type": "", "deprecated": false, @@ -11336,7 +11348,7 @@ "tags": [ "functions" ], - "description": "Delete a function execution by its unique ID.\r\n", + "description": "Delete a function execution by its unique ID.\n", "responses": { "204": { "description": "No content" @@ -11344,7 +11356,7 @@ }, "x-appwrite": { "method": "deleteExecution", - "weight": 306, + "weight": 308, "cookies": false, "type": "", "deprecated": false, @@ -11415,7 +11427,7 @@ }, "x-appwrite": { "method": "getFunctionUsage", - "weight": 291, + "weight": 293, "cookies": false, "type": "", "deprecated": false, @@ -11497,7 +11509,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 308, + "weight": 310, "cookies": false, "type": "", "deprecated": false, @@ -11558,7 +11570,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 307, + "weight": 309, "cookies": false, "type": "", "deprecated": false, @@ -11646,7 +11658,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 309, + "weight": 311, "cookies": false, "type": "", "deprecated": false, @@ -11715,7 +11727,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 310, + "weight": 312, "cookies": false, "type": "", "deprecated": false, @@ -11803,7 +11815,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 311, + "weight": 313, "cookies": false, "type": "", "deprecated": false, @@ -11874,7 +11886,7 @@ }, "x-appwrite": { "method": "query", - "weight": 329, + "weight": 331, "cookies": false, "type": "graphql", "deprecated": false, @@ -11950,7 +11962,7 @@ }, "x-appwrite": { "method": "mutation", - "weight": 328, + "weight": 330, "cookies": false, "type": "graphql", "deprecated": false, @@ -12026,7 +12038,7 @@ }, "x-appwrite": { "method": "get", - "weight": 124, + "weight": 125, "cookies": false, "type": "", "deprecated": false, @@ -12079,7 +12091,7 @@ }, "x-appwrite": { "method": "getAntivirus", - "weight": 146, + "weight": 147, "cookies": false, "type": "", "deprecated": false, @@ -12132,7 +12144,7 @@ }, "x-appwrite": { "method": "getCache", - "weight": 127, + "weight": 128, "cookies": false, "type": "", "deprecated": false, @@ -12185,7 +12197,7 @@ }, "x-appwrite": { "method": "getCertificate", - "weight": 133, + "weight": 134, "cookies": false, "type": "", "deprecated": false, @@ -12247,7 +12259,7 @@ }, "x-appwrite": { "method": "getDB", - "weight": 126, + "weight": 127, "cookies": false, "type": "", "deprecated": false, @@ -12300,7 +12312,7 @@ }, "x-appwrite": { "method": "getPubSub", - "weight": 129, + "weight": 130, "cookies": false, "type": "", "deprecated": false, @@ -12353,7 +12365,7 @@ }, "x-appwrite": { "method": "getQueue", - "weight": 128, + "weight": 129, "cookies": false, "type": "", "deprecated": false, @@ -12406,7 +12418,7 @@ }, "x-appwrite": { "method": "getQueueBuilds", - "weight": 135, + "weight": 136, "cookies": false, "type": "", "deprecated": false, @@ -12470,7 +12482,7 @@ }, "x-appwrite": { "method": "getQueueCertificates", - "weight": 134, + "weight": 135, "cookies": false, "type": "", "deprecated": false, @@ -12534,7 +12546,7 @@ }, "x-appwrite": { "method": "getQueueDatabases", - "weight": 136, + "weight": 137, "cookies": false, "type": "", "deprecated": false, @@ -12607,7 +12619,7 @@ }, "x-appwrite": { "method": "getQueueDeletes", - "weight": 137, + "weight": 138, "cookies": false, "type": "", "deprecated": false, @@ -12660,7 +12672,7 @@ "tags": [ "health" ], - "description": "Returns the amount of failed jobs in a given queue.\r\n", + "description": "Returns the amount of failed jobs in a given queue.\n", "responses": { "200": { "description": "Health Queue", @@ -12671,7 +12683,7 @@ }, "x-appwrite": { "method": "getFailedJobs", - "weight": 147, + "weight": 148, "cookies": false, "type": "", "deprecated": false, @@ -12759,7 +12771,7 @@ }, "x-appwrite": { "method": "getQueueFunctions", - "weight": 141, + "weight": 142, "cookies": false, "type": "", "deprecated": false, @@ -12823,7 +12835,7 @@ }, "x-appwrite": { "method": "getQueueLogs", - "weight": 132, + "weight": 133, "cookies": false, "type": "", "deprecated": false, @@ -12887,7 +12899,7 @@ }, "x-appwrite": { "method": "getQueueMails", - "weight": 138, + "weight": 139, "cookies": false, "type": "", "deprecated": false, @@ -12951,7 +12963,7 @@ }, "x-appwrite": { "method": "getQueueMessaging", - "weight": 139, + "weight": 140, "cookies": false, "type": "", "deprecated": false, @@ -13015,7 +13027,7 @@ }, "x-appwrite": { "method": "getQueueMigrations", - "weight": 140, + "weight": 141, "cookies": false, "type": "", "deprecated": false, @@ -13079,7 +13091,7 @@ }, "x-appwrite": { "method": "getQueueUsage", - "weight": 142, + "weight": 143, "cookies": false, "type": "", "deprecated": false, @@ -13143,7 +13155,7 @@ }, "x-appwrite": { "method": "getQueueUsageDump", - "weight": 143, + "weight": 144, "cookies": false, "type": "", "deprecated": false, @@ -13207,7 +13219,7 @@ }, "x-appwrite": { "method": "getQueueWebhooks", - "weight": 131, + "weight": 132, "cookies": false, "type": "", "deprecated": false, @@ -13271,7 +13283,7 @@ }, "x-appwrite": { "method": "getStorage", - "weight": 145, + "weight": 146, "cookies": false, "type": "", "deprecated": false, @@ -13324,7 +13336,7 @@ }, "x-appwrite": { "method": "getStorageLocal", - "weight": 144, + "weight": 145, "cookies": false, "type": "", "deprecated": false, @@ -13377,7 +13389,7 @@ }, "x-appwrite": { "method": "getTime", - "weight": 130, + "weight": 131, "cookies": false, "type": "", "deprecated": false, @@ -13419,7 +13431,7 @@ "tags": [ "locale" ], - "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", + "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", "responses": { "200": { "description": "Locale", @@ -13430,7 +13442,7 @@ }, "x-appwrite": { "method": "get", - "weight": 116, + "weight": 117, "cookies": false, "type": "", "deprecated": false, @@ -13486,7 +13498,7 @@ }, "x-appwrite": { "method": "listCodes", - "weight": 117, + "weight": 118, "cookies": false, "type": "", "deprecated": false, @@ -13542,7 +13554,7 @@ }, "x-appwrite": { "method": "listContinents", - "weight": 121, + "weight": 122, "cookies": false, "type": "", "deprecated": false, @@ -13598,7 +13610,7 @@ }, "x-appwrite": { "method": "listCountries", - "weight": 118, + "weight": 119, "cookies": false, "type": "", "deprecated": false, @@ -13654,7 +13666,7 @@ }, "x-appwrite": { "method": "listCountriesEU", - "weight": 119, + "weight": 120, "cookies": false, "type": "", "deprecated": false, @@ -13710,7 +13722,7 @@ }, "x-appwrite": { "method": "listCountriesPhones", - "weight": 120, + "weight": 121, "cookies": false, "type": "", "deprecated": false, @@ -13766,7 +13778,7 @@ }, "x-appwrite": { "method": "listCurrencies", - "weight": 122, + "weight": 123, "cookies": false, "type": "", "deprecated": false, @@ -13822,7 +13834,7 @@ }, "x-appwrite": { "method": "listLanguages", - "weight": 123, + "weight": 124, "cookies": false, "type": "", "deprecated": false, @@ -13878,7 +13890,7 @@ }, "x-appwrite": { "method": "listMessages", - "weight": 388, + "weight": 390, "cookies": false, "type": "", "deprecated": false, @@ -13955,7 +13967,7 @@ }, "x-appwrite": { "method": "createEmail", - "weight": 385, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -14104,7 +14116,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\r\n", + "description": "Update an email message by its unique ID.\n", "responses": { "200": { "description": "Message", @@ -14115,7 +14127,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 392, + "weight": 394, "cookies": false, "type": "", "deprecated": false, @@ -14272,7 +14284,7 @@ }, "x-appwrite": { "method": "createPush", - "weight": 387, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -14436,7 +14448,7 @@ "tags": [ "messaging" ], - "description": "Update a push notification by its unique ID.\r\n", + "description": "Update a push notification by its unique ID.\n", "responses": { "200": { "description": "Message", @@ -14447,7 +14459,7 @@ }, "x-appwrite": { "method": "updatePush", - "weight": 394, + "weight": 396, "cookies": false, "type": "", "deprecated": false, @@ -14619,7 +14631,7 @@ }, "x-appwrite": { "method": "createSms", - "weight": 386, + "weight": 388, "cookies": false, "type": "", "deprecated": false, @@ -14728,7 +14740,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\r\n", + "description": "Update an email message by its unique ID.\n", "responses": { "200": { "description": "Message", @@ -14739,7 +14751,7 @@ }, "x-appwrite": { "method": "updateSms", - "weight": 393, + "weight": 395, "cookies": false, "type": "", "deprecated": false, @@ -14846,7 +14858,7 @@ "tags": [ "messaging" ], - "description": "Get a message by its unique ID.\r\n", + "description": "Get a message by its unique ID.\n", "responses": { "200": { "description": "Message", @@ -14857,7 +14869,7 @@ }, "x-appwrite": { "method": "getMessage", - "weight": 391, + "weight": 393, "cookies": false, "type": "", "deprecated": false, @@ -14916,7 +14928,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 395, + "weight": 397, "cookies": false, "type": "", "deprecated": false, @@ -14980,7 +14992,7 @@ }, "x-appwrite": { "method": "listMessageLogs", - "weight": 389, + "weight": 391, "cookies": false, "type": "", "deprecated": false, @@ -15056,7 +15068,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 390, + "weight": 392, "cookies": false, "type": "", "deprecated": false, @@ -15132,7 +15144,7 @@ }, "x-appwrite": { "method": "listProviders", - "weight": 360, + "weight": 362, "cookies": false, "type": "", "deprecated": false, @@ -15209,7 +15221,7 @@ }, "x-appwrite": { "method": "createApnsProvider", - "weight": 359, + "weight": 361, "cookies": false, "type": "", "deprecated": false, @@ -15326,7 +15338,7 @@ }, "x-appwrite": { "method": "updateApnsProvider", - "weight": 372, + "weight": 374, "cookies": false, "type": "", "deprecated": false, @@ -15441,7 +15453,7 @@ }, "x-appwrite": { "method": "createFcmProvider", - "weight": 358, + "weight": 360, "cookies": false, "type": "", "deprecated": false, @@ -15534,7 +15546,7 @@ }, "x-appwrite": { "method": "updateFcmProvider", - "weight": 371, + "weight": 373, "cookies": false, "type": "", "deprecated": false, @@ -15625,7 +15637,7 @@ }, "x-appwrite": { "method": "createMailgunProvider", - "weight": 350, + "weight": 352, "cookies": false, "type": "", "deprecated": false, @@ -15754,7 +15766,7 @@ }, "x-appwrite": { "method": "updateMailgunProvider", - "weight": 363, + "weight": 365, "cookies": false, "type": "", "deprecated": false, @@ -15881,7 +15893,7 @@ }, "x-appwrite": { "method": "createMsg91Provider", - "weight": 353, + "weight": 355, "cookies": false, "type": "", "deprecated": false, @@ -15986,7 +15998,7 @@ }, "x-appwrite": { "method": "updateMsg91Provider", - "weight": 366, + "weight": 368, "cookies": false, "type": "", "deprecated": false, @@ -16089,7 +16101,7 @@ }, "x-appwrite": { "method": "createSendgridProvider", - "weight": 351, + "weight": 353, "cookies": false, "type": "", "deprecated": false, @@ -16206,7 +16218,7 @@ }, "x-appwrite": { "method": "updateSendgridProvider", - "weight": 364, + "weight": 366, "cookies": false, "type": "", "deprecated": false, @@ -16321,7 +16333,7 @@ }, "x-appwrite": { "method": "createSmtpProvider", - "weight": 352, + "weight": 354, "cookies": false, "type": "", "deprecated": false, @@ -16482,7 +16494,7 @@ }, "x-appwrite": { "method": "updateSmtpProvider", - "weight": 365, + "weight": 367, "cookies": false, "type": "", "deprecated": false, @@ -16640,7 +16652,7 @@ }, "x-appwrite": { "method": "createTelesignProvider", - "weight": 354, + "weight": 356, "cookies": false, "type": "", "deprecated": false, @@ -16745,7 +16757,7 @@ }, "x-appwrite": { "method": "updateTelesignProvider", - "weight": 367, + "weight": 369, "cookies": false, "type": "", "deprecated": false, @@ -16848,7 +16860,7 @@ }, "x-appwrite": { "method": "createTextmagicProvider", - "weight": 355, + "weight": 357, "cookies": false, "type": "", "deprecated": false, @@ -16953,7 +16965,7 @@ }, "x-appwrite": { "method": "updateTextmagicProvider", - "weight": 368, + "weight": 370, "cookies": false, "type": "", "deprecated": false, @@ -17056,7 +17068,7 @@ }, "x-appwrite": { "method": "createTwilioProvider", - "weight": 356, + "weight": 358, "cookies": false, "type": "", "deprecated": false, @@ -17161,7 +17173,7 @@ }, "x-appwrite": { "method": "updateTwilioProvider", - "weight": 369, + "weight": 371, "cookies": false, "type": "", "deprecated": false, @@ -17264,7 +17276,7 @@ }, "x-appwrite": { "method": "createVonageProvider", - "weight": 357, + "weight": 359, "cookies": false, "type": "", "deprecated": false, @@ -17369,7 +17381,7 @@ }, "x-appwrite": { "method": "updateVonageProvider", - "weight": 370, + "weight": 372, "cookies": false, "type": "", "deprecated": false, @@ -17461,7 +17473,7 @@ "tags": [ "messaging" ], - "description": "Get a provider by its unique ID.\r\n", + "description": "Get a provider by its unique ID.\n", "responses": { "200": { "description": "Provider", @@ -17472,7 +17484,7 @@ }, "x-appwrite": { "method": "getProvider", - "weight": 362, + "weight": 364, "cookies": false, "type": "", "deprecated": false, @@ -17531,7 +17543,7 @@ }, "x-appwrite": { "method": "deleteProvider", - "weight": 373, + "weight": 375, "cookies": false, "type": "", "deprecated": false, @@ -17595,7 +17607,7 @@ }, "x-appwrite": { "method": "listProviderLogs", - "weight": 361, + "weight": 363, "cookies": false, "type": "", "deprecated": false, @@ -17671,7 +17683,7 @@ }, "x-appwrite": { "method": "listSubscriberLogs", - "weight": 382, + "weight": 384, "cookies": false, "type": "", "deprecated": false, @@ -17747,7 +17759,7 @@ }, "x-appwrite": { "method": "listTopics", - "weight": 375, + "weight": 377, "cookies": false, "type": "", "deprecated": false, @@ -17822,7 +17834,7 @@ }, "x-appwrite": { "method": "createTopic", - "weight": 374, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -17903,7 +17915,7 @@ "tags": [ "messaging" ], - "description": "Get a topic by its unique ID.\r\n", + "description": "Get a topic by its unique ID.\n", "responses": { "200": { "description": "Topic", @@ -17914,7 +17926,7 @@ }, "x-appwrite": { "method": "getTopic", - "weight": 377, + "weight": 379, "cookies": false, "type": "", "deprecated": false, @@ -17965,7 +17977,7 @@ "tags": [ "messaging" ], - "description": "Update a topic by its unique ID.\r\n", + "description": "Update a topic by its unique ID.\n", "responses": { "200": { "description": "Topic", @@ -17976,7 +17988,7 @@ }, "x-appwrite": { "method": "updateTopic", - "weight": 378, + "weight": 380, "cookies": false, "type": "", "deprecated": false, @@ -18059,7 +18071,7 @@ }, "x-appwrite": { "method": "deleteTopic", - "weight": 379, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -18123,7 +18135,7 @@ }, "x-appwrite": { "method": "listTopicLogs", - "weight": 376, + "weight": 378, "cookies": false, "type": "", "deprecated": false, @@ -18199,7 +18211,7 @@ }, "x-appwrite": { "method": "listSubscribers", - "weight": 381, + "weight": 383, "cookies": false, "type": "", "deprecated": false, @@ -18282,7 +18294,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 380, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -18363,7 +18375,7 @@ "tags": [ "messaging" ], - "description": "Get a subscriber by its unique ID.\r\n", + "description": "Get a subscriber by its unique ID.\n", "responses": { "200": { "description": "Subscriber", @@ -18374,7 +18386,7 @@ }, "x-appwrite": { "method": "getSubscriber", - "weight": 383, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -18441,7 +18453,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 384, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -18516,7 +18528,7 @@ }, "x-appwrite": { "method": "list", - "weight": 337, + "weight": 339, "cookies": false, "type": "", "deprecated": false, @@ -18545,7 +18557,7 @@ "parameters": [ { "name": "queries", - "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/databases#querying-documents). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: status, stage, source, resources, statusCounters, resourceData, errors", + "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/databases#querying-documents). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: status, stage, source, destination, resources, statusCounters, resourceData, errors", "required": false, "type": "array", "collectionFormat": "multi", @@ -18591,7 +18603,7 @@ }, "x-appwrite": { "method": "createAppwriteMigration", - "weight": 332, + "weight": 334, "cookies": false, "type": "", "deprecated": false, @@ -18687,7 +18699,7 @@ }, "x-appwrite": { "method": "getAppwriteReport", - "weight": 339, + "weight": 341, "cookies": false, "type": "", "deprecated": false, @@ -18777,7 +18789,7 @@ }, "x-appwrite": { "method": "createFirebaseMigration", - "weight": 334, + "weight": 336, "cookies": false, "type": "", "deprecated": false, @@ -18859,7 +18871,7 @@ }, "x-appwrite": { "method": "deleteFirebaseAuth", - "weight": 345, + "weight": 347, "cookies": false, "type": "", "deprecated": false, @@ -18911,7 +18923,7 @@ }, "x-appwrite": { "method": "createFirebaseOAuthMigration", - "weight": 333, + "weight": 335, "cookies": false, "type": "", "deprecated": false, @@ -18993,7 +19005,7 @@ }, "x-appwrite": { "method": "listFirebaseProjects", - "weight": 344, + "weight": 346, "cookies": false, "type": "", "deprecated": false, @@ -19045,7 +19057,7 @@ }, "x-appwrite": { "method": "getFirebaseReport", - "weight": 340, + "weight": 342, "cookies": false, "type": "", "deprecated": false, @@ -19118,7 +19130,7 @@ }, "x-appwrite": { "method": "getFirebaseReportOAuth", - "weight": 341, + "weight": 343, "cookies": false, "type": "", "deprecated": false, @@ -19191,7 +19203,7 @@ }, "x-appwrite": { "method": "createNHostMigration", - "weight": 336, + "weight": 338, "cookies": false, "type": "", "deprecated": false, @@ -19314,7 +19326,7 @@ }, "x-appwrite": { "method": "getNHostReport", - "weight": 347, + "weight": 349, "cookies": false, "type": "", "deprecated": false, @@ -19436,7 +19448,7 @@ }, "x-appwrite": { "method": "createSupabaseMigration", - "weight": 335, + "weight": 337, "cookies": false, "type": "", "deprecated": false, @@ -19552,7 +19564,7 @@ }, "x-appwrite": { "method": "getSupabaseReport", - "weight": 346, + "weight": 348, "cookies": false, "type": "", "deprecated": false, @@ -19667,7 +19679,7 @@ }, "x-appwrite": { "method": "get", - "weight": 338, + "weight": 340, "cookies": false, "type": "", "deprecated": false, @@ -19727,7 +19739,7 @@ }, "x-appwrite": { "method": "retry", - "weight": 348, + "weight": 350, "cookies": false, "type": "", "deprecated": false, @@ -19782,7 +19794,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 349, + "weight": 351, "cookies": false, "type": "", "deprecated": false, @@ -19844,7 +19856,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 194, + "weight": 196, "cookies": false, "type": "", "deprecated": false, @@ -19930,7 +19942,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 196, + "weight": 198, "cookies": false, "type": "", "deprecated": false, @@ -19980,7 +19992,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 195, + "weight": 197, "cookies": false, "type": "", "deprecated": false, @@ -20059,7 +20071,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 197, + "weight": 199, "cookies": false, "type": "", "deprecated": false, @@ -20119,7 +20131,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 198, + "weight": 200, "cookies": false, "type": "", "deprecated": false, @@ -20198,7 +20210,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 199, + "weight": 201, "cookies": false, "type": "", "deprecated": false, @@ -20260,7 +20272,7 @@ }, "x-appwrite": { "method": "list", - "weight": 150, + "weight": 151, "cookies": false, "type": "", "deprecated": false, @@ -20333,7 +20345,7 @@ }, "x-appwrite": { "method": "create", - "weight": 149, + "weight": 150, "cookies": false, "type": "", "deprecated": false, @@ -20485,7 +20497,7 @@ }, "x-appwrite": { "method": "get", - "weight": 151, + "weight": 152, "cookies": false, "type": "", "deprecated": false, @@ -20545,7 +20557,7 @@ }, "x-appwrite": { "method": "update", - "weight": 152, + "weight": 153, "cookies": false, "type": "", "deprecated": false, @@ -20672,7 +20684,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 168, + "weight": 170, "cookies": false, "type": "", "deprecated": false, @@ -20734,7 +20746,7 @@ }, "x-appwrite": { "method": "updateApiStatus", - "weight": 156, + "weight": 157, "cookies": false, "type": "", "deprecated": false, @@ -20828,7 +20840,7 @@ }, "x-appwrite": { "method": "updateApiStatusAll", - "weight": 157, + "weight": 158, "cookies": false, "type": "", "deprecated": false, @@ -20908,7 +20920,7 @@ }, "x-appwrite": { "method": "updateAuthDuration", - "weight": 161, + "weight": 163, "cookies": false, "type": "", "deprecated": false, @@ -20988,7 +21000,7 @@ }, "x-appwrite": { "method": "updateAuthLimit", - "weight": 160, + "weight": 162, "cookies": false, "type": "", "deprecated": false, @@ -21068,7 +21080,7 @@ }, "x-appwrite": { "method": "updateAuthSessionsLimit", - "weight": 166, + "weight": 168, "cookies": false, "type": "", "deprecated": false, @@ -21124,6 +21136,100 @@ ] } }, + "\/projects\/{projectId}\/auth\/memberships-privacy": { + "patch": { + "summary": "Update project memberships privacy attributes", + "operationId": "projectsUpdateMembershipsPrivacy", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "projects" + ], + "description": "", + "responses": { + "200": { + "description": "Project", + "schema": { + "$ref": "#\/definitions\/project" + } + } + }, + "x-appwrite": { + "method": "updateMembershipsPrivacy", + "weight": 161, + "cookies": false, + "type": "", + "deprecated": false, + "demo": "projects\/update-memberships-privacy.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "projects.write", + "platforms": [ + "console" + ], + "packaging": false, + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [] + } + ], + "parameters": [ + { + "name": "projectId", + "description": "Project unique ID.", + "required": true, + "type": "string", + "x-example": "", + "in": "path" + }, + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "userName": { + "type": "boolean", + "description": "Set to true to show userName to members of a team.", + "default": null, + "x-example": false + }, + "userEmail": { + "type": "boolean", + "description": "Set to true to show email to members of a team.", + "default": null, + "x-example": false + }, + "mfa": { + "type": "boolean", + "description": "Set to true to show mfa to members of a team.", + "default": null, + "x-example": false + } + }, + "required": [ + "userName", + "userEmail", + "mfa" + ] + } + } + ] + } + }, "\/projects\/{projectId}\/auth\/mock-numbers": { "patch": { "summary": "Update the mock numbers for the project", @@ -21148,7 +21254,7 @@ }, "x-appwrite": { "method": "updateMockNumbers", - "weight": 167, + "weight": 169, "cookies": false, "type": "", "deprecated": false, @@ -21231,7 +21337,7 @@ }, "x-appwrite": { "method": "updateAuthPasswordDictionary", - "weight": 164, + "weight": 166, "cookies": false, "type": "", "deprecated": false, @@ -21311,7 +21417,7 @@ }, "x-appwrite": { "method": "updateAuthPasswordHistory", - "weight": 163, + "weight": 165, "cookies": false, "type": "", "deprecated": false, @@ -21391,7 +21497,7 @@ }, "x-appwrite": { "method": "updatePersonalDataCheck", - "weight": 165, + "weight": 167, "cookies": false, "type": "", "deprecated": false, @@ -21471,7 +21577,7 @@ }, "x-appwrite": { "method": "updateSessionAlerts", - "weight": 159, + "weight": 160, "cookies": false, "type": "", "deprecated": false, @@ -21551,7 +21657,7 @@ }, "x-appwrite": { "method": "updateAuthStatus", - "weight": 162, + "weight": 164, "cookies": false, "type": "", "deprecated": false, @@ -21650,7 +21756,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 180, + "weight": 182, "cookies": false, "type": "", "deprecated": false, @@ -21739,7 +21845,7 @@ }, "x-appwrite": { "method": "listKeys", - "weight": 176, + "weight": 178, "cookies": false, "type": "", "deprecated": false, @@ -21799,7 +21905,7 @@ }, "x-appwrite": { "method": "createKey", - "weight": 175, + "weight": 177, "cookies": false, "type": "", "deprecated": false, @@ -21895,7 +22001,7 @@ }, "x-appwrite": { "method": "getKey", - "weight": 177, + "weight": 179, "cookies": false, "type": "", "deprecated": false, @@ -21963,7 +22069,7 @@ }, "x-appwrite": { "method": "updateKey", - "weight": 178, + "weight": 180, "cookies": false, "type": "", "deprecated": false, @@ -22060,7 +22166,7 @@ }, "x-appwrite": { "method": "deleteKey", - "weight": 179, + "weight": 181, "cookies": false, "type": "", "deprecated": false, @@ -22130,7 +22236,7 @@ }, "x-appwrite": { "method": "updateOAuth2", - "weight": 158, + "weight": 159, "cookies": false, "type": "", "deprecated": false, @@ -22271,7 +22377,7 @@ }, "x-appwrite": { "method": "listPlatforms", - "weight": 182, + "weight": 184, "cookies": false, "type": "", "deprecated": false, @@ -22331,7 +22437,7 @@ }, "x-appwrite": { "method": "createPlatform", - "weight": 181, + "weight": 183, "cookies": false, "type": "", "deprecated": false, @@ -22455,7 +22561,7 @@ }, "x-appwrite": { "method": "getPlatform", - "weight": 183, + "weight": 185, "cookies": false, "type": "", "deprecated": false, @@ -22523,7 +22629,7 @@ }, "x-appwrite": { "method": "updatePlatform", - "weight": 184, + "weight": 186, "cookies": false, "type": "", "deprecated": false, @@ -22622,7 +22728,7 @@ }, "x-appwrite": { "method": "deletePlatform", - "weight": 185, + "weight": 187, "cookies": false, "type": "", "deprecated": false, @@ -22692,7 +22798,7 @@ }, "x-appwrite": { "method": "updateServiceStatus", - "weight": 154, + "weight": 155, "cookies": false, "type": "", "deprecated": false, @@ -22794,7 +22900,7 @@ }, "x-appwrite": { "method": "updateServiceStatusAll", - "weight": 155, + "weight": 156, "cookies": false, "type": "", "deprecated": false, @@ -22874,7 +22980,7 @@ }, "x-appwrite": { "method": "updateSmtp", - "weight": 186, + "weight": 188, "cookies": false, "type": "", "deprecated": false, @@ -23003,7 +23109,7 @@ }, "x-appwrite": { "method": "createSmtpTest", - "weight": 187, + "weight": 189, "cookies": false, "type": "", "deprecated": false, @@ -23143,7 +23249,7 @@ }, "x-appwrite": { "method": "updateTeam", - "weight": 153, + "weight": 154, "cookies": false, "type": "", "deprecated": false, @@ -23223,7 +23329,7 @@ }, "x-appwrite": { "method": "getEmailTemplate", - "weight": 189, + "weight": 191, "cookies": false, "type": "", "deprecated": false, @@ -23445,7 +23551,7 @@ }, "x-appwrite": { "method": "updateEmailTemplate", - "weight": 191, + "weight": 193, "cookies": false, "type": "", "deprecated": false, @@ -23710,7 +23816,7 @@ }, "x-appwrite": { "method": "deleteEmailTemplate", - "weight": 193, + "weight": 195, "cookies": false, "type": "", "deprecated": false, @@ -23934,7 +24040,7 @@ }, "x-appwrite": { "method": "getSmsTemplate", - "weight": 188, + "weight": 190, "cookies": false, "type": "", "deprecated": false, @@ -24153,7 +24259,7 @@ }, "x-appwrite": { "method": "updateSmsTemplate", - "weight": 190, + "weight": 192, "cookies": false, "type": "", "deprecated": false, @@ -24390,7 +24496,7 @@ }, "x-appwrite": { "method": "deleteSmsTemplate", - "weight": 192, + "weight": 194, "cookies": false, "type": "", "deprecated": false, @@ -24611,7 +24717,7 @@ }, "x-appwrite": { "method": "listWebhooks", - "weight": 170, + "weight": 172, "cookies": false, "type": "", "deprecated": false, @@ -24671,7 +24777,7 @@ }, "x-appwrite": { "method": "createWebhook", - "weight": 169, + "weight": 171, "cookies": false, "type": "", "deprecated": false, @@ -24793,7 +24899,7 @@ }, "x-appwrite": { "method": "getWebhook", - "weight": 171, + "weight": 173, "cookies": false, "type": "", "deprecated": false, @@ -24861,7 +24967,7 @@ }, "x-appwrite": { "method": "updateWebhook", - "weight": 172, + "weight": 174, "cookies": false, "type": "", "deprecated": false, @@ -24984,7 +25090,7 @@ }, "x-appwrite": { "method": "deleteWebhook", - "weight": 174, + "weight": 176, "cookies": false, "type": "", "deprecated": false, @@ -25054,7 +25160,7 @@ }, "x-appwrite": { "method": "updateWebhookSignature", - "weight": 173, + "weight": 175, "cookies": false, "type": "", "deprecated": false, @@ -25124,7 +25230,7 @@ }, "x-appwrite": { "method": "listRules", - "weight": 315, + "weight": 317, "cookies": false, "type": "", "deprecated": false, @@ -25197,7 +25303,7 @@ }, "x-appwrite": { "method": "createRule", - "weight": 314, + "weight": 316, "cookies": false, "type": "", "deprecated": false, @@ -25288,7 +25394,7 @@ }, "x-appwrite": { "method": "getRule", - "weight": 316, + "weight": 318, "cookies": false, "type": "", "deprecated": false, @@ -25343,7 +25449,7 @@ }, "x-appwrite": { "method": "deleteRule", - "weight": 317, + "weight": 319, "cookies": false, "type": "", "deprecated": false, @@ -25405,7 +25511,7 @@ }, "x-appwrite": { "method": "updateRuleVerification", - "weight": 318, + "weight": 320, "cookies": false, "type": "", "deprecated": false, @@ -25467,7 +25573,7 @@ }, "x-appwrite": { "method": "listBuckets", - "weight": 201, + "weight": 203, "cookies": false, "type": "", "deprecated": false, @@ -25541,7 +25647,7 @@ }, "x-appwrite": { "method": "createBucket", - "weight": 200, + "weight": 202, "cookies": false, "type": "", "deprecated": false, @@ -25682,7 +25788,7 @@ }, "x-appwrite": { "method": "getBucket", - "weight": 202, + "weight": 204, "cookies": false, "type": "", "deprecated": false, @@ -25743,7 +25849,7 @@ }, "x-appwrite": { "method": "updateBucket", - "weight": 203, + "weight": 205, "cookies": false, "type": "", "deprecated": false, @@ -25878,7 +25984,7 @@ }, "x-appwrite": { "method": "deleteBucket", - "weight": 204, + "weight": 206, "cookies": false, "type": "", "deprecated": false, @@ -25941,7 +26047,7 @@ }, "x-appwrite": { "method": "listFiles", - "weight": 206, + "weight": 208, "cookies": false, "type": "", "deprecated": false, @@ -26015,7 +26121,7 @@ "tags": [ "storage" ], - "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n", + "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n", "responses": { "201": { "description": "File", @@ -26026,7 +26132,7 @@ }, "x-appwrite": { "method": "createFile", - "weight": 205, + "weight": 207, "cookies": false, "type": "upload", "deprecated": false, @@ -26120,7 +26226,7 @@ }, "x-appwrite": { "method": "getFile", - "weight": 207, + "weight": 209, "cookies": false, "type": "", "deprecated": false, @@ -26192,7 +26298,7 @@ }, "x-appwrite": { "method": "updateFile", - "weight": 212, + "weight": 214, "cookies": false, "type": "", "deprecated": false, @@ -26283,7 +26389,7 @@ }, "x-appwrite": { "method": "deleteFile", - "weight": 213, + "weight": 215, "cookies": false, "type": "", "deprecated": false, @@ -26357,7 +26463,7 @@ }, "x-appwrite": { "method": "getFileDownload", - "weight": 209, + "weight": 211, "cookies": false, "type": "location", "deprecated": false, @@ -26431,7 +26537,7 @@ }, "x-appwrite": { "method": "getFilePreview", - "weight": 208, + "weight": 210, "cookies": false, "type": "location", "deprecated": false, @@ -26632,7 +26738,7 @@ }, "x-appwrite": { "method": "getFileView", - "weight": 210, + "weight": 212, "cookies": false, "type": "location", "deprecated": false, @@ -26706,7 +26812,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 214, + "weight": 216, "cookies": false, "type": "", "deprecated": false, @@ -26780,7 +26886,7 @@ }, "x-appwrite": { "method": "getBucketUsage", - "weight": 215, + "weight": 217, "cookies": false, "type": "", "deprecated": false, @@ -26862,7 +26968,7 @@ }, "x-appwrite": { "method": "list", - "weight": 217, + "weight": 219, "cookies": false, "type": "", "deprecated": false, @@ -26939,7 +27045,7 @@ }, "x-appwrite": { "method": "create", - "weight": 216, + "weight": 218, "cookies": false, "type": "", "deprecated": false, @@ -27033,7 +27139,7 @@ }, "x-appwrite": { "method": "get", - "weight": 218, + "weight": 220, "cookies": false, "type": "", "deprecated": false, @@ -27097,7 +27203,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 220, + "weight": 222, "cookies": false, "type": "", "deprecated": false, @@ -27174,7 +27280,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 222, + "weight": 224, "cookies": false, "type": "", "deprecated": false, @@ -27240,7 +27346,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 229, + "weight": 231, "cookies": false, "type": "", "deprecated": false, @@ -27303,7 +27409,7 @@ "tags": [ "teams" ], - "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint.", + "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Memberships List", @@ -27314,7 +27420,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 224, + "weight": 226, "cookies": false, "type": "", "deprecated": false, @@ -27388,7 +27494,7 @@ "tags": [ "teams" ], - "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n", + "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n", "responses": { "201": { "description": "Membership", @@ -27399,7 +27505,7 @@ }, "x-appwrite": { "method": "createMembership", - "weight": 223, + "weight": 225, "cookies": false, "type": "", "deprecated": false, @@ -27505,7 +27611,7 @@ "tags": [ "teams" ], - "description": "Get a team member by the membership unique id. All team members have read access for this resource.", + "description": "Get a team member by the membership unique id. All team members have read access for this resource. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Membership", @@ -27516,7 +27622,7 @@ }, "x-appwrite": { "method": "getMembership", - "weight": 225, + "weight": 227, "cookies": false, "type": "", "deprecated": false, @@ -27577,7 +27683,7 @@ "tags": [ "teams" ], - "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n", + "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n", "responses": { "200": { "description": "Membership", @@ -27588,7 +27694,7 @@ }, "x-appwrite": { "method": "updateMembership", - "weight": 226, + "weight": 228, "cookies": false, "type": "", "deprecated": false, @@ -27676,7 +27782,7 @@ }, "x-appwrite": { "method": "deleteMembership", - "weight": 228, + "weight": 230, "cookies": false, "type": "", "deprecated": false, @@ -27739,7 +27845,7 @@ "tags": [ "teams" ], - "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n", + "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n", "responses": { "200": { "description": "Membership", @@ -27750,7 +27856,7 @@ }, "x-appwrite": { "method": "updateMembershipStatus", - "weight": 227, + "weight": 229, "cookies": false, "type": "", "deprecated": false, @@ -27847,7 +27953,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 219, + "weight": 221, "cookies": false, "type": "", "deprecated": false, @@ -27909,7 +28015,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 221, + "weight": 223, "cookies": false, "type": "", "deprecated": false, @@ -27991,7 +28097,7 @@ }, "x-appwrite": { "method": "list", - "weight": 239, + "weight": 241, "cookies": false, "type": "", "deprecated": false, @@ -28065,7 +28171,7 @@ }, "x-appwrite": { "method": "create", - "weight": 230, + "weight": 232, "cookies": false, "type": "", "deprecated": false, @@ -28162,7 +28268,7 @@ }, "x-appwrite": { "method": "createArgon2User", - "weight": 233, + "weight": 235, "cookies": false, "type": "", "deprecated": false, @@ -28255,7 +28361,7 @@ }, "x-appwrite": { "method": "createBcryptUser", - "weight": 231, + "weight": 233, "cookies": false, "type": "", "deprecated": false, @@ -28348,7 +28454,7 @@ }, "x-appwrite": { "method": "listIdentities", - "weight": 247, + "weight": 249, "cookies": false, "type": "", "deprecated": false, @@ -28419,7 +28525,7 @@ }, "x-appwrite": { "method": "deleteIdentity", - "weight": 270, + "weight": 272, "cookies": false, "type": "", "deprecated": false, @@ -28482,7 +28588,7 @@ }, "x-appwrite": { "method": "createMD5User", - "weight": 232, + "weight": 234, "cookies": false, "type": "", "deprecated": false, @@ -28575,7 +28681,7 @@ }, "x-appwrite": { "method": "createPHPassUser", - "weight": 235, + "weight": 237, "cookies": false, "type": "", "deprecated": false, @@ -28668,7 +28774,7 @@ }, "x-appwrite": { "method": "createScryptUser", - "weight": 236, + "weight": 238, "cookies": false, "type": "", "deprecated": false, @@ -28796,7 +28902,7 @@ }, "x-appwrite": { "method": "createScryptModifiedUser", - "weight": 237, + "weight": 239, "cookies": false, "type": "", "deprecated": false, @@ -28910,7 +29016,7 @@ }, "x-appwrite": { "method": "createSHAUser", - "weight": 234, + "weight": 236, "cookies": false, "type": "", "deprecated": false, @@ -29024,7 +29130,7 @@ }, "x-appwrite": { "method": "getUsage", - "weight": 272, + "weight": 274, "cookies": false, "type": "", "deprecated": false, @@ -29098,7 +29204,7 @@ }, "x-appwrite": { "method": "get", - "weight": 240, + "weight": 242, "cookies": false, "type": "", "deprecated": false, @@ -29154,7 +29260,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 268, + "weight": 270, "cookies": false, "type": "", "deprecated": false, @@ -29217,7 +29323,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 253, + "weight": 255, "cookies": false, "type": "", "deprecated": false, @@ -29298,7 +29404,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 271, + "weight": 273, "cookies": false, "type": "", "deprecated": false, @@ -29371,7 +29477,7 @@ "tags": [ "users" ], - "description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", + "description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", "responses": { "200": { "description": "User", @@ -29382,7 +29488,7 @@ }, "x-appwrite": { "method": "updateLabels", - "weight": 249, + "weight": 251, "cookies": false, "type": "", "deprecated": false, @@ -29466,7 +29572,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 245, + "weight": 247, "cookies": false, "type": "", "deprecated": false, @@ -29541,7 +29647,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 244, + "weight": 246, "cookies": false, "type": "", "deprecated": false, @@ -29604,7 +29710,7 @@ }, "x-appwrite": { "method": "updateMfa", - "weight": 258, + "weight": 260, "cookies": false, "type": "", "deprecated": false, @@ -29685,7 +29791,7 @@ }, "x-appwrite": { "method": "deleteMfaAuthenticator", - "weight": 263, + "weight": 265, "cookies": false, "type": "", "deprecated": false, @@ -29761,7 +29867,7 @@ }, "x-appwrite": { "method": "listMfaFactors", - "weight": 259, + "weight": 261, "cookies": false, "type": "", "deprecated": false, @@ -29824,7 +29930,7 @@ }, "x-appwrite": { "method": "getMfaRecoveryCodes", - "weight": 260, + "weight": 262, "cookies": false, "type": "", "deprecated": false, @@ -29885,7 +29991,7 @@ }, "x-appwrite": { "method": "updateMfaRecoveryCodes", - "weight": 262, + "weight": 264, "cookies": false, "type": "", "deprecated": false, @@ -29946,7 +30052,7 @@ }, "x-appwrite": { "method": "createMfaRecoveryCodes", - "weight": 261, + "weight": 263, "cookies": false, "type": "", "deprecated": false, @@ -30009,7 +30115,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 251, + "weight": 253, "cookies": false, "type": "", "deprecated": false, @@ -30090,7 +30196,7 @@ }, "x-appwrite": { "method": "updatePassword", - "weight": 252, + "weight": 254, "cookies": false, "type": "", "deprecated": false, @@ -30171,7 +30277,7 @@ }, "x-appwrite": { "method": "updatePhone", - "weight": 254, + "weight": 256, "cookies": false, "type": "", "deprecated": false, @@ -30252,7 +30358,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 241, + "weight": 243, "cookies": false, "type": "", "deprecated": false, @@ -30313,7 +30419,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 256, + "weight": 258, "cookies": false, "type": "", "deprecated": false, @@ -30394,7 +30500,7 @@ }, "x-appwrite": { "method": "listSessions", - "weight": 243, + "weight": 245, "cookies": false, "type": "", "deprecated": false, @@ -30444,7 +30550,7 @@ "tags": [ "users" ], - "description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", + "description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", "responses": { "201": { "description": "Session", @@ -30455,7 +30561,7 @@ }, "x-appwrite": { "method": "createSession", - "weight": 264, + "weight": 266, "cookies": false, "type": "", "deprecated": false, @@ -30511,7 +30617,7 @@ }, "x-appwrite": { "method": "deleteSessions", - "weight": 267, + "weight": 269, "cookies": false, "type": "", "deprecated": false, @@ -30569,7 +30675,7 @@ }, "x-appwrite": { "method": "deleteSession", - "weight": 266, + "weight": 268, "cookies": false, "type": "", "deprecated": false, @@ -30640,7 +30746,7 @@ }, "x-appwrite": { "method": "updateStatus", - "weight": 248, + "weight": 250, "cookies": false, "type": "", "deprecated": false, @@ -30721,7 +30827,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 246, + "weight": 248, "cookies": false, "type": "", "deprecated": false, @@ -30795,7 +30901,7 @@ }, "x-appwrite": { "method": "createTarget", - "weight": 238, + "weight": 240, "cookies": false, "type": "", "deprecated": false, @@ -30910,7 +31016,7 @@ }, "x-appwrite": { "method": "getTarget", - "weight": 242, + "weight": 244, "cookies": false, "type": "", "deprecated": false, @@ -30980,7 +31086,7 @@ }, "x-appwrite": { "method": "updateTarget", - "weight": 257, + "weight": 259, "cookies": false, "type": "", "deprecated": false, @@ -31074,7 +31180,7 @@ }, "x-appwrite": { "method": "deleteTarget", - "weight": 269, + "weight": 271, "cookies": false, "type": "", "deprecated": false, @@ -31135,7 +31241,7 @@ "tags": [ "users" ], - "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n", + "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n", "responses": { "201": { "description": "Token", @@ -31146,7 +31252,7 @@ }, "x-appwrite": { "method": "createToken", - "weight": 265, + "weight": 267, "cookies": false, "type": "", "deprecated": false, @@ -31230,7 +31336,7 @@ }, "x-appwrite": { "method": "updateEmailVerification", - "weight": 255, + "weight": 257, "cookies": false, "type": "", "deprecated": false, @@ -31311,7 +31417,7 @@ }, "x-appwrite": { "method": "updatePhoneVerification", - "weight": 250, + "weight": 252, "cookies": false, "type": "", "deprecated": false, @@ -31392,7 +31498,7 @@ }, "x-appwrite": { "method": "listRepositories", - "weight": 277, + "weight": 279, "cookies": false, "type": "", "deprecated": false, @@ -31461,7 +31567,7 @@ }, "x-appwrite": { "method": "createRepository", - "weight": 278, + "weight": 280, "cookies": false, "type": "", "deprecated": false, @@ -31548,7 +31654,7 @@ }, "x-appwrite": { "method": "getRepository", - "weight": 279, + "weight": 281, "cookies": false, "type": "", "deprecated": false, @@ -31618,7 +31724,7 @@ }, "x-appwrite": { "method": "listRepositoryBranches", - "weight": 280, + "weight": 282, "cookies": false, "type": "", "deprecated": false, @@ -31688,7 +31794,7 @@ }, "x-appwrite": { "method": "getRepositoryContents", - "weight": 275, + "weight": 277, "cookies": false, "type": "", "deprecated": false, @@ -31767,7 +31873,7 @@ }, "x-appwrite": { "method": "createRepositoryDetection", - "weight": 276, + "weight": 278, "cookies": false, "type": "", "deprecated": false, @@ -31847,7 +31953,7 @@ }, "x-appwrite": { "method": "updateExternalDeployments", - "weight": 285, + "weight": 287, "cookies": false, "type": "", "deprecated": false, @@ -31935,7 +32041,7 @@ }, "x-appwrite": { "method": "listInstallations", - "weight": 282, + "weight": 284, "cookies": false, "type": "", "deprecated": false, @@ -32010,7 +32116,7 @@ }, "x-appwrite": { "method": "getInstallation", - "weight": 283, + "weight": 285, "cookies": false, "type": "", "deprecated": false, @@ -32065,7 +32171,7 @@ }, "x-appwrite": { "method": "deleteInstallation", - "weight": 284, + "weight": 286, "cookies": false, "type": "", "deprecated": false, @@ -35363,12 +35469,12 @@ }, "userName": { "type": "string", - "description": "User name.", + "description": "User name. Hide this attribute by toggling membership privacy in the Console.", "x-example": "John Doe" }, "userEmail": { "type": "string", - "description": "User email address.", + "description": "User email address. Hide this attribute by toggling membership privacy in the Console.", "x-example": "john@appwrite.io" }, "teamId": { @@ -35398,7 +35504,7 @@ }, "mfa": { "type": "boolean", - "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise.", + "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise. Hide this attribute by toggling membership privacy in the Console.", "x-example": false }, "roles": { @@ -35564,7 +35670,7 @@ "specification": { "type": "string", "description": "Machine specification for builds and executions.", - "x-example": "s-0.5vcpu-512mb" + "x-example": "s-1vcpu-512mb" } }, "required": [ @@ -36238,7 +36344,7 @@ "format": "int32" }, "responseBody": { - "type": "payload", + "type": "string", "description": "HTTP response body. This will return empty unless execution is created as synchronous.", "x-example": "" }, @@ -36485,6 +36591,21 @@ "description": "Whether or not to send session alert emails to users.", "x-example": true }, + "authMembershipsUserName": { + "type": "boolean", + "description": "Whether or not to show user names in the teams membership response.", + "x-example": true + }, + "authMembershipsUserEmail": { + "type": "boolean", + "description": "Whether or not to show user emails in the teams membership response.", + "x-example": true + }, + "authMembershipsMfa": { + "type": "boolean", + "description": "Whether or not to show user MFA status in the teams membership response.", + "x-example": true + }, "oAuthProviders": { "type": "array", "description": "List of Auth Providers.", @@ -36569,6 +36690,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", @@ -36683,6 +36815,9 @@ "authPersonalDataCheck", "authMockNumbers", "authSessionAlerts", + "authMembershipsUserName", + "authMembershipsUserEmail", + "authMembershipsMfa", "oAuthProviders", "platforms", "webhooks", @@ -36696,6 +36831,8 @@ "smtpUsername", "smtpPassword", "smtpSecure", + "pingCount", + "pingedAt", "authEmailPassword", "authUsersAuthMagicURL", "authEmailOtp", @@ -38303,7 +38440,7 @@ "slug": { "type": "string", "description": "Size slug.", - "x-example": "s-0.5vcpu-512mb" + "x-example": "s-1vcpu-512mb" } }, "required": [ @@ -38944,7 +39081,7 @@ "name": { "type": "string", "description": "Target Name.", - "x-example": "Aegon apple token" + "x-example": "Apple iPhone 12" }, "userId": { "type": "string", @@ -38966,6 +39103,11 @@ "type": "string", "description": "The target identifier.", "x-example": "token" + }, + "expired": { + "type": "boolean", + "description": "Is the target expired.", + "x-example": false } }, "required": [ @@ -38975,7 +39117,8 @@ "name", "userId", "providerType", - "identifier" + "identifier", + "expired" ] }, "migration": { @@ -38989,7 +39132,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": { @@ -39012,9 +39155,14 @@ "description": "A string containing the type of source of the migration.", "x-example": "Appwrite" }, + "destination": { + "type": "string", + "description": "A string containing the type of destination of the migration.", + "x-example": "Appwrite" + }, "resources": { "type": "array", - "description": "Resources to migration.", + "description": "Resources to migrate.", "items": { "type": "string" }, @@ -39050,6 +39198,7 @@ "status", "stage", "source", + "destination", "resources", "statusCounters", "resourceData", diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index 80c9e6037f..68bcf268fb 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-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, @@ -238,7 +238,7 @@ "tags": [ "account" ], - "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n", + "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n", "responses": { "200": { "description": "User", @@ -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, @@ -1705,7 +1705,7 @@ "tags": [ "account" ], - "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.", + "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.", "responses": { "200": { "description": "Token", @@ -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, @@ -1953,7 +1953,7 @@ "tags": [ "account" ], - "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Session", @@ -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, @@ -2518,7 +2518,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2529,7 +2529,7 @@ }, "x-appwrite": { "method": "createEmailToken", - "weight": 24, + "weight": 25, "cookies": false, "type": "", "deprecated": false, @@ -2604,7 +2604,7 @@ "tags": [ "account" ], - "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n", + "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n", "responses": { "201": { "description": "Token", @@ -2615,7 +2615,7 @@ }, "x-appwrite": { "method": "createMagicURLToken", - "weight": 23, + "weight": 24, "cookies": false, "type": "", "deprecated": false, @@ -2699,7 +2699,7 @@ "tags": [ "account" ], - "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "301": { "description": "No content" @@ -2707,7 +2707,7 @@ }, "x-appwrite": { "method": "createOAuth2Token", - "weight": 22, + "weight": 23, "cookies": false, "type": "webAuth", "deprecated": false, @@ -2834,7 +2834,7 @@ "tags": [ "account" ], - "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", + "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).", "responses": { "201": { "description": "Token", @@ -2845,7 +2845,7 @@ }, "x-appwrite": { "method": "createPhoneToken", - "weight": 27, + "weight": 28, "cookies": false, "type": "", "deprecated": false, @@ -2917,7 +2917,7 @@ "tags": [ "account" ], - "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n", + "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n", "responses": { "201": { "description": "Token", @@ -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, @@ -3214,7 +3214,7 @@ "tags": [ "avatars" ], - "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", + "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.", "responses": { "200": { "description": "Image", @@ -3225,7 +3225,7 @@ }, "x-appwrite": { "method": "getBrowser", - "weight": 59, + "weight": 60, "cookies": false, "type": "location", "deprecated": false, @@ -3345,7 +3345,7 @@ "tags": [ "avatars" ], - "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", + "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", "responses": { "200": { "description": "Image", @@ -3356,7 +3356,7 @@ }, "x-appwrite": { "method": "getCreditCard", - "weight": 58, + "weight": 59, "cookies": false, "type": "location", "deprecated": false, @@ -3480,7 +3480,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image", @@ -3491,7 +3491,7 @@ }, "x-appwrite": { "method": "getFavicon", - "weight": 62, + "weight": 63, "cookies": false, "type": "location", "deprecated": false, @@ -3549,7 +3549,7 @@ "tags": [ "avatars" ], - "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", + "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", "responses": { "200": { "description": "Image", @@ -3560,7 +3560,7 @@ }, "x-appwrite": { "method": "getFlag", - "weight": 60, + "weight": 61, "cookies": false, "type": "location", "deprecated": false, @@ -4042,7 +4042,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.", + "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.", "responses": { "200": { "description": "Image", @@ -4053,7 +4053,7 @@ }, "x-appwrite": { "method": "getImage", - "weight": 61, + "weight": 62, "cookies": false, "type": "location", "deprecated": false, @@ -4131,7 +4131,7 @@ "tags": [ "avatars" ], - "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n", + "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n", "responses": { "200": { "description": "Image", @@ -4142,7 +4142,7 @@ }, "x-appwrite": { "method": "getInitials", - "weight": 64, + "weight": 65, "cookies": false, "type": "location", "deprecated": false, @@ -4228,7 +4228,7 @@ "tags": [ "avatars" ], - "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n", + "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n", "responses": { "200": { "description": "Image", @@ -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, @@ -4400,7 +4400,7 @@ "tags": [ "databases" ], - "description": "Create a new Database.\r\n", + "description": "Create a new Database.\n", "responses": { "201": { "description": "Database", @@ -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, @@ -5217,7 +5217,7 @@ "tags": [ "databases" ], - "description": "Create a boolean attribute.\r\n", + "description": "Create a boolean attribute.\n", "responses": { "202": { "description": "AttributeBoolean", @@ -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, @@ -5653,7 +5653,7 @@ "tags": [ "databases" ], - "description": "Create an email attribute.\r\n", + "description": "Create an email attribute.\n", "responses": { "202": { "description": "AttributeEmail", @@ -5664,7 +5664,7 @@ }, "x-appwrite": { "method": "createEmailAttribute", - "weight": 81, + "weight": 82, "cookies": false, "type": "", "deprecated": false, @@ -5760,7 +5760,7 @@ "tags": [ "databases" ], - "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeEmail", @@ -5771,7 +5771,7 @@ }, "x-appwrite": { "method": "updateEmailAttribute", - "weight": 93, + "weight": 94, "cookies": false, "type": "", "deprecated": false, @@ -5871,7 +5871,7 @@ "tags": [ "databases" ], - "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n", + "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n", "responses": { "202": { "description": "AttributeEnum", @@ -5882,7 +5882,7 @@ }, "x-appwrite": { "method": "createEnumAttribute", - "weight": 82, + "weight": 83, "cookies": false, "type": "", "deprecated": false, @@ -5988,7 +5988,7 @@ "tags": [ "databases" ], - "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeEnum", @@ -5999,7 +5999,7 @@ }, "x-appwrite": { "method": "updateEnumAttribute", - "weight": 94, + "weight": 95, "cookies": false, "type": "", "deprecated": false, @@ -6109,7 +6109,7 @@ "tags": [ "databases" ], - "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n", + "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n", "responses": { "202": { "description": "AttributeFloat", @@ -6120,7 +6120,7 @@ }, "x-appwrite": { "method": "createFloatAttribute", - "weight": 86, + "weight": 87, "cookies": false, "type": "", "deprecated": false, @@ -6228,7 +6228,7 @@ "tags": [ "databases" ], - "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeFloat", @@ -6239,7 +6239,7 @@ }, "x-appwrite": { "method": "updateFloatAttribute", - "weight": 98, + "weight": 99, "cookies": false, "type": "", "deprecated": false, @@ -6353,7 +6353,7 @@ "tags": [ "databases" ], - "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n", + "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n", "responses": { "202": { "description": "AttributeInteger", @@ -6364,7 +6364,7 @@ }, "x-appwrite": { "method": "createIntegerAttribute", - "weight": 85, + "weight": 86, "cookies": false, "type": "", "deprecated": false, @@ -6472,7 +6472,7 @@ "tags": [ "databases" ], - "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeInteger", @@ -6483,7 +6483,7 @@ }, "x-appwrite": { "method": "updateIntegerAttribute", - "weight": 97, + "weight": 98, "cookies": false, "type": "", "deprecated": false, @@ -6597,7 +6597,7 @@ "tags": [ "databases" ], - "description": "Create IP address attribute.\r\n", + "description": "Create IP address attribute.\n", "responses": { "202": { "description": "AttributeIP", @@ -6608,7 +6608,7 @@ }, "x-appwrite": { "method": "createIpAttribute", - "weight": 83, + "weight": 84, "cookies": false, "type": "", "deprecated": false, @@ -6704,7 +6704,7 @@ "tags": [ "databases" ], - "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeIP", @@ -6715,7 +6715,7 @@ }, "x-appwrite": { "method": "updateIpAttribute", - "weight": 95, + "weight": 96, "cookies": false, "type": "", "deprecated": false, @@ -6815,7 +6815,7 @@ "tags": [ "databases" ], - "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", + "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", "responses": { "202": { "description": "AttributeRelationship", @@ -6826,7 +6826,7 @@ }, "x-appwrite": { "method": "createRelationshipAttribute", - "weight": 89, + "weight": 90, "cookies": false, "type": "", "deprecated": false, @@ -6951,7 +6951,7 @@ "tags": [ "databases" ], - "description": "Create a string attribute.\r\n", + "description": "Create a string attribute.\n", "responses": { "202": { "description": "AttributeString", @@ -6962,7 +6962,7 @@ }, "x-appwrite": { "method": "createStringAttribute", - "weight": 80, + "weight": 81, "cookies": false, "type": "", "deprecated": false, @@ -7071,7 +7071,7 @@ "tags": [ "databases" ], - "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeString", @@ -7082,7 +7082,7 @@ }, "x-appwrite": { "method": "updateStringAttribute", - "weight": 92, + "weight": 93, "cookies": false, "type": "", "deprecated": false, @@ -7157,7 +7157,7 @@ "type": "integer", "description": "Maximum size of the string attribute.", "default": null, - "x-example": null + "x-example": 1 }, "newKey": { "type": "string", @@ -7188,7 +7188,7 @@ "tags": [ "databases" ], - "description": "Create a URL attribute.\r\n", + "description": "Create a URL attribute.\n", "responses": { "202": { "description": "AttributeURL", @@ -7199,7 +7199,7 @@ }, "x-appwrite": { "method": "createUrlAttribute", - "weight": 84, + "weight": 85, "cookies": false, "type": "", "deprecated": false, @@ -7295,7 +7295,7 @@ "tags": [ "databases" ], - "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n", + "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n", "responses": { "200": { "description": "AttributeURL", @@ -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, @@ -7586,7 +7586,7 @@ "tags": [ "databases" ], - "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n", + "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n", "responses": { "200": { "description": "AttributeRelationship", @@ -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, @@ -8250,7 +8250,7 @@ "tags": [ "databases" ], - "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.", + "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.", "responses": { "202": { "description": "Index", @@ -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": 289, "cookies": false, "type": "", "deprecated": false, @@ -8610,7 +8610,7 @@ }, "x-appwrite": { "method": "create", - "weight": 286, + "weight": 288, "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,24 +8697,30 @@ "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", - "go-1.23" + "bun-1.1", + "go-1.23", + "static-1" ], "x-enum-name": null, "x-enum-keys": [] @@ -8836,7 +8845,7 @@ "specification": { "type": "string", "description": "Runtime specification for the function and builds.", - "default": "s-0.5vcpu-512mb", + "default": "s-1vcpu-512mb", "x-example": null } }, @@ -8874,7 +8883,7 @@ }, "x-appwrite": { "method": "listRuntimes", - "weight": 288, + "weight": 290, "cookies": false, "type": "", "deprecated": false, @@ -8917,7 +8926,7 @@ "tags": [ "functions" ], - "description": "List allowed function specifications for this instance.\r\n", + "description": "List allowed function specifications for this instance.\n", "responses": { "200": { "description": "Specifications List", @@ -8928,7 +8937,7 @@ }, "x-appwrite": { "method": "listSpecifications", - "weight": 289, + "weight": 291, "cookies": false, "type": "", "deprecated": false, @@ -8983,7 +8992,7 @@ }, "x-appwrite": { "method": "get", - "weight": 290, + "weight": 292, "cookies": false, "type": "", "deprecated": false, @@ -9045,7 +9054,7 @@ }, "x-appwrite": { "method": "update", - "weight": 293, + "weight": 295, "cookies": false, "type": "", "deprecated": false, @@ -9106,6 +9115,7 @@ "node-19.0", "node-20.0", "node-21.0", + "node-22", "php-8.0", "php-8.1", "php-8.2", @@ -9124,6 +9134,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,24 +9143,30 @@ "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", - "go-1.23" + "bun-1.1", + "go-1.23", + "static-1" ], "x-enum-name": null, "x-enum-keys": [] @@ -9250,7 +9268,7 @@ "specification": { "type": "string", "description": "Runtime specification for the function and builds.", - "default": "s-0.5vcpu-512mb", + "default": "s-1vcpu-512mb", "x-example": null } }, @@ -9279,7 +9297,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 296, + "weight": 298, "cookies": false, "type": "", "deprecated": false, @@ -9343,7 +9361,7 @@ }, "x-appwrite": { "method": "listDeployments", - "weight": 298, + "weight": 300, "cookies": false, "type": "", "deprecated": false, @@ -9415,7 +9433,7 @@ "tags": [ "functions" ], - "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\r\n\r\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.", + "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\n\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.", "responses": { "202": { "description": "Deployment", @@ -9426,7 +9444,7 @@ }, "x-appwrite": { "method": "createDeployment", - "weight": 297, + "weight": 299, "cookies": false, "type": "upload", "deprecated": false, @@ -9521,7 +9539,7 @@ }, "x-appwrite": { "method": "getDeployment", - "weight": 299, + "weight": 301, "cookies": false, "type": "", "deprecated": false, @@ -9591,7 +9609,7 @@ }, "x-appwrite": { "method": "updateDeployment", - "weight": 295, + "weight": 297, "cookies": false, "type": "", "deprecated": false, @@ -9656,7 +9674,7 @@ }, "x-appwrite": { "method": "deleteDeployment", - "weight": 300, + "weight": 302, "cookies": false, "type": "", "deprecated": false, @@ -9723,7 +9741,7 @@ }, "x-appwrite": { "method": "createBuild", - "weight": 301, + "weight": 303, "cookies": false, "type": "", "deprecated": false, @@ -9808,7 +9826,7 @@ }, "x-appwrite": { "method": "updateDeploymentBuild", - "weight": 302, + "weight": 304, "cookies": false, "type": "", "deprecated": false, @@ -9880,7 +9898,7 @@ }, "x-appwrite": { "method": "getDeploymentDownload", - "weight": 294, + "weight": 296, "cookies": false, "type": "location", "deprecated": false, @@ -9954,7 +9972,7 @@ }, "x-appwrite": { "method": "listExecutions", - "weight": 304, + "weight": 306, "cookies": false, "type": "", "deprecated": false, @@ -10022,7 +10040,7 @@ "summary": "Create execution", "operationId": "functionsCreateExecution", "consumes": [ - "multipart\/form-data" + "application\/json" ], "produces": [ "multipart\/form-data" @@ -10041,7 +10059,7 @@ }, "x-appwrite": { "method": "createExecution", - "weight": 303, + "weight": 305, "cookies": false, "type": "", "deprecated": false, @@ -10083,65 +10101,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 + } + } + } } ] } @@ -10170,7 +10182,7 @@ }, "x-appwrite": { "method": "getExecution", - "weight": 305, + "weight": 307, "cookies": false, "type": "", "deprecated": false, @@ -10231,7 +10243,7 @@ "tags": [ "functions" ], - "description": "Delete a function execution by its unique ID.\r\n", + "description": "Delete a function execution by its unique ID.\n", "responses": { "204": { "description": "No content" @@ -10239,7 +10251,7 @@ }, "x-appwrite": { "method": "deleteExecution", - "weight": 306, + "weight": 308, "cookies": false, "type": "", "deprecated": false, @@ -10311,7 +10323,7 @@ }, "x-appwrite": { "method": "listVariables", - "weight": 308, + "weight": 310, "cookies": false, "type": "", "deprecated": false, @@ -10373,7 +10385,7 @@ }, "x-appwrite": { "method": "createVariable", - "weight": 307, + "weight": 309, "cookies": false, "type": "", "deprecated": false, @@ -10462,7 +10474,7 @@ }, "x-appwrite": { "method": "getVariable", - "weight": 309, + "weight": 311, "cookies": false, "type": "", "deprecated": false, @@ -10532,7 +10544,7 @@ }, "x-appwrite": { "method": "updateVariable", - "weight": 310, + "weight": 312, "cookies": false, "type": "", "deprecated": false, @@ -10621,7 +10633,7 @@ }, "x-appwrite": { "method": "deleteVariable", - "weight": 311, + "weight": 313, "cookies": false, "type": "", "deprecated": false, @@ -10693,7 +10705,7 @@ }, "x-appwrite": { "method": "query", - "weight": 329, + "weight": 331, "cookies": false, "type": "graphql", "deprecated": false, @@ -10771,7 +10783,7 @@ }, "x-appwrite": { "method": "mutation", - "weight": 328, + "weight": 330, "cookies": false, "type": "graphql", "deprecated": false, @@ -10849,7 +10861,7 @@ }, "x-appwrite": { "method": "get", - "weight": 124, + "weight": 125, "cookies": false, "type": "", "deprecated": false, @@ -10903,7 +10915,7 @@ }, "x-appwrite": { "method": "getAntivirus", - "weight": 146, + "weight": 147, "cookies": false, "type": "", "deprecated": false, @@ -10957,7 +10969,7 @@ }, "x-appwrite": { "method": "getCache", - "weight": 127, + "weight": 128, "cookies": false, "type": "", "deprecated": false, @@ -11011,7 +11023,7 @@ }, "x-appwrite": { "method": "getCertificate", - "weight": 133, + "weight": 134, "cookies": false, "type": "", "deprecated": false, @@ -11074,7 +11086,7 @@ }, "x-appwrite": { "method": "getDB", - "weight": 126, + "weight": 127, "cookies": false, "type": "", "deprecated": false, @@ -11128,7 +11140,7 @@ }, "x-appwrite": { "method": "getPubSub", - "weight": 129, + "weight": 130, "cookies": false, "type": "", "deprecated": false, @@ -11182,7 +11194,7 @@ }, "x-appwrite": { "method": "getQueue", - "weight": 128, + "weight": 129, "cookies": false, "type": "", "deprecated": false, @@ -11236,7 +11248,7 @@ }, "x-appwrite": { "method": "getQueueBuilds", - "weight": 135, + "weight": 136, "cookies": false, "type": "", "deprecated": false, @@ -11301,7 +11313,7 @@ }, "x-appwrite": { "method": "getQueueCertificates", - "weight": 134, + "weight": 135, "cookies": false, "type": "", "deprecated": false, @@ -11366,7 +11378,7 @@ }, "x-appwrite": { "method": "getQueueDatabases", - "weight": 136, + "weight": 137, "cookies": false, "type": "", "deprecated": false, @@ -11440,7 +11452,7 @@ }, "x-appwrite": { "method": "getQueueDeletes", - "weight": 137, + "weight": 138, "cookies": false, "type": "", "deprecated": false, @@ -11494,7 +11506,7 @@ "tags": [ "health" ], - "description": "Returns the amount of failed jobs in a given queue.\r\n", + "description": "Returns the amount of failed jobs in a given queue.\n", "responses": { "200": { "description": "Health Queue", @@ -11505,7 +11517,7 @@ }, "x-appwrite": { "method": "getFailedJobs", - "weight": 147, + "weight": 148, "cookies": false, "type": "", "deprecated": false, @@ -11594,7 +11606,7 @@ }, "x-appwrite": { "method": "getQueueFunctions", - "weight": 141, + "weight": 142, "cookies": false, "type": "", "deprecated": false, @@ -11659,7 +11671,7 @@ }, "x-appwrite": { "method": "getQueueLogs", - "weight": 132, + "weight": 133, "cookies": false, "type": "", "deprecated": false, @@ -11724,7 +11736,7 @@ }, "x-appwrite": { "method": "getQueueMails", - "weight": 138, + "weight": 139, "cookies": false, "type": "", "deprecated": false, @@ -11789,7 +11801,7 @@ }, "x-appwrite": { "method": "getQueueMessaging", - "weight": 139, + "weight": 140, "cookies": false, "type": "", "deprecated": false, @@ -11854,7 +11866,7 @@ }, "x-appwrite": { "method": "getQueueMigrations", - "weight": 140, + "weight": 141, "cookies": false, "type": "", "deprecated": false, @@ -11919,7 +11931,7 @@ }, "x-appwrite": { "method": "getQueueUsage", - "weight": 142, + "weight": 143, "cookies": false, "type": "", "deprecated": false, @@ -11984,7 +11996,7 @@ }, "x-appwrite": { "method": "getQueueUsageDump", - "weight": 143, + "weight": 144, "cookies": false, "type": "", "deprecated": false, @@ -12049,7 +12061,7 @@ }, "x-appwrite": { "method": "getQueueWebhooks", - "weight": 131, + "weight": 132, "cookies": false, "type": "", "deprecated": false, @@ -12114,7 +12126,7 @@ }, "x-appwrite": { "method": "getStorage", - "weight": 145, + "weight": 146, "cookies": false, "type": "", "deprecated": false, @@ -12168,7 +12180,7 @@ }, "x-appwrite": { "method": "getStorageLocal", - "weight": 144, + "weight": 145, "cookies": false, "type": "", "deprecated": false, @@ -12222,7 +12234,7 @@ }, "x-appwrite": { "method": "getTime", - "weight": 130, + "weight": 131, "cookies": false, "type": "", "deprecated": false, @@ -12265,7 +12277,7 @@ "tags": [ "locale" ], - "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", + "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))", "responses": { "200": { "description": "Locale", @@ -12276,7 +12288,7 @@ }, "x-appwrite": { "method": "get", - "weight": 116, + "weight": 117, "cookies": false, "type": "", "deprecated": false, @@ -12334,7 +12346,7 @@ }, "x-appwrite": { "method": "listCodes", - "weight": 117, + "weight": 118, "cookies": false, "type": "", "deprecated": false, @@ -12392,7 +12404,7 @@ }, "x-appwrite": { "method": "listContinents", - "weight": 121, + "weight": 122, "cookies": false, "type": "", "deprecated": false, @@ -12450,7 +12462,7 @@ }, "x-appwrite": { "method": "listCountries", - "weight": 118, + "weight": 119, "cookies": false, "type": "", "deprecated": false, @@ -12508,7 +12520,7 @@ }, "x-appwrite": { "method": "listCountriesEU", - "weight": 119, + "weight": 120, "cookies": false, "type": "", "deprecated": false, @@ -12566,7 +12578,7 @@ }, "x-appwrite": { "method": "listCountriesPhones", - "weight": 120, + "weight": 121, "cookies": false, "type": "", "deprecated": false, @@ -12624,7 +12636,7 @@ }, "x-appwrite": { "method": "listCurrencies", - "weight": 122, + "weight": 123, "cookies": false, "type": "", "deprecated": false, @@ -12682,7 +12694,7 @@ }, "x-appwrite": { "method": "listLanguages", - "weight": 123, + "weight": 124, "cookies": false, "type": "", "deprecated": false, @@ -12740,7 +12752,7 @@ }, "x-appwrite": { "method": "listMessages", - "weight": 388, + "weight": 390, "cookies": false, "type": "", "deprecated": false, @@ -12818,7 +12830,7 @@ }, "x-appwrite": { "method": "createEmail", - "weight": 385, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -12968,7 +12980,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\r\n", + "description": "Update an email message by its unique ID.\n", "responses": { "200": { "description": "Message", @@ -12979,7 +12991,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 392, + "weight": 394, "cookies": false, "type": "", "deprecated": false, @@ -13137,7 +13149,7 @@ }, "x-appwrite": { "method": "createPush", - "weight": 387, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -13302,7 +13314,7 @@ "tags": [ "messaging" ], - "description": "Update a push notification by its unique ID.\r\n", + "description": "Update a push notification by its unique ID.\n", "responses": { "200": { "description": "Message", @@ -13313,7 +13325,7 @@ }, "x-appwrite": { "method": "updatePush", - "weight": 394, + "weight": 396, "cookies": false, "type": "", "deprecated": false, @@ -13486,7 +13498,7 @@ }, "x-appwrite": { "method": "createSms", - "weight": 386, + "weight": 388, "cookies": false, "type": "", "deprecated": false, @@ -13596,7 +13608,7 @@ "tags": [ "messaging" ], - "description": "Update an email message by its unique ID.\r\n", + "description": "Update an email message by its unique ID.\n", "responses": { "200": { "description": "Message", @@ -13607,7 +13619,7 @@ }, "x-appwrite": { "method": "updateSms", - "weight": 393, + "weight": 395, "cookies": false, "type": "", "deprecated": false, @@ -13715,7 +13727,7 @@ "tags": [ "messaging" ], - "description": "Get a message by its unique ID.\r\n", + "description": "Get a message by its unique ID.\n", "responses": { "200": { "description": "Message", @@ -13726,7 +13738,7 @@ }, "x-appwrite": { "method": "getMessage", - "weight": 391, + "weight": 393, "cookies": false, "type": "", "deprecated": false, @@ -13786,7 +13798,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 395, + "weight": 397, "cookies": false, "type": "", "deprecated": false, @@ -13851,7 +13863,7 @@ }, "x-appwrite": { "method": "listMessageLogs", - "weight": 389, + "weight": 391, "cookies": false, "type": "", "deprecated": false, @@ -13928,7 +13940,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 390, + "weight": 392, "cookies": false, "type": "", "deprecated": false, @@ -14005,7 +14017,7 @@ }, "x-appwrite": { "method": "listProviders", - "weight": 360, + "weight": 362, "cookies": false, "type": "", "deprecated": false, @@ -14083,7 +14095,7 @@ }, "x-appwrite": { "method": "createApnsProvider", - "weight": 359, + "weight": 361, "cookies": false, "type": "", "deprecated": false, @@ -14201,7 +14213,7 @@ }, "x-appwrite": { "method": "updateApnsProvider", - "weight": 372, + "weight": 374, "cookies": false, "type": "", "deprecated": false, @@ -14317,7 +14329,7 @@ }, "x-appwrite": { "method": "createFcmProvider", - "weight": 358, + "weight": 360, "cookies": false, "type": "", "deprecated": false, @@ -14411,7 +14423,7 @@ }, "x-appwrite": { "method": "updateFcmProvider", - "weight": 371, + "weight": 373, "cookies": false, "type": "", "deprecated": false, @@ -14503,7 +14515,7 @@ }, "x-appwrite": { "method": "createMailgunProvider", - "weight": 350, + "weight": 352, "cookies": false, "type": "", "deprecated": false, @@ -14633,7 +14645,7 @@ }, "x-appwrite": { "method": "updateMailgunProvider", - "weight": 363, + "weight": 365, "cookies": false, "type": "", "deprecated": false, @@ -14761,7 +14773,7 @@ }, "x-appwrite": { "method": "createMsg91Provider", - "weight": 353, + "weight": 355, "cookies": false, "type": "", "deprecated": false, @@ -14867,7 +14879,7 @@ }, "x-appwrite": { "method": "updateMsg91Provider", - "weight": 366, + "weight": 368, "cookies": false, "type": "", "deprecated": false, @@ -14971,7 +14983,7 @@ }, "x-appwrite": { "method": "createSendgridProvider", - "weight": 351, + "weight": 353, "cookies": false, "type": "", "deprecated": false, @@ -15089,7 +15101,7 @@ }, "x-appwrite": { "method": "updateSendgridProvider", - "weight": 364, + "weight": 366, "cookies": false, "type": "", "deprecated": false, @@ -15205,7 +15217,7 @@ }, "x-appwrite": { "method": "createSmtpProvider", - "weight": 352, + "weight": 354, "cookies": false, "type": "", "deprecated": false, @@ -15367,7 +15379,7 @@ }, "x-appwrite": { "method": "updateSmtpProvider", - "weight": 365, + "weight": 367, "cookies": false, "type": "", "deprecated": false, @@ -15526,7 +15538,7 @@ }, "x-appwrite": { "method": "createTelesignProvider", - "weight": 354, + "weight": 356, "cookies": false, "type": "", "deprecated": false, @@ -15632,7 +15644,7 @@ }, "x-appwrite": { "method": "updateTelesignProvider", - "weight": 367, + "weight": 369, "cookies": false, "type": "", "deprecated": false, @@ -15736,7 +15748,7 @@ }, "x-appwrite": { "method": "createTextmagicProvider", - "weight": 355, + "weight": 357, "cookies": false, "type": "", "deprecated": false, @@ -15842,7 +15854,7 @@ }, "x-appwrite": { "method": "updateTextmagicProvider", - "weight": 368, + "weight": 370, "cookies": false, "type": "", "deprecated": false, @@ -15946,7 +15958,7 @@ }, "x-appwrite": { "method": "createTwilioProvider", - "weight": 356, + "weight": 358, "cookies": false, "type": "", "deprecated": false, @@ -16052,7 +16064,7 @@ }, "x-appwrite": { "method": "updateTwilioProvider", - "weight": 369, + "weight": 371, "cookies": false, "type": "", "deprecated": false, @@ -16156,7 +16168,7 @@ }, "x-appwrite": { "method": "createVonageProvider", - "weight": 357, + "weight": 359, "cookies": false, "type": "", "deprecated": false, @@ -16262,7 +16274,7 @@ }, "x-appwrite": { "method": "updateVonageProvider", - "weight": 370, + "weight": 372, "cookies": false, "type": "", "deprecated": false, @@ -16355,7 +16367,7 @@ "tags": [ "messaging" ], - "description": "Get a provider by its unique ID.\r\n", + "description": "Get a provider by its unique ID.\n", "responses": { "200": { "description": "Provider", @@ -16366,7 +16378,7 @@ }, "x-appwrite": { "method": "getProvider", - "weight": 362, + "weight": 364, "cookies": false, "type": "", "deprecated": false, @@ -16426,7 +16438,7 @@ }, "x-appwrite": { "method": "deleteProvider", - "weight": 373, + "weight": 375, "cookies": false, "type": "", "deprecated": false, @@ -16491,7 +16503,7 @@ }, "x-appwrite": { "method": "listProviderLogs", - "weight": 361, + "weight": 363, "cookies": false, "type": "", "deprecated": false, @@ -16568,7 +16580,7 @@ }, "x-appwrite": { "method": "listSubscriberLogs", - "weight": 382, + "weight": 384, "cookies": false, "type": "", "deprecated": false, @@ -16645,7 +16657,7 @@ }, "x-appwrite": { "method": "listTopics", - "weight": 375, + "weight": 377, "cookies": false, "type": "", "deprecated": false, @@ -16721,7 +16733,7 @@ }, "x-appwrite": { "method": "createTopic", - "weight": 374, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -16803,7 +16815,7 @@ "tags": [ "messaging" ], - "description": "Get a topic by its unique ID.\r\n", + "description": "Get a topic by its unique ID.\n", "responses": { "200": { "description": "Topic", @@ -16814,7 +16826,7 @@ }, "x-appwrite": { "method": "getTopic", - "weight": 377, + "weight": 379, "cookies": false, "type": "", "deprecated": false, @@ -16866,7 +16878,7 @@ "tags": [ "messaging" ], - "description": "Update a topic by its unique ID.\r\n", + "description": "Update a topic by its unique ID.\n", "responses": { "200": { "description": "Topic", @@ -16877,7 +16889,7 @@ }, "x-appwrite": { "method": "updateTopic", - "weight": 378, + "weight": 380, "cookies": false, "type": "", "deprecated": false, @@ -16961,7 +16973,7 @@ }, "x-appwrite": { "method": "deleteTopic", - "weight": 379, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -17026,7 +17038,7 @@ }, "x-appwrite": { "method": "listTopicLogs", - "weight": 376, + "weight": 378, "cookies": false, "type": "", "deprecated": false, @@ -17103,7 +17115,7 @@ }, "x-appwrite": { "method": "listSubscribers", - "weight": 381, + "weight": 383, "cookies": false, "type": "", "deprecated": false, @@ -17187,7 +17199,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 380, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -17270,7 +17282,7 @@ "tags": [ "messaging" ], - "description": "Get a subscriber by its unique ID.\r\n", + "description": "Get a subscriber by its unique ID.\n", "responses": { "200": { "description": "Subscriber", @@ -17281,7 +17293,7 @@ }, "x-appwrite": { "method": "getSubscriber", - "weight": 383, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -17349,7 +17361,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 384, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -17426,7 +17438,7 @@ }, "x-appwrite": { "method": "listBuckets", - "weight": 201, + "weight": 203, "cookies": false, "type": "", "deprecated": false, @@ -17501,7 +17513,7 @@ }, "x-appwrite": { "method": "createBucket", - "weight": 200, + "weight": 202, "cookies": false, "type": "", "deprecated": false, @@ -17643,7 +17655,7 @@ }, "x-appwrite": { "method": "getBucket", - "weight": 202, + "weight": 204, "cookies": false, "type": "", "deprecated": false, @@ -17705,7 +17717,7 @@ }, "x-appwrite": { "method": "updateBucket", - "weight": 203, + "weight": 205, "cookies": false, "type": "", "deprecated": false, @@ -17841,7 +17853,7 @@ }, "x-appwrite": { "method": "deleteBucket", - "weight": 204, + "weight": 206, "cookies": false, "type": "", "deprecated": false, @@ -17905,7 +17917,7 @@ }, "x-appwrite": { "method": "listFiles", - "weight": 206, + "weight": 208, "cookies": false, "type": "", "deprecated": false, @@ -17981,7 +17993,7 @@ "tags": [ "storage" ], - "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n", + "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n", "responses": { "201": { "description": "File", @@ -17992,7 +18004,7 @@ }, "x-appwrite": { "method": "createFile", - "weight": 205, + "weight": 207, "cookies": false, "type": "upload", "deprecated": false, @@ -18088,7 +18100,7 @@ }, "x-appwrite": { "method": "getFile", - "weight": 207, + "weight": 209, "cookies": false, "type": "", "deprecated": false, @@ -18162,7 +18174,7 @@ }, "x-appwrite": { "method": "updateFile", - "weight": 212, + "weight": 214, "cookies": false, "type": "", "deprecated": false, @@ -18255,7 +18267,7 @@ }, "x-appwrite": { "method": "deleteFile", - "weight": 213, + "weight": 215, "cookies": false, "type": "", "deprecated": false, @@ -18331,7 +18343,7 @@ }, "x-appwrite": { "method": "getFileDownload", - "weight": 209, + "weight": 211, "cookies": false, "type": "location", "deprecated": false, @@ -18407,7 +18419,7 @@ }, "x-appwrite": { "method": "getFilePreview", - "weight": 208, + "weight": 210, "cookies": false, "type": "location", "deprecated": false, @@ -18610,7 +18622,7 @@ }, "x-appwrite": { "method": "getFileView", - "weight": 210, + "weight": 212, "cookies": false, "type": "location", "deprecated": false, @@ -18686,7 +18698,7 @@ }, "x-appwrite": { "method": "list", - "weight": 217, + "weight": 219, "cookies": false, "type": "", "deprecated": false, @@ -18765,7 +18777,7 @@ }, "x-appwrite": { "method": "create", - "weight": 216, + "weight": 218, "cookies": false, "type": "", "deprecated": false, @@ -18861,7 +18873,7 @@ }, "x-appwrite": { "method": "get", - "weight": 218, + "weight": 220, "cookies": false, "type": "", "deprecated": false, @@ -18927,7 +18939,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 220, + "weight": 222, "cookies": false, "type": "", "deprecated": false, @@ -19006,7 +19018,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 222, + "weight": 224, "cookies": false, "type": "", "deprecated": false, @@ -19063,7 +19075,7 @@ "tags": [ "teams" ], - "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint.", + "description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Memberships List", @@ -19074,7 +19086,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 224, + "weight": 226, "cookies": false, "type": "", "deprecated": false, @@ -19150,7 +19162,7 @@ "tags": [ "teams" ], - "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n", + "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n", "responses": { "201": { "description": "Membership", @@ -19161,7 +19173,7 @@ }, "x-appwrite": { "method": "createMembership", - "weight": 223, + "weight": 225, "cookies": false, "type": "", "deprecated": false, @@ -19269,7 +19281,7 @@ "tags": [ "teams" ], - "description": "Get a team member by the membership unique id. All team members have read access for this resource.", + "description": "Get a team member by the membership unique id. All team members have read access for this resource. Hide sensitive attributes from the response by toggling membership privacy in the Console.", "responses": { "200": { "description": "Membership", @@ -19280,7 +19292,7 @@ }, "x-appwrite": { "method": "getMembership", - "weight": 225, + "weight": 227, "cookies": false, "type": "", "deprecated": false, @@ -19343,7 +19355,7 @@ "tags": [ "teams" ], - "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n", + "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n", "responses": { "200": { "description": "Membership", @@ -19354,7 +19366,7 @@ }, "x-appwrite": { "method": "updateMembership", - "weight": 226, + "weight": 228, "cookies": false, "type": "", "deprecated": false, @@ -19444,7 +19456,7 @@ }, "x-appwrite": { "method": "deleteMembership", - "weight": 228, + "weight": 230, "cookies": false, "type": "", "deprecated": false, @@ -19509,7 +19521,7 @@ "tags": [ "teams" ], - "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n", + "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n", "responses": { "200": { "description": "Membership", @@ -19520,7 +19532,7 @@ }, "x-appwrite": { "method": "updateMembershipStatus", - "weight": 227, + "weight": 229, "cookies": false, "type": "", "deprecated": false, @@ -19619,7 +19631,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 219, + "weight": 221, "cookies": false, "type": "", "deprecated": false, @@ -19683,7 +19695,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 221, + "weight": 223, "cookies": false, "type": "", "deprecated": false, @@ -19767,7 +19779,7 @@ }, "x-appwrite": { "method": "list", - "weight": 239, + "weight": 241, "cookies": false, "type": "", "deprecated": false, @@ -19842,7 +19854,7 @@ }, "x-appwrite": { "method": "create", - "weight": 230, + "weight": 232, "cookies": false, "type": "", "deprecated": false, @@ -19940,7 +19952,7 @@ }, "x-appwrite": { "method": "createArgon2User", - "weight": 233, + "weight": 235, "cookies": false, "type": "", "deprecated": false, @@ -20034,7 +20046,7 @@ }, "x-appwrite": { "method": "createBcryptUser", - "weight": 231, + "weight": 233, "cookies": false, "type": "", "deprecated": false, @@ -20128,7 +20140,7 @@ }, "x-appwrite": { "method": "listIdentities", - "weight": 247, + "weight": 249, "cookies": false, "type": "", "deprecated": false, @@ -20200,7 +20212,7 @@ }, "x-appwrite": { "method": "deleteIdentity", - "weight": 270, + "weight": 272, "cookies": false, "type": "", "deprecated": false, @@ -20264,7 +20276,7 @@ }, "x-appwrite": { "method": "createMD5User", - "weight": 232, + "weight": 234, "cookies": false, "type": "", "deprecated": false, @@ -20358,7 +20370,7 @@ }, "x-appwrite": { "method": "createPHPassUser", - "weight": 235, + "weight": 237, "cookies": false, "type": "", "deprecated": false, @@ -20452,7 +20464,7 @@ }, "x-appwrite": { "method": "createScryptUser", - "weight": 236, + "weight": 238, "cookies": false, "type": "", "deprecated": false, @@ -20581,7 +20593,7 @@ }, "x-appwrite": { "method": "createScryptModifiedUser", - "weight": 237, + "weight": 239, "cookies": false, "type": "", "deprecated": false, @@ -20696,7 +20708,7 @@ }, "x-appwrite": { "method": "createSHAUser", - "weight": 234, + "weight": 236, "cookies": false, "type": "", "deprecated": false, @@ -20811,7 +20823,7 @@ }, "x-appwrite": { "method": "get", - "weight": 240, + "weight": 242, "cookies": false, "type": "", "deprecated": false, @@ -20868,7 +20880,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 268, + "weight": 270, "cookies": false, "type": "", "deprecated": false, @@ -20932,7 +20944,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 253, + "weight": 255, "cookies": false, "type": "", "deprecated": false, @@ -21014,7 +21026,7 @@ }, "x-appwrite": { "method": "createJWT", - "weight": 271, + "weight": 273, "cookies": false, "type": "", "deprecated": false, @@ -21088,7 +21100,7 @@ "tags": [ "users" ], - "description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", + "description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.", "responses": { "200": { "description": "User", @@ -21099,7 +21111,7 @@ }, "x-appwrite": { "method": "updateLabels", - "weight": 249, + "weight": 251, "cookies": false, "type": "", "deprecated": false, @@ -21184,7 +21196,7 @@ }, "x-appwrite": { "method": "listLogs", - "weight": 245, + "weight": 247, "cookies": false, "type": "", "deprecated": false, @@ -21260,7 +21272,7 @@ }, "x-appwrite": { "method": "listMemberships", - "weight": 244, + "weight": 246, "cookies": false, "type": "", "deprecated": false, @@ -21324,7 +21336,7 @@ }, "x-appwrite": { "method": "updateMfa", - "weight": 258, + "weight": 260, "cookies": false, "type": "", "deprecated": false, @@ -21406,7 +21418,7 @@ }, "x-appwrite": { "method": "deleteMfaAuthenticator", - "weight": 263, + "weight": 265, "cookies": false, "type": "", "deprecated": false, @@ -21483,7 +21495,7 @@ }, "x-appwrite": { "method": "listMfaFactors", - "weight": 259, + "weight": 261, "cookies": false, "type": "", "deprecated": false, @@ -21547,7 +21559,7 @@ }, "x-appwrite": { "method": "getMfaRecoveryCodes", - "weight": 260, + "weight": 262, "cookies": false, "type": "", "deprecated": false, @@ -21609,7 +21621,7 @@ }, "x-appwrite": { "method": "updateMfaRecoveryCodes", - "weight": 262, + "weight": 264, "cookies": false, "type": "", "deprecated": false, @@ -21671,7 +21683,7 @@ }, "x-appwrite": { "method": "createMfaRecoveryCodes", - "weight": 261, + "weight": 263, "cookies": false, "type": "", "deprecated": false, @@ -21735,7 +21747,7 @@ }, "x-appwrite": { "method": "updateName", - "weight": 251, + "weight": 253, "cookies": false, "type": "", "deprecated": false, @@ -21817,7 +21829,7 @@ }, "x-appwrite": { "method": "updatePassword", - "weight": 252, + "weight": 254, "cookies": false, "type": "", "deprecated": false, @@ -21899,7 +21911,7 @@ }, "x-appwrite": { "method": "updatePhone", - "weight": 254, + "weight": 256, "cookies": false, "type": "", "deprecated": false, @@ -21981,7 +21993,7 @@ }, "x-appwrite": { "method": "getPrefs", - "weight": 241, + "weight": 243, "cookies": false, "type": "", "deprecated": false, @@ -22043,7 +22055,7 @@ }, "x-appwrite": { "method": "updatePrefs", - "weight": 256, + "weight": 258, "cookies": false, "type": "", "deprecated": false, @@ -22125,7 +22137,7 @@ }, "x-appwrite": { "method": "listSessions", - "weight": 243, + "weight": 245, "cookies": false, "type": "", "deprecated": false, @@ -22176,7 +22188,7 @@ "tags": [ "users" ], - "description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", + "description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.", "responses": { "201": { "description": "Session", @@ -22187,7 +22199,7 @@ }, "x-appwrite": { "method": "createSession", - "weight": 264, + "weight": 266, "cookies": false, "type": "", "deprecated": false, @@ -22244,7 +22256,7 @@ }, "x-appwrite": { "method": "deleteSessions", - "weight": 267, + "weight": 269, "cookies": false, "type": "", "deprecated": false, @@ -22303,7 +22315,7 @@ }, "x-appwrite": { "method": "deleteSession", - "weight": 266, + "weight": 268, "cookies": false, "type": "", "deprecated": false, @@ -22375,7 +22387,7 @@ }, "x-appwrite": { "method": "updateStatus", - "weight": 248, + "weight": 250, "cookies": false, "type": "", "deprecated": false, @@ -22457,7 +22469,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 246, + "weight": 248, "cookies": false, "type": "", "deprecated": false, @@ -22532,7 +22544,7 @@ }, "x-appwrite": { "method": "createTarget", - "weight": 238, + "weight": 240, "cookies": false, "type": "", "deprecated": false, @@ -22648,7 +22660,7 @@ }, "x-appwrite": { "method": "getTarget", - "weight": 242, + "weight": 244, "cookies": false, "type": "", "deprecated": false, @@ -22719,7 +22731,7 @@ }, "x-appwrite": { "method": "updateTarget", - "weight": 257, + "weight": 259, "cookies": false, "type": "", "deprecated": false, @@ -22814,7 +22826,7 @@ }, "x-appwrite": { "method": "deleteTarget", - "weight": 269, + "weight": 271, "cookies": false, "type": "", "deprecated": false, @@ -22876,7 +22888,7 @@ "tags": [ "users" ], - "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n", + "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n", "responses": { "201": { "description": "Token", @@ -22887,7 +22899,7 @@ }, "x-appwrite": { "method": "createToken", - "weight": 265, + "weight": 267, "cookies": false, "type": "", "deprecated": false, @@ -22972,7 +22984,7 @@ }, "x-appwrite": { "method": "updateEmailVerification", - "weight": 255, + "weight": 257, "cookies": false, "type": "", "deprecated": false, @@ -23054,7 +23066,7 @@ }, "x-appwrite": { "method": "updatePhoneVerification", - "weight": 250, + "weight": 252, "cookies": false, "type": "", "deprecated": false, @@ -26072,12 +26084,12 @@ }, "userName": { "type": "string", - "description": "User name.", + "description": "User name. Hide this attribute by toggling membership privacy in the Console.", "x-example": "John Doe" }, "userEmail": { "type": "string", - "description": "User email address.", + "description": "User email address. Hide this attribute by toggling membership privacy in the Console.", "x-example": "john@appwrite.io" }, "teamId": { @@ -26107,7 +26119,7 @@ }, "mfa": { "type": "boolean", - "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise.", + "description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise. Hide this attribute by toggling membership privacy in the Console.", "x-example": false }, "roles": { @@ -26273,7 +26285,7 @@ "specification": { "type": "string", "description": "Machine specification for builds and executions.", - "x-example": "s-0.5vcpu-512mb" + "x-example": "s-1vcpu-512mb" } }, "required": [ @@ -26591,7 +26603,7 @@ "format": "int32" }, "responseBody": { - "type": "payload", + "type": "string", "description": "HTTP response body. This will return empty unless execution is created as synchronous.", "x-example": "" }, @@ -27087,7 +27099,7 @@ "slug": { "type": "string", "description": "Size slug.", - "x-example": "s-0.5vcpu-512mb" + "x-example": "s-1vcpu-512mb" } }, "required": [ @@ -27538,7 +27550,7 @@ "name": { "type": "string", "description": "Target Name.", - "x-example": "Aegon apple token" + "x-example": "Apple iPhone 12" }, "userId": { "type": "string", @@ -27560,6 +27572,11 @@ "type": "string", "description": "The target identifier.", "x-example": "token" + }, + "expired": { + "type": "boolean", + "description": "Is the target expired.", + "x-example": false } }, "required": [ @@ -27569,7 +27586,8 @@ "name", "userId", "providerType", - "identifier" + "identifier", + "expired" ] } }, 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 diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 64441fee5c..76a3ef8b61 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; @@ -273,7 +274,6 @@ $createSession = function (string $userId, string $secret, Request $request, Res App::post('/v1/account') ->desc('Create account') ->groups(['api', 'account', 'auth']) - ->label('event', 'users.[userId].create') ->label('scope', 'sessions.write') ->label('auth.type', 'emailPassword') ->label('audits.event', 'user.create') @@ -296,9 +296,8 @@ App::post('/v1/account') ->inject('user') ->inject('project') ->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) { + ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Hooks $hooks) { $email = \strtolower($email); if ('console' === $project->getId()) { @@ -331,7 +330,7 @@ App::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 */ } @@ -394,7 +393,7 @@ App::post('/v1/account') $existingTarget = $dbForProject->findOne('targets', [ Query::equal('identifier', [$email]), ]); - if ($existingTarget) { + if (!$existingTarget->isEmpty()) { $user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND); } } @@ -408,8 +407,6 @@ App::post('/v1/account') Authorization::setRole(Role::user($user->getId())->toString()); Authorization::setRole(Role::users()->toString()); - $queueForEvents->setParam('userId', $user->getId()); - $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($user, Response::MODEL_ACCOUNT); @@ -441,7 +438,6 @@ App::get('/v1/account') App::delete('/v1/account') ->desc('Delete account') ->groups(['api', 'account']) - ->label('event', 'users.[userId].delete') ->label('scope', 'account') ->label('audits.event', 'user.delete') ->label('audits.resource', 'user/{response.$id}') @@ -833,7 +829,7 @@ App::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); } @@ -1373,7 +1369,7 @@ App::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); } @@ -1404,7 +1400,7 @@ App::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()); } } @@ -1422,7 +1418,7 @@ App::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()); } @@ -1433,7 +1429,7 @@ App::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')); } } @@ -1453,7 +1449,7 @@ App::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 */ } @@ -1498,6 +1494,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'providerType' => MESSAGE_TYPE_EMAIL, 'identifier' => $email, ])); + } catch (Duplicate) { $failureRedirect(Exception::USER_ALREADY_EXISTS); } @@ -1516,7 +1513,7 @@ App::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(); @@ -1800,7 +1797,7 @@ App::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; @@ -1817,7 +1814,7 @@ App::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); } @@ -2041,7 +2038,7 @@ App::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; @@ -2058,7 +2055,7 @@ App::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 */ } @@ -2328,7 +2325,7 @@ App::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; @@ -2385,7 +2382,7 @@ App::post('/v1/account/tokens/phone') $existingTarget = $dbForProject->findOne('targets', [ Query::equal('identifier', [$phone]), ]); - $user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]); + $user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget->isEmpty() ? false : $existingTarget]); } $dbForProject->purgeCachedDocument('users', $user->getId()); } @@ -2752,7 +2749,7 @@ App::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 */ } @@ -2773,7 +2770,7 @@ App::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); } @@ -2839,7 +2836,7 @@ App::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); } @@ -2998,7 +2995,7 @@ App::post('/v1/account/recovery') Query::equal('email', [$email]), ]); - if (!$profile) { + if ($profile->isEmpty()) { throw new Exception(Exception::USER_NOT_FOUND); } @@ -4314,7 +4311,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 { @@ -4383,7 +4380,9 @@ App::put('/v1/account/targets/:targetId/push') } if ($identifier) { - $target->setAttribute('identifier', $identifier); + $target + ->setAttribute('identifier', $identifier) + ->setAttribute('expired', false); } $detector = new Detector($request->getUserAgent()); @@ -4486,6 +4485,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/avatars.php b/app/controllers/api/avatars.php index fcff3e4179..f73f8a148a 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -55,15 +55,15 @@ $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); }; -$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForConsole, ?Logger $logger) { +$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForPlatform, ?Logger $logger) { try { - $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); + $user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId)); $sessions = $user->getAttribute('sessions', []); @@ -122,7 +122,7 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro do { $previousAccessToken = $gitHubSession->getAttribute('providerAccessToken'); - $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); + $user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId)); $sessions = $user->getAttribute('sessions', []); $gitHubSession = new Document(); @@ -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()); }); @@ -565,14 +565,14 @@ App::get('/v1/cards/cloud') ->inject('user') ->inject('project') ->inject('dbForProject') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('response') ->inject('heroes') ->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)); + ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) { + $user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId)); if ($user->isEmpty() && empty($mock)) { throw new Exception(Exception::USER_NOT_FOUND); @@ -583,7 +583,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, $dbForPlatform, $logger); $githubName = $gitHub['name'] ?? ''; $githubId = $gitHub['id'] ?? ''; @@ -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()); }); @@ -772,14 +772,14 @@ App::get('/v1/cards/cloud-back') ->inject('user') ->inject('project') ->inject('dbForProject') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('response') ->inject('heroes') ->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)); + ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) { + $user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId)); if ($user->isEmpty() && empty($mock)) { throw new Exception(Exception::USER_NOT_FOUND); @@ -789,7 +789,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, $dbForPlatform, $logger); $githubId = $gitHub['id'] ?? ''; $isHero = \array_key_exists($email, $heroes); @@ -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()); }); @@ -850,14 +850,14 @@ App::get('/v1/cards/cloud-og') ->inject('user') ->inject('project') ->inject('dbForProject') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('response') ->inject('heroes') ->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)); + ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) { + $user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId)); if ($user->isEmpty() && empty($mock)) { throw new Exception(Exception::USER_NOT_FOUND); @@ -872,7 +872,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, $dbForPlatform, $logger); $githubName = $gitHub['name'] ?? ''; $githubId = $gitHub['id'] ?? ''; @@ -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/databases.php b/app/controllers/api/databases.php index 72e3c6d464..aad072c50a 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -3,7 +3,6 @@ use Appwrite\Auth\Auth; use Appwrite\Detector\Detector; use Appwrite\Event\Database as EventDatabase; -use Appwrite\Event\Delete; use Appwrite\Event\Event; use Appwrite\Event\Usage; use Appwrite\Extend\Exception; @@ -21,11 +20,11 @@ use Utopia\Audit\Audit; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; -use Utopia\Database\Exception as DatabaseException; use Utopia\Database\Exception\Authorization as AuthorizationException; use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Duplicate as DuplicateException; use Utopia\Database\Exception\Limit as LimitException; +use Utopia\Database\Exception\NotFound as NotFoundException; use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Exception\Restricted as RestrictedException; use Utopia\Database\Exception\Structure as StructureException; @@ -40,6 +39,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; @@ -152,7 +152,7 @@ function createAttribute(string $databaseId, string $collectionId, Document $att } catch (DuplicateException) { throw new Exception(Exception::ATTRIBUTE_ALREADY_EXISTS); } catch (LimitException) { - throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, 'Attribute limit exceeded'); + throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED); } catch (\Throwable $e) { $dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collectionId); $dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $collection->getInternalId()); @@ -196,7 +196,7 @@ function createAttribute(string $databaseId, string $collectionId, Document $att throw new Exception(Exception::ATTRIBUTE_ALREADY_EXISTS); } catch (LimitException) { $dbForProject->deleteDocument('attributes', $attribute->getId()); - throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, 'Attribute limit exceeded'); + throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED); } catch (\Throwable $e) { $dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $relatedCollection->getId()); $dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $relatedCollection->getInternalId()); @@ -290,15 +290,9 @@ function updateAttribute( $attribute->setAttribute('size', $size); } - $formatOptions = $attribute->getAttribute('formatOptions'); - switch ($attribute->getAttribute('format')) { case APP_DATABASE_ATTRIBUTE_INT_RANGE: case APP_DATABASE_ATTRIBUTE_FLOAT_RANGE: - if ($min === $formatOptions['min'] && $max === $formatOptions['max']) { - break; - } - if ($min > $max) { throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Minimum value must be lesser than maximum value'); } @@ -351,13 +345,16 @@ function updateAttribute( if ($type === Database::VAR_RELATIONSHIP) { $primaryDocumentOptions = \array_merge($attribute->getAttribute('options', []), $options); $attribute->setAttribute('options', $primaryDocumentOptions); - - $dbForProject->updateRelationship( - collection: $collectionId, - id: $key, - newKey: $newKey, - onDelete: $primaryDocumentOptions['onDelete'], - ); + try { + $dbForProject->updateRelationship( + collection: $collectionId, + id: $key, + newKey: $newKey, + onDelete: $primaryDocumentOptions['onDelete'], + ); + } catch (NotFoundException) { + throw new Exception(Exception::ATTRIBUTE_NOT_FOUND); + } if ($primaryDocumentOptions['twoWay']) { $relatedCollection = $dbForProject->getDocument('database_' . $db->getInternalId(), $primaryDocumentOptions['relatedCollection']); @@ -382,28 +379,42 @@ function updateAttribute( size: $size, required: $required, default: $default, - formatOptions: $options ?? null, + formatOptions: $options, newKey: $newKey ?? null ); } catch (TruncateException) { throw new Exception(Exception::ATTRIBUTE_INVALID_RESIZE); + } catch (NotFoundException) { + throw new Exception(Exception::ATTRIBUTE_NOT_FOUND); + } catch (LimitException) { + throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED); } } if (!empty($newKey) && $key !== $newKey) { - // Delete attribute and recreate since we can't modify IDs - $original = clone $attribute; - - $dbForProject->deleteDocument('attributes', $attribute->getId()); + $originalUid = $attribute->getId(); $attribute ->setAttribute('$id', ID::custom($db->getInternalId() . '_' . $collection->getInternalId() . '_' . $newKey)) ->setAttribute('key', $newKey); - try { - $attribute = $dbForProject->createDocument('attributes', $attribute); - } catch (DatabaseException|PDOException) { - $attribute = $dbForProject->createDocument('attributes', $original); + $dbForProject->updateDocument('attributes', $originalUid, $attribute); + + /** + * @var Document $index + */ + foreach ($collection->getAttribute('indexes') as $index) { + /** + * @var string[] $attributes + */ + $attributes = $index->getAttribute('attributes', []); + $found = \array_search($key, $attributes); + + if ($found !== false) { + $attributes[$found] = $newKey; + $index->setAttribute('attributes', $attributes); + $dbForProject->updateDocument('indexes', $index->getId(), $index); + } } } else { $attribute = $dbForProject->updateDocument('attributes', $db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key, $attribute); @@ -438,6 +449,7 @@ App::post('/v1/databases') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].create') ->label('scope', 'databases.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'database.create') ->label('audits.resource', 'database/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -515,6 +527,7 @@ App::get('/v1/databases') ->desc('List databases') ->groups(['api', 'database']) ->label('scope', 'databases.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'list') @@ -527,12 +540,7 @@ App::get('/v1/databases') ->inject('response') ->inject('dbForProject') ->action(function (array $queries, string $search, Response $response, Database $dbForProject) { - - try { - $queries = Query::parseQueries($queries); - } catch (QueryException $e) { - throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); - } + $queries = Query::parseQueries($queries); if (!empty($search)) { $queries[] = Query::search('search', $search); @@ -546,6 +554,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); @@ -568,6 +583,7 @@ App::get('/v1/databases/:databaseId') ->desc('Get database') ->groups(['api', 'database']) ->label('scope', 'databases.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'get') @@ -593,6 +609,7 @@ App::get('/v1/databases/:databaseId/logs') ->desc('List database logs') ->groups(['api', 'database']) ->label('scope', 'databases.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'listLogs') @@ -684,6 +701,7 @@ App::put('/v1/databases/:databaseId') ->desc('Update database') ->groups(['api', 'database', 'schema']) ->label('scope', 'databases.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('event', 'databases.[databaseId].update') ->label('audits.event', 'database.update') ->label('audits.resource', 'database/{response.$id}') @@ -722,6 +740,7 @@ App::delete('/v1/databases/:databaseId') ->desc('Delete database') ->groups(['api', 'database', 'schema']) ->label('scope', 'databases.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('event', 'databases.[databaseId].delete') ->label('audits.event', 'database.delete') ->label('audits.resource', 'database/{request.databaseId}') @@ -771,6 +790,7 @@ App::post('/v1/databases/:databaseId/collections') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].create') ->label('scope', 'collections.write') + ->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]) @@ -801,22 +821,21 @@ App::post('/v1/databases/:databaseId/collections') $collectionId = $collectionId == 'unique()' ? ID::unique() : $collectionId; // Map aggregate permissions into the multiple permissions they represent. - $permissions = Permission::aggregate($permissions); + $permissions = Permission::aggregate($permissions) ?? []; try { - $dbForProject->createDocument('database_' . $database->getInternalId(), new Document([ + $collection = $dbForProject->createDocument('database_' . $database->getInternalId(), new Document([ '$id' => $collectionId, 'databaseInternalId' => $database->getInternalId(), 'databaseId' => $databaseId, - '$permissions' => $permissions ?? [], + '$permissions' => $permissions, 'documentSecurity' => $documentSecurity, 'enabled' => $enabled, 'name' => $name, 'search' => implode(' ', [$collectionId, $name]), ])); - $collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId); - $dbForProject->createCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), permissions: $permissions ?? [], documentSecurity: $documentSecurity); + $dbForProject->createCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), permissions: $permissions, documentSecurity: $documentSecurity); } catch (DuplicateException) { throw new Exception(Exception::COLLECTION_ALREADY_EXISTS); } catch (LimitException) { @@ -834,10 +853,11 @@ App::post('/v1/databases/:databaseId/collections') }); App::get('/v1/databases/:databaseId/collections') - ->alias('/v1/database/collections', ['databaseId' => 'default']) + ->alias('/v1/database/collections') ->desc('List collections') ->groups(['api', 'database']) ->label('scope', 'collections.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'listCollections') @@ -859,11 +879,7 @@ App::get('/v1/databases/:databaseId/collections') throw new Exception(Exception::DATABASE_NOT_FOUND); } - try { - $queries = Query::parseQueries($queries); - } catch (QueryException $e) { - throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); - } + $queries = Query::parseQueries($queries); if (!empty($search)) { $queries[] = Query::search('search', $search); @@ -878,6 +894,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); @@ -897,10 +919,11 @@ App::get('/v1/databases/:databaseId/collections') }); App::get('/v1/databases/:databaseId/collections/:collectionId') - ->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId') ->desc('Get collection') ->groups(['api', 'database']) ->label('scope', 'collections.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'getCollection') @@ -931,10 +954,11 @@ App::get('/v1/databases/:databaseId/collections/:collectionId') }); App::get('/v1/databases/:databaseId/collections/:collectionId/logs') - ->alias('/v1/database/collections/:collectionId/logs', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/logs') ->desc('List collection logs') ->groups(['api', 'database']) ->label('scope', 'collections.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'listCollectionLogs') @@ -964,12 +988,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs') throw new Exception(Exception::COLLECTION_NOT_FOUND); } - try { - $queries = Query::parseQueries($queries); - } catch (QueryException $e) { - throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); - } - + $queries = Query::parseQueries($queries); $grouped = Query::groupByType($queries); $limit = $grouped['limit'] ?? APP_LIMIT_COUNT; $offset = $grouped['offset'] ?? 0; @@ -1031,10 +1050,11 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs') App::put('/v1/databases/:databaseId/collections/:collectionId') - ->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId') ->desc('Update collection') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->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}') @@ -1076,12 +1096,16 @@ App::put('/v1/databases/:databaseId/collections/:collectionId') $enabled ??= $collection->getAttribute('enabled', true); - $collection = $dbForProject->updateDocument('database_' . $database->getInternalId(), $collectionId, $collection + $collection = $dbForProject->updateDocument( + 'database_' . $database->getInternalId(), + $collectionId, + $collection ->setAttribute('name', $name) ->setAttribute('$permissions', $permissions) ->setAttribute('documentSecurity', $documentSecurity) ->setAttribute('enabled', $enabled) - ->setAttribute('search', implode(' ', [$collectionId, $name]))); + ->setAttribute('search', \implode(' ', [$collectionId, $name])) + ); $dbForProject->updateCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $permissions, $documentSecurity); @@ -1094,10 +1118,11 @@ App::put('/v1/databases/:databaseId/collections/:collectionId') }); App::delete('/v1/databases/:databaseId/collections/:collectionId') - ->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId') ->desc('Delete collection') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->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}') @@ -1149,11 +1174,12 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId') }); App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string') - ->alias('/v1/database/collections/:collectionId/attributes/string', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/string') ->desc('Create string attribute') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') + ->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]) @@ -1199,18 +1225,18 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string 'filters' => $filters, ]), $response, $dbForProject, $queueForDatabase, $queueForEvents); - $response ->setStatusCode(Response::STATUS_CODE_ACCEPTED) ->dynamic($attribute, Response::MODEL_ATTRIBUTE_STRING); }); App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email') - ->alias('/v1/database/collections/:collectionId/attributes/email', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/email') ->desc('Create email attribute') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1248,11 +1274,12 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email' }); App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum') - ->alias('/v1/database/collections/:collectionId/attributes/enum', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/enum') ->desc('Create enum attribute') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1295,11 +1322,12 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum') }); App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') - ->alias('/v1/database/collections/:collectionId/attributes/ip', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/ip') ->desc('Create IP address attribute') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1337,11 +1365,12 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') }); App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') - ->alias('/v1/database/collections/:collectionId/attributes/url', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/url') ->desc('Create URL attribute') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1379,11 +1408,12 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') }); App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/integer') - ->alias('/v1/database/collections/:collectionId/attributes/integer', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/integer') ->desc('Create integer attribute') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1408,8 +1438,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege ->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); - $max = (is_null($max)) ? PHP_INT_MAX : \intval($max); + $min = \is_null($min) ? PHP_INT_MIN : $min; + $max = \is_null($max) ? PHP_INT_MAX : $max; if ($min > $max) { throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Minimum value must be lesser than maximum value'); @@ -1450,11 +1480,12 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege }); App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float') - ->alias('/v1/database/collections/:collectionId/attributes/float', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/float') ->desc('Create float attribute') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1479,21 +1510,16 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float' ->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); - $max = (is_null($max)) ? PHP_FLOAT_MAX : \floatval($max); + $min = \is_null($min) ? -PHP_FLOAT_MAX : $min; + $max = \is_null($max) ? PHP_FLOAT_MAX : $max; if ($min > $max) { throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Minimum value must be lesser than maximum value'); } - // Ensure default value is a float - if (!is_null($default)) { - $default = \floatval($default); - } - $validator = new Range($min, $max, Database::VAR_FLOAT); - if (!is_null($default) && !$validator->isValid($default)) { + if (!\is_null($default) && !$validator->isValid($default)) { throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, $validator->getDescription()); } @@ -1524,11 +1550,12 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float' }); App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean') - ->alias('/v1/database/collections/:collectionId/attributes/boolean', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/boolean') ->desc('Create boolean attribute') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1565,11 +1592,12 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolea }); App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/datetime') - ->alias('/v1/database/collections/:collectionId/attributes/datetime', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/datetime') ->desc('Create datetime attribute') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1583,7 +1611,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('default', null, new DatetimeValidator(), 'Default value for the attribute in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Cannot be set when attribute is required.', true) + ->param('default', null, fn (Database $dbForProject) => new DatetimeValidator($dbForProject->getAdapter()->getMinDateTime(), $dbForProject->getAdapter()->getMaxDateTime()), 'Default value for the attribute in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Cannot be set when attribute is required.', true, ['dbForProject']) ->param('array', false, new Boolean(), 'Is attribute an array?', true) ->inject('response') ->inject('dbForProject') @@ -1609,11 +1637,12 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti }); App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relationship') - ->alias('/v1/database/collections/:collectionId/attributes/relationship', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/relationship') ->desc('Create relationship attribute') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('audits.event', 'attribute.create') ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}') ->label('sdk.namespace', 'databases') @@ -1737,10 +1766,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati }); App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') - ->alias('/v1/database/collections/:collectionId/attributes', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes') ->desc('List attributes') ->groups(['api', 'database']) ->label('scope', 'collections.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'listAttributes') @@ -1767,16 +1797,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') throw new Exception(Exception::COLLECTION_NOT_FOUND); } - try { - $queries = Query::parseQueries($queries); - } catch (QueryException $e) { - throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); - } + $queries = Query::parseQueries($queries); \array_push( $queries, + Query::equal('databaseInternalId', [$database->getInternalId()]), Query::equal('collectionInternalId', [$collection->getInternalId()]), - Query::equal('databaseInternalId', [$database->getInternalId()]) ); /** @@ -1785,13 +1811,19 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') $cursor = \array_filter($queries, function ($query) { return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); }); + $cursor = \reset($cursor); if ($cursor) { + $validator = new Cursor(); + if (!$validator->isValid($cursor)) { + throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); + } + $attributeId = $cursor->getValue(); $cursorDocument = Authorization::skip(fn () => $dbForProject->find('attributes', [ - Query::equal('collectionInternalId', [$collection->getInternalId()]), Query::equal('databaseInternalId', [$database->getInternalId()]), + Query::equal('collectionInternalId', [$collection->getInternalId()]), Query::equal('key', [$attributeId]), Query::limit(1), ])); @@ -1815,10 +1847,11 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') }); App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key') - ->alias('/v1/database/collections/:collectionId/attributes/:key', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/:key') ->desc('Get attribute') ->groups(['api', 'database']) ->label('scope', 'collections.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'getAttribute') @@ -1893,6 +1926,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/strin ->desc('Update string attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->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}') @@ -1907,7 +1941,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/strin ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') ->param('default', null, new Nullable(new Text(0, 0)), 'Default value for attribute when not provided. Cannot be set when attribute is required.') - ->param('size', null, new Integer(), 'Maximum size of the string attribute.', true) + ->param('size', null, new Range(1, APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH, Range::TYPE_INTEGER), 'Maximum size of the string attribute.', true) ->param('newKey', null, new Key(), 'New attribute key.', true) ->inject('response') ->inject('dbForProject') @@ -1936,6 +1970,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/email ->desc('Update email attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->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}') @@ -1977,6 +2012,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/enum/ ->desc('Update enum attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->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}') @@ -2020,6 +2056,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', 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}') @@ -2061,6 +2098,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/url/: ->desc('Update URL attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->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}') @@ -2102,6 +2140,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/integ ->desc('Update integer attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->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}') @@ -2153,6 +2192,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/float ->desc('Update float attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->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}') @@ -2204,6 +2244,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/boole ->desc('Update boolean attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->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}') @@ -2244,6 +2285,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datet ->desc('Update dateTime attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->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}') @@ -2257,7 +2299,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datet ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('default', null, new Nullable(new DatetimeValidator()), 'Default value for attribute when not provided. Cannot be set when attribute is required.') + ->param('default', null, fn (Database $dbForProject) => new Nullable(new DatetimeValidator($dbForProject->getAdapter()->getMinDateTime(), $dbForProject->getAdapter()->getMaxDateTime())), 'Default value for attribute when not provided. Cannot be set when attribute is required.', injections: ['dbForProject']) ->param('newKey', null, new Key(), 'New attribute key.', true) ->inject('response') ->inject('dbForProject') @@ -2284,6 +2326,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/ ->desc('Update relationship attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->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}') @@ -2337,10 +2380,11 @@ App::patch('/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']) + ->alias('/v1/database/collections/:collectionId/attributes/:key') ->desc('Delete attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') + ->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}') @@ -2450,11 +2494,12 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key }); App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') - ->alias('/v1/database/collections/:collectionId/indexes', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/indexes') ->desc('Create index') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].indexes.[indexId].create') ->label('scope', 'collections.write') + ->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]) @@ -2547,7 +2592,6 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') $attributeStatus = $oldAttributes[$attributeIndex]['status']; $attributeType = $oldAttributes[$attributeIndex]['type']; - $attributeSize = $oldAttributes[$attributeIndex]['size']; $attributeArray = $oldAttributes[$attributeIndex]['array'] ?? false; if ($attributeType === Database::VAR_RELATIONSHIP) { @@ -2561,10 +2605,6 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') $lengths[$i] = null; - if ($attributeType === Database::VAR_STRING) { - $lengths[$i] = $attributeSize; // set attribute size as index length only for strings - } - if ($attributeArray === true) { $lengths[$i] = Database::ARRAY_INDEX_LENGTH; $orders[$i] = null; @@ -2587,7 +2627,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') $validator = new IndexValidator( $collection->getAttribute('attributes'), - $dbForProject->getAdapter()->getMaxIndexLength() + $dbForProject->getAdapter()->getMaxIndexLength(), + $dbForProject->getAdapter()->getInternalIndexesKeys(), ); if (!$validator->isValid($index)) { throw new Exception(Exception::INDEX_INVALID, $validator->getDescription()); @@ -2620,10 +2661,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') }); App::get('/v1/databases/:databaseId/collections/:collectionId/indexes') - ->alias('/v1/database/collections/:collectionId/indexes', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/indexes') ->desc('List indexes') ->groups(['api', 'database']) ->label('scope', 'collections.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'listIndexes') @@ -2650,13 +2692,13 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes') throw new Exception(Exception::COLLECTION_NOT_FOUND); } - try { - $queries = Query::parseQueries($queries); - } catch (QueryException $e) { - throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); - } + $queries = Query::parseQueries($queries); - \array_push($queries, Query::equal('collectionId', [$collectionId]), Query::equal('databaseId', [$databaseId])); + \array_push( + $queries, + Query::equal('databaseId', [$databaseId]), + Query::equal('collectionId', [$collectionId]), + ); /** * Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries @@ -2667,6 +2709,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()]), @@ -2694,6 +2741,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') ->desc('Get index') ->groups(['api', 'database']) ->label('scope', 'collections.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'getIndex') @@ -2733,6 +2781,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') ->desc('Delete index') ->groups(['api', 'database']) ->label('scope', 'collections.write') + ->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}') @@ -2798,6 +2847,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', 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}') @@ -2978,10 +3028,12 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') try { $document = $dbForProject->createDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $document); - } catch (StructureException $exception) { - throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $exception->getMessage()); - } catch (DuplicateException $exception) { + } catch (StructureException $e) { + throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage()); + } catch (DuplicateException $e) { throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS); + } catch (NotFoundException $e) { + throw new Exception(Exception::COLLECTION_NOT_FOUND); } // Add $collectionId and $databaseId for all documents @@ -3049,6 +3101,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') ->desc('List documents') ->groups(['api', 'database']) ->label('scope', 'documents.read') + ->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') @@ -3094,6 +3147,12 @@ 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)); @@ -3105,14 +3164,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') $cursor->setValue($cursorDocument); } - try { - $documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries); - $total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, APP_LIMIT_COUNT); - } catch (AuthorizationException) { - throw new Exception(Exception::USER_UNAUTHORIZED); - } catch (QueryException $e) { - throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); - } + $documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries); + $total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, APP_LIMIT_COUNT); // Add $collectionId and $databaseId for all documents $processDocument = (function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database): bool { @@ -3204,6 +3257,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->desc('Get document') ->groups(['api', 'database']) ->label('scope', 'documents.read') + ->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') @@ -3296,6 +3350,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->desc('List document logs') ->groups(['api', 'database']) ->label('scope', 'documents.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'listDocumentLogs') @@ -3396,11 +3451,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen }); App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId') - ->alias('/v1/database/collections/:collectionId/documents/:documentId', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/documents/:documentId') ->desc('Update document') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].update') ->label('scope', 'documents.write') + ->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}') @@ -3573,8 +3629,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum throw new Exception(Exception::USER_UNAUTHORIZED); } catch (DuplicateException) { throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS); - } catch (StructureException $exception) { - throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $exception->getMessage()); + } catch (StructureException $e) { + throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage()); + } catch (NotFoundException $e) { + throw new Exception(Exception::COLLECTION_NOT_FOUND); } // Add $collectionId and $databaseId for all documents @@ -3636,6 +3694,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu ->desc('Delete document') ->groups(['api', 'database']) ->label('scope', 'documents.write') + ->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}') @@ -3682,12 +3741,16 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu throw new Exception(Exception::DOCUMENT_NOT_FOUND); } - $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $documentId) { - $dbForProject->deleteDocument( - 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), - $documentId - ); - }); + try { + $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $documentId) { + $dbForProject->deleteDocument( + 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), + $documentId + ); + }); + } catch (NotFoundException $e) { + throw new Exception(Exception::COLLECTION_NOT_FOUND); + } // Add $collectionId and $databaseId for all documents $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) { @@ -3750,6 +3813,7 @@ App::get('/v1/databases/usage') ->desc('Get databases usage stats') ->groups(['api', 'database', 'usage']) ->label('scope', 'collections.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'getUsage') @@ -3831,6 +3895,7 @@ App::get('/v1/databases/:databaseId/usage') ->desc('Get database usage stats') ->groups(['api', 'database', 'usage']) ->label('scope', 'collections.read') + ->label('resourceType', RESOURCE_TYPE_DATABASES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'getDatabaseUsage') @@ -3918,6 +3983,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/usage') ->desc('Get collection usage stats') ->groups(['api', 'database', 'usage']) ->label('scope', 'collections.read') + ->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 f1e8d82a9b..5934258037 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -23,6 +23,8 @@ use Appwrite\Utopia\Response; use Appwrite\Utopia\Response\Model\Rule; use Executor\Executor; use MaxMind\Db\Reader; +use Utopia\Abuse\Abuse; +use Utopia\Abuse\Adapters\Database\TimeLimit; use Utopia\App; use Utopia\CLI\Console; use Utopia\Config\Config; @@ -37,6 +39,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; @@ -137,6 +140,7 @@ App::post('/v1/functions') ->desc('Create function') ->label('scope', 'functions.write') ->label('event', 'functions.[functionId].create') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('audits.event', 'function.create') ->label('audits.resource', 'function/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -180,11 +184,40 @@ App::post('/v1/functions') ->inject('user') ->inject('queueForEvents') ->inject('queueForBuilds') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->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) { + ->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 $dbForPlatform, GitHub $github) use ($redeployVcs) { $functionId = ($functionId == 'unique()') ? ID::unique() : $functionId; + // Temporary abuse check + $abuseCheck = function () use ($project, $dbForProject, $response) { + $abuseKey = "projectId:{projectId},url:{url}"; + $abuseLimit = App::getEnv('_APP_FUNCTIONS_CREATION_ABUSE_LIMIT', 50); + $abuseTime = 86400; // 1 day + + $timeLimit = new TimeLimit($abuseKey, $abuseLimit, $abuseTime, $dbForProject); + $timeLimit + ->setParam('{projectId}', $project->getId()) + ->setParam('{url}', '/v1/functions'); + + $abuse = new Abuse($timeLimit); + $remaining = $timeLimit->remaining(); + $limit = $timeLimit->limit(); + $time = (new \DateTime($timeLimit->time()))->getTimestamp() + $abuseTime; + + $response + ->addHeader('X-RateLimit-Limit', $limit) + ->addHeader('X-RateLimit-Remaining', $remaining) + ->addHeader('X-RateLimit-Reset', $time); + + $enabled = System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled'; + if ($enabled && $abuse->check()) { + throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED); + } + }; + + $abuseCheck(); + $allowList = \array_filter(\explode(',', System::getEnv('_APP_FUNCTIONS_RUNTIMES', ''))); if (!empty($allowList) && !\in_array($runtime, $allowList)) { @@ -205,7 +238,7 @@ App::post('/v1/functions') ->setAttribute('version', $templateVersion); } - $installation = $dbForConsole->getDocument('installations', $installationId); + $installation = $dbForPlatform->getDocument('installations', $installationId); if (!empty($installationId) && $installation->isEmpty()) { throw new Exception(Exception::INSTALLATION_NOT_FOUND); @@ -247,7 +280,7 @@ App::post('/v1/functions') ])); $schedule = Authorization::skip( - fn () => $dbForConsole->createDocument('schedules', new Document([ + fn () => $dbForPlatform->createDocument('schedules', new Document([ 'region' => System::getEnv('_APP_REGION', 'default'), // Todo replace with projects region 'resourceType' => 'function', 'resourceId' => $function->getId(), @@ -266,7 +299,7 @@ App::post('/v1/functions') if (!empty($providerRepositoryId)) { $teamId = $project->getAttribute('teamId', ''); - $repository = $dbForConsole->createDocument('repositories', new Document([ + $repository = $dbForPlatform->createDocument('repositories', new Document([ '$id' => ID::unique(), '$permissions' => [ Permission::read(Role::team(ID::custom($teamId))), @@ -324,12 +357,13 @@ App::post('/v1/functions') $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); if (!empty($functionsDomain)) { - $ruleId = ID::unique(); $routeSubdomain = ID::unique(); $domain = "{$routeSubdomain}.{$functionsDomain}"; + // TODO: @christyjacob remove once we migrate the rules in 1.7.x + $ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique(); $rule = Authorization::skip( - fn () => $dbForConsole->createDocument('rules', new Document([ + fn () => $dbForPlatform->createDocument('rules', new Document([ '$id' => $ruleId, 'projectId' => $project->getId(), 'projectInternalId' => $project->getInternalId(), @@ -399,6 +433,7 @@ App::get('/v1/functions') ->groups(['api', 'functions']) ->desc('List functions') ->label('scope', 'functions.read') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'list') @@ -431,6 +466,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); @@ -453,6 +494,7 @@ App::get('/v1/functions/runtimes') ->groups(['api', 'functions']) ->desc('List runtimes') ->label('scope', 'functions.read') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'listRuntimes') @@ -486,6 +528,7 @@ App::get('/v1/functions/specifications') ->groups(['api', 'functions']) ->desc('List available function runtime specifications') ->label('scope', 'functions.read') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'listSpecifications') @@ -522,6 +565,7 @@ App::get('/v1/functions/:functionId') ->groups(['api', 'functions']) ->desc('Get function') ->label('scope', 'functions.read') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'get') @@ -546,6 +590,7 @@ App::get('/v1/functions/:functionId/usage') ->desc('Get function usage') ->groups(['api', 'functions', 'usage']) ->label('scope', 'functions.read') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'getFunctionUsage') @@ -650,6 +695,7 @@ App::get('/v1/functions/usage') ->desc('Get functions usage') ->groups(['api', 'functions']) ->label('scope', 'functions.read') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'getUsage') @@ -749,6 +795,7 @@ App::put('/v1/functions/:functionId') ->groups(['api', 'functions']) ->desc('Update function') ->label('scope', 'functions.write') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('event', 'functions.[functionId].update') ->label('audits.event', 'function.update') ->label('audits.resource', 'function/{response.$id}') @@ -788,9 +835,9 @@ App::put('/v1/functions/:functionId') ->inject('project') ->inject('queueForEvents') ->inject('queueForBuilds') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->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) { + ->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 $dbForPlatform, GitHub $github) use ($redeployVcs) { // TODO: If only branch changes, re-deploy $function = $dbForProject->getDocument('functions', $functionId); @@ -798,7 +845,7 @@ App::put('/v1/functions/:functionId') throw new Exception(Exception::FUNCTION_NOT_FOUND); } - $installation = $dbForConsole->getDocument('installations', $installationId); + $installation = $dbForPlatform->getDocument('installations', $installationId); if (!empty($installationId) && $installation->isEmpty()) { throw new Exception(Exception::INSTALLATION_NOT_FOUND); @@ -829,7 +876,7 @@ App::put('/v1/functions/:functionId') // Git disconnect logic. Disconnecting only when providerRepositoryId is empty, allowing for continue updates without disconnecting git if ($isConnected && ($providerRepositoryId !== null && empty($providerRepositoryId))) { - $repositories = $dbForConsole->find('repositories', [ + $repositories = $dbForPlatform->find('repositories', [ Query::equal('projectInternalId', [$project->getInternalId()]), Query::equal('resourceInternalId', [$function->getInternalId()]), Query::equal('resourceType', ['function']), @@ -837,7 +884,7 @@ App::put('/v1/functions/:functionId') ]); foreach ($repositories as $repository) { - $dbForConsole->deleteDocument('repositories', $repository->getId()); + $dbForPlatform->deleteDocument('repositories', $repository->getId()); } $providerRepositoryId = ''; @@ -853,7 +900,7 @@ App::put('/v1/functions/:functionId') if (!$isConnected && !empty($providerRepositoryId)) { $teamId = $project->getAttribute('teamId', ''); - $repository = $dbForConsole->createDocument('repositories', new Document([ + $repository = $dbForPlatform->createDocument('repositories', new Document([ '$id' => ID::unique(), '$permissions' => [ Permission::read(Role::team(ID::custom($teamId))), @@ -935,12 +982,12 @@ App::put('/v1/functions/:functionId') } // Inform scheduler if function is still active - $schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId')); + $schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId')); $schedule ->setAttribute('resourceUpdatedAt', DateTime::now()) ->setAttribute('schedule', $function->getAttribute('schedule')) ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment'))); - Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); + Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule)); $queueForEvents->setParam('functionId', $function->getId()); @@ -951,6 +998,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download') ->groups(['api', 'functions']) ->desc('Download deployment') ->label('scope', 'functions.read') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'getDeploymentDownload') @@ -987,7 +1035,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"'); @@ -1036,6 +1084,7 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId') ->groups(['api', 'functions']) ->desc('Update deployment') ->label('scope', 'functions.write') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('event', 'functions.[functionId].deployments.[deploymentId].update') ->label('audits.event', 'deployment.update') ->label('audits.resource', 'function/{request.functionId}') @@ -1051,8 +1100,8 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId') ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('dbForConsole') - ->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Event $queueForEvents, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Event $queueForEvents, Database $dbForPlatform) { $function = $dbForProject->getDocument('functions', $functionId); $deployment = $dbForProject->getDocument('deployments', $deploymentId); @@ -1080,12 +1129,12 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId') ]))); // Inform scheduler if function is still active - $schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId')); + $schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId')); $schedule ->setAttribute('resourceUpdatedAt', DateTime::now()) ->setAttribute('schedule', $function->getAttribute('schedule')) ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment'))); - Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); + Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule)); $queueForEvents ->setParam('functionId', $function->getId()) @@ -1098,6 +1147,7 @@ App::delete('/v1/functions/:functionId') ->groups(['api', 'functions']) ->desc('Delete function') ->label('scope', 'functions.write') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('event', 'functions.[functionId].delete') ->label('audits.event', 'function.delete') ->label('audits.resource', 'function/{request.functionId}') @@ -1112,8 +1162,8 @@ App::delete('/v1/functions/:functionId') ->inject('dbForProject') ->inject('queueForDeletes') ->inject('queueForEvents') - ->inject('dbForConsole') - ->action(function (string $functionId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $functionId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Database $dbForPlatform) { $function = $dbForProject->getDocument('functions', $functionId); @@ -1126,11 +1176,11 @@ App::delete('/v1/functions/:functionId') } // Inform scheduler to no longer run function - $schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId')); + $schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId')); $schedule ->setAttribute('resourceUpdatedAt', DateTime::now()) ->setAttribute('active', false); - Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); + Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule)); $queueForDeletes ->setType(DELETE_TYPE_DOCUMENT) @@ -1145,6 +1195,7 @@ App::post('/v1/functions/:functionId/deployments') ->groups(['api', 'functions']) ->desc('Create deployment') ->label('scope', 'functions.write') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('event', 'functions.[functionId].deployments.[deploymentId].create') ->label('audits.event', 'deployment.create') ->label('audits.resource', 'function/{request.functionId}') @@ -1364,6 +1415,7 @@ App::get('/v1/functions/:functionId/deployments') ->groups(['api', 'functions']) ->desc('List deployments') ->label('scope', 'functions.read') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'listDeployments') @@ -1407,6 +1459,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); @@ -1441,6 +1499,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId') ->groups(['api', 'functions']) ->desc('Get deployment') ->label('scope', 'functions.read') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'getDeployment') @@ -1484,6 +1543,7 @@ App::delete('/v1/functions/:functionId/deployments/:deploymentId') ->groups(['api', 'functions']) ->desc('Delete deployment') ->label('scope', 'functions.write') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('event', 'functions.[functionId].deployments.[deploymentId].delete') ->label('audits.event', 'deployment.delete') ->label('audits.resource', 'function/{request.functionId}') @@ -1549,6 +1609,7 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/build') ->groups(['api', 'functions']) ->desc('Rebuild deployment') ->label('scope', 'functions.write') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('event', 'functions.[functionId].deployments.[deploymentId].update') ->label('audits.event', 'deployment.update') ->label('audits.resource', 'function/{request.functionId}') @@ -1617,6 +1678,7 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId/build') ->groups(['api', 'functions']) ->desc('Cancel deployment') ->label('scope', 'functions.write') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('audits.event', 'deployment.update') ->label('audits.resource', 'function/{request.functionId}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -1706,7 +1768,9 @@ App::post('/v1/functions/:functionId/executions') ->groups(['api', 'functions']) ->desc('Create execution') ->label('scope', 'execution.write') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('event', 'functions.[functionId].executions.[executionId].create') + ->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') @@ -1721,18 +1785,18 @@ App::post('/v1/functions/:functionId/executions') ->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) ->param('headers', [], new AnyOf([new Assoc(), new Text(65535)], AnyOf::TYPE_MIXED), 'HTTP headers of execution. Defaults to empty.', true) - ->param('scheduledAt', null, new DatetimeValidator(true, DateTimeValidator::PRECISION_MINUTES, 60), '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.', true) + ->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true, precision: DateTimeValidator::PRECISION_MINUTES, offset: 60), '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.', true) ->inject('response') ->inject('request') ->inject('project') ->inject('dbForProject') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('user') ->inject('queueForEvents') ->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) { + ->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 $dbForPlatform, Document $user, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb) { $async = \strval($async) === 'true' || \strval($async) === '1'; if (!$async && !is_null($scheduledAt)) { @@ -1923,7 +1987,7 @@ App::post('/v1/functions/:functionId/executions') 'userId' => $user->getId() ]; - $schedule = $dbForConsole->createDocument('schedules', new Document([ + $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => System::getEnv('_APP_REGION', 'default'), 'resourceType' => ScheduleExecutions::getSupportedResource(), 'resourceId' => $execution->getId(), @@ -2108,6 +2172,7 @@ App::get('/v1/functions/:functionId/executions') ->groups(['api', 'functions']) ->desc('List executions') ->label('scope', 'execution.read') + ->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') @@ -2153,6 +2218,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); @@ -2189,6 +2260,7 @@ App::get('/v1/functions/:functionId/executions/:executionId') ->groups(['api', 'functions']) ->desc('Get execution') ->label('scope', 'execution.read') + ->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') @@ -2236,6 +2308,7 @@ App::delete('/v1/functions/:functionId/executions/:executionId') ->groups(['api', 'functions']) ->desc('Delete execution') ->label('scope', 'execution.write') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('event', 'functions.[functionId].executions.[executionId].delete') ->label('audits.event', 'executions.delete') ->label('audits.resource', 'function/{request.functionId}') @@ -2249,9 +2322,9 @@ App::delete('/v1/functions/:functionId/executions/:executionId') ->param('executionId', '', new UID(), 'Execution ID.') ->inject('response') ->inject('dbForProject') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('queueForEvents') - ->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, Database $dbForConsole, Event $queueForEvents) { + ->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, Database $dbForPlatform, Event $queueForEvents) { $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { @@ -2277,18 +2350,18 @@ App::delete('/v1/functions/:functionId/executions/:executionId') } if ($status === 'scheduled') { - $schedule = $dbForConsole->findOne('schedules', [ + $schedule = $dbForPlatform->findOne('schedules', [ Query::equal('resourceId', [$execution->getId()]), Query::equal('resourceType', [ScheduleExecutions::getSupportedResource()]), Query::equal('active', [true]), ]); - if ($schedule && !$schedule->isEmpty()) { + if (!$schedule->isEmpty()) { $schedule ->setAttribute('resourceUpdatedAt', DateTime::now()) ->setAttribute('active', false); - Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); + Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule)); } } @@ -2306,6 +2379,7 @@ App::post('/v1/functions/:functionId/variables') ->desc('Create variable') ->groups(['api', 'functions']) ->label('scope', 'functions.write') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('audits.event', 'variable.create') ->label('audits.resource', 'function/{request.functionId}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -2320,8 +2394,8 @@ App::post('/v1/functions/:functionId/variables') ->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false) ->inject('response') ->inject('dbForProject') - ->inject('dbForConsole') - ->action(function (string $functionId, string $key, string $value, Response $response, Database $dbForProject, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $functionId, string $key, string $value, Response $response, Database $dbForProject, Database $dbForPlatform) { $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { @@ -2354,12 +2428,12 @@ App::post('/v1/functions/:functionId/variables') $dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false)); // Inform scheduler to pull the latest changes - $schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId')); + $schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId')); $schedule ->setAttribute('resourceUpdatedAt', DateTime::now()) ->setAttribute('schedule', $function->getAttribute('schedule')) ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment'))); - Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); + Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule)); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -2370,6 +2444,7 @@ App::get('/v1/functions/:functionId/variables') ->desc('List variables') ->groups(['api', 'functions']) ->label('scope', 'functions.read') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'listVariables') @@ -2397,6 +2472,7 @@ App::get('/v1/functions/:functionId/variables/:variableId') ->desc('Get variable') ->groups(['api', 'functions']) ->label('scope', 'functions.read') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'getVariable') @@ -2436,6 +2512,7 @@ App::put('/v1/functions/:functionId/variables/:variableId') ->desc('Update variable') ->groups(['api', 'functions']) ->label('scope', 'functions.write') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('audits.event', 'variable.update') ->label('audits.resource', 'function/{request.functionId}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -2451,8 +2528,8 @@ App::put('/v1/functions/:functionId/variables/:variableId') ->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', true) ->inject('response') ->inject('dbForProject') - ->inject('dbForConsole') - ->action(function (string $functionId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $functionId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject, Database $dbForPlatform) { $function = $dbForProject->getDocument('functions', $functionId); @@ -2483,12 +2560,12 @@ App::put('/v1/functions/:functionId/variables/:variableId') $dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false)); // Inform scheduler to pull the latest changes - $schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId')); + $schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId')); $schedule ->setAttribute('resourceUpdatedAt', DateTime::now()) ->setAttribute('schedule', $function->getAttribute('schedule')) ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment'))); - Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); + Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule)); $response->dynamic($variable, Response::MODEL_VARIABLE); }); @@ -2497,6 +2574,7 @@ App::delete('/v1/functions/:functionId/variables/:variableId') ->desc('Delete variable') ->groups(['api', 'functions']) ->label('scope', 'functions.write') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('audits.event', 'variable.delete') ->label('audits.resource', 'function/{request.functionId}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -2509,8 +2587,8 @@ App::delete('/v1/functions/:functionId/variables/:variableId') ->param('variableId', '', new UID(), 'Variable unique ID.', false) ->inject('response') ->inject('dbForProject') - ->inject('dbForConsole') - ->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject, Database $dbForPlatform) { $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { @@ -2531,12 +2609,12 @@ App::delete('/v1/functions/:functionId/variables/:variableId') $dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false)); // Inform scheduler to pull the latest changes - $schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId')); + $schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId')); $schedule ->setAttribute('resourceUpdatedAt', DateTime::now()) ->setAttribute('schedule', $function->getAttribute('schedule')) ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment'))); - Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); + Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule)); $response->noContent(); }); @@ -2545,6 +2623,7 @@ App::get('/v1/functions/templates') ->groups(['api']) ->desc('List function templates') ->label('scope', 'public') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'listTemplates') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) @@ -2582,6 +2661,7 @@ App::get('/v1/functions/templates') App::get('/v1/functions/templates/:templateId') ->desc('Get function template') ->label('scope', 'public') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'getTemplate') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) @@ -2594,9 +2674,11 @@ 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) { + $filtered = \array_filter($templates, function ($template) use ($templateId) { return $template['id'] === $templateId; - })); + }); + + $template = array_shift($filtered); if (empty($template)) { throw new Exception(Exception::FUNCTION_TEMPLATE_NOT_FOUND); diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index f4581df8e4..60a8c0ca97 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -135,6 +135,7 @@ App::get('/v1/health/cache') foreach ($configs as $key => $config) { foreach ($config as $database) { try { + /** @var \Utopia\Cache\Adapter $adapter */ $adapter = $pools->get($database)->pop()->getResource(); $checkStart = \microtime(true); @@ -191,11 +192,11 @@ App::get('/v1/health/queue') foreach ($configs as $key => $config) { foreach ($config as $database) { + $checkStart = \microtime(true); try { + /** @var Connection $adapter */ $adapter = $pools->get($database)->pop()->getResource(); - $checkStart = \microtime(true); - if ($adapter->ping()) { $output[] = new Document([ 'name' => $key . " ($database)", @@ -249,6 +250,7 @@ App::get('/v1/health/pubsub') foreach ($configs as $key => $config) { foreach ($config as $database) { try { + /** @var \Appwrite\PubSub\Adapter $adapter */ $adapter = $pools->get($database)->pop()->getResource(); $checkStart = \microtime(true); 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/messaging.php b/app/controllers/api/messaging.php index 7da0348a8f..00a68454ee 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; @@ -55,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', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createMailgunProvider') @@ -142,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', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createSendgridProvider') @@ -217,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', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createSmtpProvider') @@ -304,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', RESOURCE_TYPE_PROVIDERS) ->label('event', 'providers.[providerId].create') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') @@ -381,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', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createTelesignProvider') @@ -458,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', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createTextmagicProvider') @@ -535,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', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createTwilioProvider') @@ -612,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', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createVonageProvider') @@ -689,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', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createFcmProvider') @@ -752,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', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createApnsProvider') @@ -835,6 +846,7 @@ App::get('/v1/messaging/providers') ->desc('List providers') ->groups(['api', 'messaging']) ->label('scope', 'providers.read') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listProviders') @@ -866,6 +878,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)); @@ -886,6 +903,7 @@ App::get('/v1/messaging/providers/:providerId/logs') ->desc('List provider logs') ->groups(['api', 'messaging']) ->label('scope', 'providers.read') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listProviderLogs') @@ -974,6 +992,7 @@ App::get('/v1/messaging/providers/:providerId') ->desc('Get provider') ->groups(['api', 'messaging']) ->label('scope', 'providers.read') + ->label('resourceType', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'getProvider') @@ -1001,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', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateMailgunProvider') @@ -1107,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', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateSendgridProvider') @@ -1198,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', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateSmtpProvider') @@ -1320,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', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateMsg91Provider') @@ -1400,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', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateTelesignProvider') @@ -1482,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', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateTextmagicProvider') @@ -1564,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', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateTwilioProvider') @@ -1646,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', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateVonageProvider') @@ -1728,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', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateFcmProvider') @@ -1797,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', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateApnsProvider') @@ -1892,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', RESOURCE_TYPE_PROVIDERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'deleteProvider') @@ -1927,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', RESOURCE_TYPE_TOPICS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createTopic') @@ -1967,6 +1998,7 @@ App::get('/v1/messaging/topics') ->desc('List topics') ->groups(['api', 'messaging']) ->label('scope', 'topics.read') + ->label('resourceType', RESOURCE_TYPE_TOPICS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listTopics') @@ -1998,6 +2030,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)); @@ -2018,6 +2055,7 @@ App::get('/v1/messaging/topics/:topicId/logs') ->desc('List topic logs') ->groups(['api', 'messaging']) ->label('scope', 'topics.read') + ->label('resourceType', RESOURCE_TYPE_TOPICS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listTopicLogs') @@ -2107,6 +2145,7 @@ App::get('/v1/messaging/topics/:topicId') ->desc('Get topic') ->groups(['api', 'messaging']) ->label('scope', 'topics.read') + ->label('resourceType', RESOURCE_TYPE_TOPICS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'getTopic') @@ -2135,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', RESOURCE_TYPE_TOPICS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateTopic') @@ -2179,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', RESOURCE_TYPE_TOPICS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'deleteTopic') @@ -2219,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', 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') @@ -2312,6 +2354,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers') ->desc('List subscribers') ->groups(['api', 'messaging']) ->label('scope', 'subscribers.read') + ->label('resourceType', RESOURCE_TYPE_SUBSCRIBERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listSubscribers') @@ -2352,6 +2395,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)); @@ -2386,6 +2434,7 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs') ->desc('List subscriber logs') ->groups(['api', 'messaging']) ->label('scope', 'subscribers.read') + ->label('resourceType', RESOURCE_TYPE_SUBSCRIBERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listSubscriberLogs') @@ -2475,6 +2524,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId') ->desc('Get subscriber') ->groups(['api', 'messaging']) ->label('scope', 'subscribers.read') + ->label('resourceType', RESOURCE_TYPE_SUBSCRIBERS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'getSubscriber') @@ -2517,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', 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') @@ -2576,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', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createEmail') @@ -2597,11 +2649,11 @@ App::post('/v1/messaging/messages/email') ->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->inject('queueForEvents') ->inject('dbForProject') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, string $subject, string $content, array $topics, array $users, array $targets, array $cc, array $bcc, array $attachments, bool $draft, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { + ->action(function (string $messageId, string $subject, string $content, array $topics, array $users, array $targets, array $cc, array $bcc, array $attachments, bool $draft, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) { $messageId = $messageId == 'unique()' ? ID::unique() : $messageId; @@ -2690,7 +2742,7 @@ App::post('/v1/messaging/messages/email') ->setMessageId($message->getId()); break; case MessageStatus::SCHEDULED: - $schedule = $dbForConsole->createDocument('schedules', new Document([ + $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => System::getEnv('_APP_REGION', 'default'), 'resourceType' => 'message', 'resourceId' => $message->getId(), @@ -2728,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', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createSms') @@ -2744,11 +2797,11 @@ App::post('/v1/messaging/messages/sms') ->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->inject('queueForEvents') ->inject('dbForProject') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, string $content, array $topics, array $users, array $targets, bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { + ->action(function (string $messageId, string $content, array $topics, array $users, array $targets, bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) { $messageId = $messageId == 'unique()' ? ID::unique() : $messageId; @@ -2806,7 +2859,7 @@ App::post('/v1/messaging/messages/sms') ->setMessageId($message->getId()); break; case MessageStatus::SCHEDULED: - $schedule = $dbForConsole->createDocument('schedules', new Document([ + $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => System::getEnv('_APP_REGION', 'default'), 'resourceType' => 'message', 'resourceId' => $message->getId(), @@ -2844,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', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'createPush') @@ -2869,11 +2923,11 @@ App::post('/v1/messaging/messages/push') ->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->inject('queueForEvents') ->inject('dbForProject') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, string $title, string $body, array $topics, array $users, array $targets, ?array $data, string $action, string $image, string $icon, string $sound, string $color, string $tag, string $badge, bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { + ->action(function (string $messageId, string $title, string $body, array $topics, array $users, array $targets, ?array $data, string $action, string $image, string $icon, string $sound, string $color, string $tag, string $badge, bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) { $messageId = $messageId == 'unique()' ? ID::unique() : $messageId; @@ -2982,7 +3036,7 @@ App::post('/v1/messaging/messages/push') ->setMessageId($message->getId()); break; case MessageStatus::SCHEDULED: - $schedule = $dbForConsole->createDocument('schedules', new Document([ + $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => System::getEnv('_APP_REGION', 'default'), 'resourceType' => 'message', 'resourceId' => $message->getId(), @@ -3017,6 +3071,7 @@ App::get('/v1/messaging/messages') ->desc('List messages') ->groups(['api', 'messaging']) ->label('scope', 'messages.read') + ->label('resourceType', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listMessages') @@ -3048,6 +3103,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)); @@ -3068,6 +3128,7 @@ App::get('/v1/messaging/messages/:messageId/logs') ->desc('List message logs') ->groups(['api', 'messaging']) ->label('scope', 'messages.read') + ->label('resourceType', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listMessageLogs') @@ -3157,6 +3218,7 @@ App::get('/v1/messaging/messages/:messageId/targets') ->desc('List message targets') ->groups(['api', 'messaging']) ->label('scope', 'messages.read') + ->label('resourceType', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'listTargets') @@ -3202,6 +3264,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); @@ -3222,6 +3289,7 @@ App::get('/v1/messaging/messages/:messageId') ->desc('Get message') ->groups(['api', 'messaging']) ->label('scope', 'messages.read') + ->label('resourceType', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'getMessage') @@ -3249,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', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateEmail') @@ -3270,11 +3339,11 @@ App::patch('/v1/messaging/messages/email/:messageId') ->param('attachments', null, new ArrayList(new CompoundUID()), 'Array of compound ID strings of bucket IDs and file IDs to be attached to the email. They should be formatted as :.', true) ->inject('queueForEvents') ->inject('dbForProject') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $subject, ?string $content, ?bool $draft, ?bool $html, ?array $cc, ?array $bcc, ?string $scheduledAt, ?array $attachments, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { + ->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $subject, ?string $content, ?bool $draft, ?bool $html, ?array $cc, ?array $bcc, ?string $scheduledAt, ?array $attachments, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) { $message = $dbForProject->getDocument('messages', $messageId); if ($message->isEmpty()) { @@ -3326,7 +3395,7 @@ App::patch('/v1/messaging/messages/email/:messageId') } if (\is_null($currentScheduledAt) && !\is_null($scheduledAt)) { - $schedule = $dbForConsole->createDocument('schedules', new Document([ + $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => System::getEnv('_APP_REGION', 'default'), 'resourceType' => 'message', 'resourceId' => $message->getId(), @@ -3341,7 +3410,7 @@ App::patch('/v1/messaging/messages/email/:messageId') } if (!\is_null($currentScheduledAt)) { - $schedule = $dbForConsole->getDocument('schedules', $message->getAttribute('scheduleId')); + $schedule = $dbForPlatform->getDocument('schedules', $message->getAttribute('scheduleId')); $scheduledStatus = ($status ?? $message->getAttribute('status')) === MessageStatus::SCHEDULED; if ($schedule->isEmpty()) { @@ -3356,7 +3425,7 @@ App::patch('/v1/messaging/messages/email/:messageId') $schedule->setAttribute('schedule', $scheduledAt); } - $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule); + $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule); } if (!\is_null($scheduledAt)) { @@ -3449,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', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updateSms') @@ -3465,11 +3535,11 @@ App::patch('/v1/messaging/messages/sms/:messageId') ->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->inject('queueForEvents') ->inject('dbForProject') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $content, ?bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { + ->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $content, ?bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) { $message = $dbForProject->getDocument('messages', $messageId); if ($message->isEmpty()) { @@ -3521,7 +3591,7 @@ App::patch('/v1/messaging/messages/sms/:messageId') } if (\is_null($currentScheduledAt) && !\is_null($scheduledAt)) { - $schedule = $dbForConsole->createDocument('schedules', new Document([ + $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => System::getEnv('_APP_REGION', 'default'), 'resourceType' => 'message', 'resourceId' => $message->getId(), @@ -3536,7 +3606,7 @@ App::patch('/v1/messaging/messages/sms/:messageId') } if (!\is_null($currentScheduledAt)) { - $schedule = $dbForConsole->getDocument('schedules', $message->getAttribute('scheduleId')); + $schedule = $dbForPlatform->getDocument('schedules', $message->getAttribute('scheduleId')); $scheduledStatus = ($status ?? $message->getAttribute('status')) === MessageStatus::SCHEDULED; if ($schedule->isEmpty()) { @@ -3551,7 +3621,7 @@ App::patch('/v1/messaging/messages/sms/:messageId') $schedule->setAttribute('schedule', $scheduledAt); } - $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule); + $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule); } if (!\is_null($scheduledAt)) { @@ -3604,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', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'updatePush') @@ -3629,11 +3700,11 @@ App::patch('/v1/messaging/messages/push/:messageId') ->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->inject('queueForEvents') ->inject('dbForProject') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $title, ?string $body, ?array $data, ?string $action, ?string $image, ?string $icon, ?string $sound, ?string $color, ?string $tag, ?int $badge, ?bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) { + ->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $title, ?string $body, ?array $data, ?string $action, ?string $image, ?string $icon, ?string $sound, ?string $color, ?string $tag, ?int $badge, ?bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) { $message = $dbForProject->getDocument('messages', $messageId); if ($message->isEmpty()) { @@ -3685,7 +3756,7 @@ App::patch('/v1/messaging/messages/push/:messageId') } if (\is_null($currentScheduledAt) && !\is_null($scheduledAt)) { - $schedule = $dbForConsole->createDocument('schedules', new Document([ + $schedule = $dbForPlatform->createDocument('schedules', new Document([ 'region' => System::getEnv('_APP_REGION', 'default'), 'resourceType' => 'message', 'resourceId' => $message->getId(), @@ -3700,7 +3771,7 @@ App::patch('/v1/messaging/messages/push/:messageId') } if (!\is_null($currentScheduledAt)) { - $schedule = $dbForConsole->getDocument('schedules', $message->getAttribute('scheduleId')); + $schedule = $dbForPlatform->getDocument('schedules', $message->getAttribute('scheduleId')); $scheduledStatus = ($status ?? $message->getAttribute('status')) === MessageStatus::SCHEDULED; if ($schedule->isEmpty()) { @@ -3715,7 +3786,7 @@ App::patch('/v1/messaging/messages/push/:messageId') $schedule->setAttribute('schedule', $scheduledAt); } - $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule); + $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule); } if (!\is_null($scheduledAt)) { @@ -3842,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', RESOURCE_TYPE_MESSAGES) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') ->label('sdk.method', 'delete') @@ -3851,10 +3923,10 @@ App::delete('/v1/messaging/messages/:messageId') ->label('sdk.response.model', Response::MODEL_NONE) ->param('messageId', '', new UID(), 'Message ID.') ->inject('dbForProject') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('queueForEvents') ->inject('response') - ->action(function (string $messageId, Database $dbForProject, Database $dbForConsole, Event $queueForEvents, Response $response) { + ->action(function (string $messageId, Database $dbForProject, Database $dbForPlatform, Event $queueForEvents, Response $response) { $message = $dbForProject->getDocument('messages', $messageId); if ($message->isEmpty()) { @@ -3877,7 +3949,7 @@ App::delete('/v1/messaging/messages/:messageId') if (!empty($scheduleId)) { try { - $dbForConsole->deleteDocument('schedules', $scheduleId); + $dbForPlatform->deleteDocument('schedules', $scheduleId); } catch (Exception) { // Ignore } diff --git a/app/controllers/api/migrations.php b/app/controllers/api/migrations.php index bb89d4a26f..3abd1db016 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; @@ -103,24 +104,24 @@ App::post('/v1/migrations/firebase/oauth') ->param('projectId', '', new Text(65536), 'Project ID of the Firebase Project') ->inject('response') ->inject('dbForProject') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('project') ->inject('user') ->inject('queueForEvents') ->inject('queueForMigrations') ->inject('request') - ->action(function (array $resources, string $projectId, Response $response, Database $dbForProject, Database $dbForConsole, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations, Request $request) { + ->action(function (array $resources, string $projectId, Response $response, Database $dbForProject, Database $dbForPlatform, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations, Request $request) { $firebase = new OAuth2Firebase( System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', ''), System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', ''), $request->getProtocol() . '://' . $request->getHostname() . '/v1/migrations/firebase/redirect' ); - $identity = $dbForConsole->findOne('identities', [ + $identity = $dbForPlatform->findOne('identities', [ 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); } @@ -146,7 +147,7 @@ App::post('/v1/migrations/firebase/oauth') ->setAttribute('providerRefreshToken', $refreshToken) ->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$firebase->getAccessTokenExpiry(''))); - $dbForConsole->updateDocument('identities', $identity->getId(), $identity); + $dbForPlatform->updateDocument('identities', $identity->getId(), $identity); } if ($identity->getAttribute('secrets')) { @@ -157,7 +158,7 @@ App::post('/v1/migrations/firebase/oauth') $identity = $identity ->setAttribute('secrets', json_encode($serviceAccount)); - $dbForConsole->updateDocument('identities', $identity->getId(), $identity); + $dbForPlatform->updateDocument('identities', $identity->getId(), $identity); } $migration = $dbForProject->createDocument('migrations', new Document([ @@ -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); @@ -556,20 +563,20 @@ App::get('/v1/migrations/firebase/report/oauth') ->inject('response') ->inject('request') ->inject('user') - ->inject('dbForConsole') - ->action(function (array $resources, string $projectId, Response $response, Request $request, Document $user, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (array $resources, string $projectId, Response $response, Request $request, Document $user, Database $dbForPlatform) { $firebase = new OAuth2Firebase( System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', ''), System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', ''), $request->getProtocol() . '://' . $request->getHostname() . '/v1/migrations/firebase/redirect' ); - $identity = $dbForConsole->findOne('identities', [ + $identity = $dbForPlatform->findOne('identities', [ 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); } @@ -603,7 +610,7 @@ App::get('/v1/migrations/firebase/report/oauth') ->setAttribute('providerRefreshToken', $refreshToken) ->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$firebase->getAccessTokenExpiry(''))); - $dbForConsole->updateDocument('identities', $identity->getId(), $identity); + $dbForPlatform->updateDocument('identities', $identity->getId(), $identity); } // Get Service Account @@ -615,7 +622,7 @@ App::get('/v1/migrations/firebase/report/oauth') $identity = $identity ->setAttribute('secrets', json_encode($serviceAccount)); - $dbForConsole->updateDocument('identities', $identity->getId(), $identity); + $dbForPlatform->updateDocument('identities', $identity->getId(), $identity); } $firebase = new Firebase($serviceAccount); @@ -648,8 +655,8 @@ App::get('/v1/migrations/firebase/connect') ->inject('response') ->inject('request') ->inject('user') - ->inject('dbForConsole') - ->action(function (string $redirect, string $projectId, Response $response, Request $request, Document $user, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $redirect, string $projectId, Response $response, Request $request, Document $user, Database $dbForPlatform) { $state = \json_encode([ 'projectId' => $projectId, 'redirect' => $redirect, @@ -658,7 +665,7 @@ App::get('/v1/migrations/firebase/connect') $prefs = $user->getAttribute('prefs', []); $prefs['migrationState'] = $state; $user->setAttribute('prefs', $prefs); - $dbForConsole->updateDocument('users', $user->getId(), $user); + $dbForPlatform->updateDocument('users', $user->getId(), $user); $oauth2 = new OAuth2Firebase( System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', ''), @@ -683,12 +690,12 @@ App::get('/v1/migrations/firebase/redirect') ->inject('project') ->inject('request') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $code, Document $user, Document $project, Request $request, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $code, Document $user, Document $project, Request $request, Response $response, Database $dbForPlatform) { $state = $user['prefs']['migrationState'] ?? '{}'; $prefs['migrationState'] = ''; $user->setAttribute('prefs', $prefs); - $dbForConsole->updateDocument('users', $user->getId(), $user); + $dbForPlatform->updateDocument('users', $user->getId(), $user); if (empty($state)) { throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Installation requests from organisation members for the Appwrite Google App are currently unsupported.'); @@ -698,7 +705,7 @@ App::get('/v1/migrations/firebase/redirect') $redirect = $state['redirect'] ?? ''; $projectId = $state['projectId'] ?? ''; - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if (empty($redirect)) { $redirect = $request->getProtocol() . '://' . $request->getHostname() . '/console/project-$projectId/settings/migrations'; @@ -740,25 +747,25 @@ App::get('/v1/migrations/firebase/redirect') } // Makes sure this email is not already used in another identity - $identity = $dbForConsole->findOne('identities', [ + $identity = $dbForPlatform->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); } } - if ($identity !== false && !$identity->isEmpty()) { + if (!$identity->isEmpty()) { $identity = $identity ->setAttribute('providerAccessToken', $accessToken) ->setAttribute('providerRefreshToken', $refreshToken) ->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$accessTokenExpiry)); - $dbForConsole->updateDocument('identities', $identity->getId(), $identity); + $dbForPlatform->updateDocument('identities', $identity->getId(), $identity); } else { - $identity = $dbForConsole->createDocument('identities', new Document([ + $identity = $dbForPlatform->createDocument('identities', new Document([ '$id' => ID::unique(), '$permissions' => [ Permission::read(Role::any()), @@ -799,21 +806,21 @@ App::get('/v1/migrations/firebase/projects') ->inject('user') ->inject('response') ->inject('project') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('request') - ->action(function (Document $user, Response $response, Document $project, Database $dbForConsole, Request $request) { + ->action(function (Document $user, Response $response, Document $project, Database $dbForPlatform, Request $request) { $firebase = new OAuth2Firebase( System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', ''), System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', ''), $request->getProtocol() . '://' . $request->getHostname() . '/v1/migrations/firebase/redirect' ); - $identity = $dbForConsole->findOne('identities', [ + $identity = $dbForPlatform->findOne('identities', [ 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); } @@ -852,7 +859,7 @@ App::get('/v1/migrations/firebase/projects') ->setAttribute('providerRefreshToken', $refreshToken) ->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$firebase->getAccessTokenExpiry(''))); - $dbForConsole->updateDocument('identities', $identity->getId(), $identity); + $dbForPlatform->updateDocument('identities', $identity->getId(), $identity); } $projects = $firebase->getProjects($accessToken); @@ -886,18 +893,18 @@ App::get('/v1/migrations/firebase/deauthorize') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->inject('user') ->inject('response') - ->inject('dbForConsole') - ->action(function (Document $user, Response $response, Database $dbForConsole) { - $identity = $dbForConsole->findOne('identities', [ + ->inject('dbForPlatform') + ->action(function (Document $user, Response $response, Database $dbForPlatform) { + $identity = $dbForPlatform->findOne('identities', [ Query::equal('provider', ['firebase']), 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 } - $dbForConsole->deleteDocument('identities', $identity->getId()); + $dbForPlatform->deleteDocument('identities', $identity->getId()); $response->noContent(); }); diff --git a/app/controllers/api/project.php b/app/controllers/api/project.php index 6053326308..45c8f2c32b 100644 --- a/app/controllers/api/project.php +++ b/app/controllers/api/project.php @@ -325,8 +325,8 @@ App::post('/v1/project/variables') ->inject('project') ->inject('response') ->inject('dbForProject') - ->inject('dbForConsole') - ->action(function (string $key, string $value, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $key, string $value, Document $project, Response $response, Database $dbForProject, Database $dbForPlatform) { $variableId = ID::unique(); $variable = new Document([ @@ -429,8 +429,8 @@ App::put('/v1/project/variables/:variableId') ->inject('project') ->inject('response') ->inject('dbForProject') - ->inject('dbForConsole') - ->action(function (string $variableId, string $key, ?string $value, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $variableId, string $key, ?string $value, Document $project, Response $response, Database $dbForProject, Database $dbForPlatform) { $variable = $dbForProject->getDocument('variables', $variableId); if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceType') !== 'project') { throw new Exception(Exception::VARIABLE_NOT_FOUND); diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 934793410b..388c9e22ea 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; @@ -82,13 +83,13 @@ App::post('/v1/projects') ->param('legalTaxId', '', new Text(256), 'Project legal Tax ID. Max length: 256 chars.', true) ->inject('request') ->inject('response') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('cache') ->inject('pools') ->inject('hooks') - ->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Request $request, Response $response, Database $dbForConsole, Cache $cache, Group $pools, Hooks $hooks) { + ->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Request $request, Response $response, Database $dbForPlatform, Cache $cache, Group $pools, Hooks $hooks) { - $team = $dbForConsole->getDocument('teams', $teamId); + $team = $dbForPlatform->getDocument('teams', $teamId); if ($team->isEmpty()) { throw new Exception(Exception::TEAM_NOT_FOUND); @@ -110,6 +111,9 @@ App::post('/v1/projects') 'personalDataCheck' => false, 'mockNumbers' => [], 'sessionAlerts' => false, + 'membershipsUserName' => false, + 'membershipsUserEmail' => false, + 'membershipsMfa' => false, ]; foreach ($auth as $method) { @@ -118,6 +122,10 @@ App::post('/v1/projects') $projectId = ($projectId == 'unique()') ? ID::unique() : $projectId; + if ($projectId === 'console') { + throw new Exception(Exception::PROJECT_RESERVED_PROJECT, "'console' is a reserved project."); + } + $databases = Config::getParam('pools-database', []); $databaseOverride = System::getEnv('_APP_DATABASE_OVERRIDE'); @@ -128,16 +136,14 @@ App::post('/v1/projects') $dsn = $databases[array_rand($databases)]; } - if ($projectId === 'console') { - throw new Exception(Exception::PROJECT_RESERVED_PROJECT, "'console' is a reserved project."); - } - // TODO: Temporary until all projects are using shared tables. - if ($dsn === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); + + if (\in_array($dsn, $sharedTables)) { $schema = 'appwrite'; $database = 'appwrite'; $namespace = System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', ''); - $dsn = $schema . '://' . System::getEnv('_APP_DATABASE_SHARED_TABLES', '') . '?database=' . $database; + $dsn = $schema . '://' . $dsn . '?database=' . $database; if (!empty($namespace)) { $dsn .= '&namespace=' . $namespace; @@ -145,7 +151,7 @@ App::post('/v1/projects') } try { - $project = $dbForConsole->createDocument('projects', new Document([ + $project = $dbForPlatform->createDocument('projects', new Document([ '$id' => $projectId, '$permissions' => [ Permission::read(Role::team(ID::custom($teamId))), @@ -191,47 +197,92 @@ App::post('/v1/projects') $adapter = $pools->get($dsn->getHost())->pop()->getResource(); $dbForProject = new Database($adapter, $cache); + $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); + $sharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES_V1', '')); - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { - $dbForProject - ->setSharedTables(true) - ->setTenant($project->getInternalId()) - ->setNamespace($dsn->getParam('namespace')); - } else { - $dbForProject - ->setSharedTables(false) - ->setTenant(null) - ->setNamespace('_' . $project->getInternalId()); - } + $projectTables = !\in_array($dsn->getHost(), $sharedTables); + $sharedTablesV1 = \in_array($dsn->getHost(), $sharedTablesV1); + $sharedTablesV2 = !$projectTables && !$sharedTablesV1; + $sharedTables = $sharedTablesV1 || $sharedTablesV2; - $dbForProject->create(); - - $audit = new Audit($dbForProject); - $audit->setup(); - - $abuse = new TimeLimit('', 0, 1, $dbForProject); - $abuse->setup(); - - /** @var array $collections */ - $collections = Config::getParam('collections', [])['projects'] ?? []; - - foreach ($collections as $key => $collection) { - if (($collection['$collection'] ?? '') !== Database::METADATA) { - continue; + if (!$sharedTablesV2) { + if ($sharedTables) { + $dbForProject + ->setSharedTables(true) + ->setTenant($sharedTablesV1 ? $project->getInternalId() : null) + ->setNamespace($dsn->getParam('namespace')); + } else { + $dbForProject + ->setSharedTables(false) + ->setTenant(null) + ->setNamespace('_' . $project->getInternalId()); } - $attributes = \array_map(function (array $attribute) { - return new Document($attribute); - }, $collection['attributes']); - - $indexes = \array_map(function (array $index) { - return new Document($index); - }, $collection['indexes']); + $create = true; try { - $dbForProject->createCollection($key, $attributes, $indexes); + $dbForProject->create(); } catch (Duplicate) { - // Collection already exists + $create = false; + } + + if ($create || $projectTables) { + $audit = new Audit($dbForProject); + $audit->setup(); + + $abuse = new TimeLimit('', 0, 1, $dbForProject); + $abuse->setup(); + } + + if (!$create && $sharedTablesV1) { + $attributes = \array_map(fn ($attribute) => new Document($attribute), Audit::ATTRIBUTES); + $indexes = \array_map(fn (array $index) => new Document($index), Audit::INDEXES); + $dbForProject->createDocument(Database::METADATA, new Document([ + '$id' => ID::custom('audit'), + '$permissions' => [Permission::create(Role::any())], + 'name' => 'audit', + 'attributes' => $attributes, + 'indexes' => $indexes, + 'documentSecurity' => true + ])); + + $attributes = \array_map(fn ($attribute) => new Document($attribute), TimeLimit::ATTRIBUTES); + $indexes = \array_map(fn (array $index) => new Document($index), TimeLimit::INDEXES); + $dbForProject->createDocument(Database::METADATA, new Document([ + '$id' => ID::custom('abuse'), + '$permissions' => [Permission::create(Role::any())], + 'name' => 'abuse', + 'attributes' => $attributes, + 'indexes' => $indexes, + 'documentSecurity' => true + ])); + } + + if ($create || $sharedTablesV1) { + /** @var array $collections */ + $collections = Config::getParam('collections', [])['projects'] ?? []; + + foreach ($collections as $key => $collection) { + if (($collection['$collection'] ?? '') !== Database::METADATA) { + continue; + } + + $attributes = \array_map(fn ($attribute) => new Document($attribute), $collection['attributes']); + $indexes = \array_map(fn (array $index) => new Document($index), $collection['indexes']); + + try { + $dbForProject->createCollection($key, $attributes, $indexes); + } catch (Duplicate) { + $dbForProject->createDocument(Database::METADATA, new Document([ + '$id' => ID::custom($key), + '$permissions' => [Permission::create(Role::any())], + 'name' => $key, + 'attributes' => $attributes, + 'indexes' => $indexes, + 'documentSecurity' => true + ])); + } + } } } @@ -257,8 +308,8 @@ App::get('/v1/projects') ->param('queries', [], new Projects(), '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(', ', Projects::ALLOWED_ATTRIBUTES), true) ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->inject('response') - ->inject('dbForConsole') - ->action(function (array $queries, string $search, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (array $queries, string $search, Response $response, Database $dbForPlatform) { try { $queries = Query::parseQueries($queries); @@ -279,8 +330,14 @@ 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); + $cursorDocument = $dbForPlatform->getDocument('projects', $projectId); if ($cursorDocument->isEmpty()) { throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Project '{$projectId}' for the 'cursor' value not found."); @@ -292,8 +349,8 @@ App::get('/v1/projects') $filterQueries = Query::groupByType($queries)['filters']; $response->dynamic(new Document([ - 'projects' => $dbForConsole->find('projects', $queries), - 'total' => $dbForConsole->count('projects', $filterQueries, APP_LIMIT_COUNT), + 'projects' => $dbForPlatform->find('projects', $queries), + 'total' => $dbForPlatform->count('projects', $filterQueries, APP_LIMIT_COUNT), ]), Response::MODEL_PROJECT_LIST); }); @@ -309,10 +366,10 @@ App::get('/v1/projects/:projectId') ->label('sdk.response.model', Response::MODEL_PROJECT) ->param('projectId', '', new UID(), 'Project unique ID.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -343,16 +400,16 @@ App::patch('/v1/projects/:projectId') ->param('legalAddress', '', new Text(256), 'Project legal address. Max length: 256 chars.', true) ->param('legalTaxId', '', new Text(256), 'Project legal tax ID. Max length: 256 chars.', true) ->inject('response') - ->inject('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) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $name, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } - $project = $dbForConsole->updateDocument('projects', $project->getId(), $project + $project = $dbForPlatform->updateDocument('projects', $project->getId(), $project ->setAttribute('name', $name) ->setAttribute('description', $description) ->setAttribute('logo', $logo) @@ -381,11 +438,11 @@ App::patch('/v1/projects/:projectId/team') ->param('projectId', '', new UID(), 'Project unique ID.') ->param('teamId', '', new UID(), 'Team ID of the team to transfer project to.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, string $teamId, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $teamId, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); - $team = $dbForConsole->getDocument('teams', $teamId); + $project = $dbForPlatform->getDocument('projects', $projectId); + $team = $dbForPlatform->getDocument('teams', $teamId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -407,30 +464,30 @@ App::patch('/v1/projects/:projectId/team') ->setAttribute('teamId', $teamId) ->setAttribute('teamInternalId', $team->getInternalId()) ->setAttribute('$permissions', $permissions); - $project = $dbForConsole->updateDocument('projects', $project->getId(), $project); + $project = $dbForPlatform->updateDocument('projects', $project->getId(), $project); - $installations = $dbForConsole->find('installations', [ + $installations = $dbForPlatform->find('installations', [ Query::equal('projectInternalId', [$project->getInternalId()]), ]); foreach ($installations as $installation) { $installation->getAttribute('$permissions', $permissions); - $dbForConsole->updateDocument('installations', $installation->getId(), $installation); + $dbForPlatform->updateDocument('installations', $installation->getId(), $installation); } - $repositories = $dbForConsole->find('repositories', [ + $repositories = $dbForPlatform->find('repositories', [ Query::equal('projectInternalId', [$project->getInternalId()]), ]); foreach ($repositories as $repository) { $repository->getAttribute('$permissions', $permissions); - $dbForConsole->updateDocument('repositories', $repository->getId(), $repository); + $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository); } - $vcsComments = $dbForConsole->find('vcsComments', [ + $vcsComments = $dbForPlatform->find('vcsComments', [ Query::equal('projectInternalId', [$project->getInternalId()]), ]); foreach ($vcsComments as $vcsComment) { $vcsComment->getAttribute('$permissions', $permissions); - $dbForConsole->updateDocument('vcsComments', $vcsComment->getId(), $vcsComment); + $dbForPlatform->updateDocument('vcsComments', $vcsComment->getId(), $vcsComment); } $response->dynamic($project, Response::MODEL_PROJECT); @@ -450,10 +507,10 @@ App::patch('/v1/projects/:projectId/service') ->param('service', '', new WhiteList(array_keys(array_filter(Config::getParam('services'), fn ($element) => $element['optional'])), true), 'Service name.') ->param('status', null, new Boolean(), 'Service status.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, string $service, bool $status, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $service, bool $status, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -462,7 +519,7 @@ App::patch('/v1/projects/:projectId/service') $services = $project->getAttribute('services', []); $services[$service] = $status; - $project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('services', $services)); + $project = $dbForPlatform->updateDocument('projects', $project->getId(), $project->setAttribute('services', $services)); $response->dynamic($project, Response::MODEL_PROJECT); }); @@ -480,10 +537,10 @@ App::patch('/v1/projects/:projectId/service/all') ->param('projectId', '', new UID(), 'Project unique ID.') ->param('status', null, new Boolean(), 'Service status.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, bool $status, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, bool $status, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -496,7 +553,7 @@ App::patch('/v1/projects/:projectId/service/all') $services[$service] = $status; } - $project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('services', $services)); + $project = $dbForPlatform->updateDocument('projects', $project->getId(), $project->setAttribute('services', $services)); $response->dynamic($project, Response::MODEL_PROJECT); }); @@ -515,10 +572,10 @@ App::patch('/v1/projects/:projectId/api') ->param('api', '', new WhiteList(array_keys(Config::getParam('apis')), true), 'API name.') ->param('status', null, new Boolean(), 'API status.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, string $api, bool $status, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $api, bool $status, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -527,7 +584,7 @@ App::patch('/v1/projects/:projectId/api') $apis = $project->getAttribute('apis', []); $apis[$api] = $status; - $project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('apis', $apis)); + $project = $dbForPlatform->updateDocument('projects', $project->getId(), $project->setAttribute('apis', $apis)); $response->dynamic($project, Response::MODEL_PROJECT); }); @@ -545,10 +602,10 @@ App::patch('/v1/projects/:projectId/api/all') ->param('projectId', '', new UID(), 'Project unique ID.') ->param('status', null, new Boolean(), 'API status.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, bool $status, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, bool $status, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -561,7 +618,7 @@ App::patch('/v1/projects/:projectId/api/all') $apis[$api] = $status; } - $project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('apis', $apis)); + $project = $dbForPlatform->updateDocument('projects', $project->getId(), $project->setAttribute('apis', $apis)); $response->dynamic($project, Response::MODEL_PROJECT); }); @@ -582,10 +639,10 @@ App::patch('/v1/projects/:projectId/oauth2') ->param('secret', null, new text(512), 'Provider secret key. Max length: 512 chars.', true) ->param('enabled', null, new Boolean(), 'Provider status. Set to \'false\' to disable new session creation.', true) ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, string $provider, ?string $appId, ?string $secret, ?bool $enabled, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $provider, ?string $appId, ?string $secret, ?bool $enabled, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -605,7 +662,7 @@ App::patch('/v1/projects/:projectId/oauth2') $providers[$provider . 'Enabled'] = $enabled; } - $project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('oAuthProviders', $providers)); + $project = $dbForPlatform->updateDocument('projects', $project->getId(), $project->setAttribute('oAuthProviders', $providers)); $response->dynamic($project, Response::MODEL_PROJECT); }); @@ -623,10 +680,10 @@ App::patch('/v1/projects/:projectId/auth/session-alerts') ->param('projectId', '', new UID(), 'Project unique ID.') ->param('alerts', false, new Boolean(true), 'Set to true to enable session emails.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, bool $alerts, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, bool $alerts, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -635,7 +692,42 @@ App::patch('/v1/projects/:projectId/auth/session-alerts') $auths = $project->getAttribute('auths', []); $auths['sessionAlerts'] = $alerts; - $dbForConsole->updateDocument('projects', $project->getId(), $project + $dbForPlatform->updateDocument('projects', $project->getId(), $project + ->setAttribute('auths', $auths)); + + $response->dynamic($project, Response::MODEL_PROJECT); + }); + +App::patch('/v1/projects/:projectId/auth/memberships-privacy') + ->desc('Update project memberships privacy attributes') + ->groups(['api', 'projects']) + ->label('scope', 'projects.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'projects') + ->label('sdk.method', 'updateMembershipsPrivacy') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROJECT) + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('userName', true, new Boolean(true), 'Set to true to show userName to members of a team.') + ->param('userEmail', true, new Boolean(true), 'Set to true to show email to members of a team.') + ->param('mfa', true, new Boolean(true), 'Set to true to show mfa to members of a team.') + ->inject('response') + ->inject('dbForPlatform') + ->action(function (string $projectId, bool $userName, bool $userEmail, bool $mfa, Response $response, Database $dbForPlatform) { + $project = $dbForPlatform->getDocument('projects', $projectId); + + if ($project->isEmpty()) { + throw new Exception(Exception::PROJECT_NOT_FOUND); + } + + $auths = $project->getAttribute('auths', []); + + $auths['membershipsUserName'] = $userName; + $auths['membershipsUserEmail'] = $userEmail; + $auths['membershipsMfa'] = $mfa; + + $dbForPlatform->updateDocument('projects', $project->getId(), $project ->setAttribute('auths', $auths)); $response->dynamic($project, Response::MODEL_PROJECT); @@ -654,10 +746,10 @@ App::patch('/v1/projects/:projectId/auth/limit') ->param('projectId', '', new UID(), 'Project unique ID.') ->param('limit', false, new Range(0, APP_LIMIT_USERS), 'Set the max number of users allowed in this project. Use 0 for unlimited.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, int $limit, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -666,7 +758,7 @@ App::patch('/v1/projects/:projectId/auth/limit') $auths = $project->getAttribute('auths', []); $auths['limit'] = $limit; - $dbForConsole->updateDocument('projects', $project->getId(), $project + $dbForPlatform->updateDocument('projects', $project->getId(), $project ->setAttribute('auths', $auths)); $response->dynamic($project, Response::MODEL_PROJECT); @@ -685,10 +777,10 @@ App::patch('/v1/projects/:projectId/auth/duration') ->param('projectId', '', new UID(), 'Project unique ID.') ->param('duration', 31536000, new Range(0, 31536000), 'Project session length in seconds. Max length: 31536000 seconds.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, int $duration, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, int $duration, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -697,7 +789,7 @@ App::patch('/v1/projects/:projectId/auth/duration') $auths = $project->getAttribute('auths', []); $auths['duration'] = $duration; - $dbForConsole->updateDocument('projects', $project->getId(), $project + $dbForPlatform->updateDocument('projects', $project->getId(), $project ->setAttribute('auths', $auths)); $response->dynamic($project, Response::MODEL_PROJECT); @@ -717,10 +809,10 @@ App::patch('/v1/projects/:projectId/auth/:method') ->param('method', '', new WhiteList(\array_keys(Config::getParam('auth')), true), 'Auth Method. Possible values: ' . implode(',', \array_keys(Config::getParam('auth'))), false) ->param('status', false, new Boolean(true), 'Set the status of this auth method.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, string $method, bool $status, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $method, bool $status, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); $auth = Config::getParam('auth')[$method] ?? []; $authKey = $auth['key'] ?? ''; $status = ($status === '1' || $status === 'true' || $status === 1 || $status === true); @@ -732,7 +824,7 @@ App::patch('/v1/projects/:projectId/auth/:method') $auths = $project->getAttribute('auths', []); $auths[$authKey] = $status; - $project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('auths', $auths)); + $project = $dbForPlatform->updateDocument('projects', $project->getId(), $project->setAttribute('auths', $auths)); $response->dynamic($project, Response::MODEL_PROJECT); }); @@ -750,10 +842,10 @@ App::patch('/v1/projects/:projectId/auth/password-history') ->param('projectId', '', new UID(), 'Project unique ID.') ->param('limit', 0, new Range(0, APP_LIMIT_USER_PASSWORD_HISTORY), 'Set the max number of passwords to store in user history. User can\'t choose a new password that is already stored in the password history list. Max number of passwords allowed in history is' . APP_LIMIT_USER_PASSWORD_HISTORY . '. Default value is 0') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, int $limit, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -762,7 +854,7 @@ App::patch('/v1/projects/:projectId/auth/password-history') $auths = $project->getAttribute('auths', []); $auths['passwordHistory'] = $limit; - $dbForConsole->updateDocument('projects', $project->getId(), $project + $dbForPlatform->updateDocument('projects', $project->getId(), $project ->setAttribute('auths', $auths)); $response->dynamic($project, Response::MODEL_PROJECT); @@ -781,10 +873,10 @@ App::patch('/v1/projects/:projectId/auth/password-dictionary') ->param('projectId', '', new UID(), 'Project unique ID.') ->param('enabled', false, new Boolean(false), 'Set whether or not to enable checking user\'s password against most commonly used passwords. Default is false.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, bool $enabled, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, bool $enabled, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -793,7 +885,7 @@ App::patch('/v1/projects/:projectId/auth/password-dictionary') $auths = $project->getAttribute('auths', []); $auths['passwordDictionary'] = $enabled; - $dbForConsole->updateDocument('projects', $project->getId(), $project + $dbForPlatform->updateDocument('projects', $project->getId(), $project ->setAttribute('auths', $auths)); $response->dynamic($project, Response::MODEL_PROJECT); @@ -812,10 +904,10 @@ App::patch('/v1/projects/:projectId/auth/personal-data') ->param('projectId', '', new UID(), 'Project unique ID.') ->param('enabled', false, new Boolean(false), 'Set whether or not to check a password for similarity with personal data. Default is false.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, bool $enabled, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, bool $enabled, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -824,7 +916,7 @@ App::patch('/v1/projects/:projectId/auth/personal-data') $auths = $project->getAttribute('auths', []); $auths['personalDataCheck'] = $enabled; - $dbForConsole->updateDocument('projects', $project->getId(), $project + $dbForPlatform->updateDocument('projects', $project->getId(), $project ->setAttribute('auths', $auths)); $response->dynamic($project, Response::MODEL_PROJECT); @@ -843,10 +935,10 @@ App::patch('/v1/projects/:projectId/auth/max-sessions') ->param('projectId', '', new UID(), 'Project unique ID.') ->param('limit', false, new Range(1, APP_LIMIT_USER_SESSIONS_MAX), 'Set the max number of users allowed in this project. Value allowed is between 1-' . APP_LIMIT_USER_SESSIONS_MAX . '. Default is ' . APP_LIMIT_USER_SESSIONS_DEFAULT) ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, int $limit, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -855,7 +947,7 @@ App::patch('/v1/projects/:projectId/auth/max-sessions') $auths = $project->getAttribute('auths', []); $auths['maxSessions'] = $limit; - $dbForConsole->updateDocument('projects', $project->getId(), $project + $dbForPlatform->updateDocument('projects', $project->getId(), $project ->setAttribute('auths', $auths)); $response->dynamic($project, Response::MODEL_PROJECT); @@ -874,8 +966,8 @@ App::patch('/v1/projects/:projectId/auth/mock-numbers') ->param('projectId', '', new UID(), 'Project unique ID.') ->param('numbers', '', new ArrayList(new MockNumber(), 10), 'An array of mock numbers and their corresponding verification codes (OTPs). Each number should be a valid E.164 formatted phone number. Maximum of 10 numbers are allowed.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, array $numbers, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, array $numbers, Response $response, Database $dbForPlatform) { $uniqueNumbers = []; foreach ($numbers as $number) { @@ -885,7 +977,7 @@ App::patch('/v1/projects/:projectId/auth/mock-numbers') $uniqueNumbers[$number['phone']] = $number['otp']; } - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -895,7 +987,7 @@ App::patch('/v1/projects/:projectId/auth/mock-numbers') $auths['mockNumbers'] = $numbers; - $project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('auths', $auths)); + $project = $dbForPlatform->updateDocument('projects', $project->getId(), $project->setAttribute('auths', $auths)); $response->dynamic($project, Response::MODEL_PROJECT); }); @@ -913,10 +1005,10 @@ App::delete('/v1/projects/:projectId') ->param('projectId', '', new UID(), 'Project unique ID.') ->inject('response') ->inject('user') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('queueForDeletes') - ->action(function (string $projectId, Response $response, Document $user, Database $dbForConsole, Delete $queueForDeletes) { - $project = $dbForConsole->getDocument('projects', $projectId); + ->action(function (string $projectId, Response $response, Document $user, Database $dbForPlatform, Delete $queueForDeletes) { + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -927,7 +1019,7 @@ App::delete('/v1/projects/:projectId') ->setType(DELETE_TYPE_DOCUMENT) ->setDocument($project); - if (!$dbForConsole->deleteDocument('projects', $projectId)) { + if (!$dbForPlatform->deleteDocument('projects', $projectId)) { throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove project from DB'); } @@ -955,10 +1047,10 @@ App::post('/v1/projects/:projectId/webhooks') ->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true) ->param('httpPass', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true) ->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) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $name, bool $enabled, array $events, string $url, bool $security, string $httpUser, string $httpPass, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -985,9 +1077,9 @@ App::post('/v1/projects/:projectId/webhooks') 'enabled' => $enabled, ]); - $webhook = $dbForConsole->createDocument('webhooks', $webhook); + $webhook = $dbForPlatform->createDocument('webhooks', $webhook); - $dbForConsole->purgeCachedDocument('projects', $project->getId()); + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -1006,16 +1098,16 @@ App::get('/v1/projects/:projectId/webhooks') ->label('sdk.response.model', Response::MODEL_WEBHOOK_LIST) ->param('projectId', '', new UID(), 'Project unique ID.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } - $webhooks = $dbForConsole->find('webhooks', [ + $webhooks = $dbForPlatform->find('webhooks', [ Query::equal('projectInternalId', [$project->getInternalId()]), Query::limit(5000), ]); @@ -1039,21 +1131,21 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId') ->param('projectId', '', new UID(), 'Project unique ID.') ->param('webhookId', '', new UID(), 'Webhook unique ID.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } - $webhook = $dbForConsole->findOne('webhooks', [ + $webhook = $dbForPlatform->findOne('webhooks', [ Query::equal('$id', [$webhookId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($webhook === false || $webhook->isEmpty()) { + if ($webhook->isEmpty()) { throw new Exception(Exception::WEBHOOK_NOT_FOUND); } @@ -1080,10 +1172,10 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') ->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true) ->param('httpPass', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true) ->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) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $webhookId, string $name, bool $enabled, array $events, string $url, bool $security, string $httpUser, string $httpPass, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -1091,12 +1183,12 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); - $webhook = $dbForConsole->findOne('webhooks', [ + $webhook = $dbForPlatform->findOne('webhooks', [ Query::equal('$id', [$webhookId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($webhook === false || $webhook->isEmpty()) { + if ($webhook->isEmpty()) { throw new Exception(Exception::WEBHOOK_NOT_FOUND); } @@ -1113,8 +1205,8 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') $webhook->setAttribute('attempts', 0); } - $dbForConsole->updateDocument('webhooks', $webhook->getId(), $webhook); - $dbForConsole->purgeCachedDocument('projects', $project->getId()); + $dbForPlatform->updateDocument('webhooks', $webhook->getId(), $webhook); + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); $response->dynamic($webhook, Response::MODEL_WEBHOOK); }); @@ -1132,28 +1224,28 @@ App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature') ->param('projectId', '', new UID(), 'Project unique ID.') ->param('webhookId', '', new UID(), 'Webhook unique ID.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } - $webhook = $dbForConsole->findOne('webhooks', [ + $webhook = $dbForPlatform->findOne('webhooks', [ Query::equal('$id', [$webhookId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($webhook === false || $webhook->isEmpty()) { + if ($webhook->isEmpty()) { throw new Exception(Exception::WEBHOOK_NOT_FOUND); } $webhook->setAttribute('signatureKey', \bin2hex(\random_bytes(64))); - $dbForConsole->updateDocument('webhooks', $webhook->getId(), $webhook); - $dbForConsole->purgeCachedDocument('projects', $project->getId()); + $dbForPlatform->updateDocument('webhooks', $webhook->getId(), $webhook); + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); $response->dynamic($webhook, Response::MODEL_WEBHOOK); }); @@ -1170,27 +1262,27 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId') ->param('projectId', '', new UID(), 'Project unique ID.') ->param('webhookId', '', new UID(), 'Webhook unique ID.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } - $webhook = $dbForConsole->findOne('webhooks', [ + $webhook = $dbForPlatform->findOne('webhooks', [ Query::equal('$id', [$webhookId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($webhook === false || $webhook->isEmpty()) { + if ($webhook->isEmpty()) { throw new Exception(Exception::WEBHOOK_NOT_FOUND); } - $dbForConsole->deleteDocument('webhooks', $webhook->getId()); + $dbForPlatform->deleteDocument('webhooks', $webhook->getId()); - $dbForConsole->purgeCachedDocument('projects', $project->getId()); + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); $response->noContent(); }); @@ -1212,10 +1304,10 @@ App::post('/v1/projects/:projectId/keys') ->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.') ->param('expire', null, new DatetimeValidator(), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true) ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -1238,9 +1330,9 @@ App::post('/v1/projects/:projectId/keys') 'secret' => API_KEY_STANDARD . '_' . \bin2hex(\random_bytes(128)), ]); - $key = $dbForConsole->createDocument('keys', $key); + $key = $dbForPlatform->createDocument('keys', $key); - $dbForConsole->purgeCachedDocument('projects', $project->getId()); + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -1259,16 +1351,16 @@ App::get('/v1/projects/:projectId/keys') ->label('sdk.response.model', Response::MODEL_KEY_LIST) ->param('projectId', '', new UID(), 'Project unique ID.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } - $keys = $dbForConsole->find('keys', [ + $keys = $dbForPlatform->find('keys', [ Query::equal('projectInternalId', [$project->getInternalId()]), Query::limit(5000), ]); @@ -1292,21 +1384,21 @@ App::get('/v1/projects/:projectId/keys/:keyId') ->param('projectId', '', new UID(), 'Project unique ID.') ->param('keyId', '', new UID(), 'Key unique ID.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $keyId, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } - $key = $dbForConsole->findOne('keys', [ + $key = $dbForPlatform->findOne('keys', [ Query::equal('$id', [$keyId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($key === false || $key->isEmpty()) { + if ($key->isEmpty()) { throw new Exception(Exception::KEY_NOT_FOUND); } @@ -1329,21 +1421,21 @@ App::put('/v1/projects/:projectId/keys/:keyId') ->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.') ->param('expire', null, new DatetimeValidator(), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true) ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, string $keyId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $keyId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } - $key = $dbForConsole->findOne('keys', [ + $key = $dbForPlatform->findOne('keys', [ Query::equal('$id', [$keyId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($key === false || $key->isEmpty()) { + if ($key->isEmpty()) { throw new Exception(Exception::KEY_NOT_FOUND); } @@ -1352,9 +1444,9 @@ App::put('/v1/projects/:projectId/keys/:keyId') ->setAttribute('scopes', $scopes) ->setAttribute('expire', $expire); - $dbForConsole->updateDocument('keys', $key->getId(), $key); + $dbForPlatform->updateDocument('keys', $key->getId(), $key); - $dbForConsole->purgeCachedDocument('projects', $project->getId()); + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); $response->dynamic($key, Response::MODEL_KEY); }); @@ -1371,27 +1463,27 @@ App::delete('/v1/projects/:projectId/keys/:keyId') ->param('projectId', '', new UID(), 'Project unique ID.') ->param('keyId', '', new UID(), 'Key unique ID.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $keyId, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } - $key = $dbForConsole->findOne('keys', [ + $key = $dbForPlatform->findOne('keys', [ Query::equal('$id', [$keyId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($key === false || $key->isEmpty()) { + if ($key->isEmpty()) { throw new Exception(Exception::KEY_NOT_FOUND); } - $dbForConsole->deleteDocument('keys', $key->getId()); + $dbForPlatform->deleteDocument('keys', $key->getId()); - $dbForConsole->purgeCachedDocument('projects', $project->getId()); + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); $response->noContent(); }); @@ -1412,10 +1504,10 @@ App::post('/v1/projects/:projectId/jwts') ->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for JWT key. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.') ->param('duration', 900, new Range(0, 3600), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.', true) ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, array $scopes, int $duration, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, array $scopes, int $duration, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -1451,9 +1543,9 @@ App::post('/v1/projects/:projectId/platforms') ->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true) ->param('hostname', '', new Hostname(), 'Platform client hostname. Max length: 256 chars.', true) ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, string $type, string $name, string $key, string $store, string $hostname, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); + ->inject('dbForPlatform') + ->action(function (string $projectId, string $type, string $name, string $key, string $store, string $hostname, Response $response, Database $dbForPlatform) { + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -1475,9 +1567,9 @@ App::post('/v1/projects/:projectId/platforms') 'hostname' => $hostname ]); - $platform = $dbForConsole->createDocument('platforms', $platform); + $platform = $dbForPlatform->createDocument('platforms', $platform); - $dbForConsole->purgeCachedDocument('projects', $project->getId()); + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -1496,16 +1588,16 @@ App::get('/v1/projects/:projectId/platforms') ->label('sdk.response.model', Response::MODEL_PLATFORM_LIST) ->param('projectId', '', new UID(), 'Project unique ID.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } - $platforms = $dbForConsole->find('platforms', [ + $platforms = $dbForPlatform->find('platforms', [ Query::equal('projectInternalId', [$project->getInternalId()]), Query::limit(5000), ]); @@ -1529,21 +1621,21 @@ App::get('/v1/projects/:projectId/platforms/:platformId') ->param('projectId', '', new UID(), 'Project unique ID.') ->param('platformId', '', new UID(), 'Platform unique ID.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $platformId, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } - $platform = $dbForConsole->findOne('platforms', [ + $platform = $dbForPlatform->findOne('platforms', [ Query::equal('$id', [$platformId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($platform === false || $platform->isEmpty()) { + if ($platform->isEmpty()) { throw new Exception(Exception::PLATFORM_NOT_FOUND); } @@ -1567,20 +1659,20 @@ App::put('/v1/projects/:projectId/platforms/:platformId') ->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true) ->param('hostname', '', new Hostname(), 'Platform client URL. Max length: 256 chars.', true) ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, string $platformId, string $name, string $key, string $store, string $hostname, Response $response, Database $dbForConsole) { - $project = $dbForConsole->getDocument('projects', $projectId); + ->inject('dbForPlatform') + ->action(function (string $projectId, string $platformId, string $name, string $key, string $store, string $hostname, Response $response, Database $dbForPlatform) { + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } - $platform = $dbForConsole->findOne('platforms', [ + $platform = $dbForPlatform->findOne('platforms', [ Query::equal('$id', [$platformId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($platform === false || $platform->isEmpty()) { + if ($platform->isEmpty()) { throw new Exception(Exception::PLATFORM_NOT_FOUND); } @@ -1590,9 +1682,9 @@ App::put('/v1/projects/:projectId/platforms/:platformId') ->setAttribute('store', $store) ->setAttribute('hostname', $hostname); - $dbForConsole->updateDocument('platforms', $platform->getId(), $platform); + $dbForPlatform->updateDocument('platforms', $platform->getId(), $platform); - $dbForConsole->purgeCachedDocument('projects', $project->getId()); + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); $response->dynamic($platform, Response::MODEL_PLATFORM); }); @@ -1610,27 +1702,27 @@ App::delete('/v1/projects/:projectId/platforms/:platformId') ->param('projectId', '', new UID(), 'Project unique ID.') ->param('platformId', '', new UID(), 'Platform unique ID.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $platformId, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } - $platform = $dbForConsole->findOne('platforms', [ + $platform = $dbForPlatform->findOne('platforms', [ Query::equal('$id', [$platformId]), Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($platform === false || $platform->isEmpty()) { + if ($platform->isEmpty()) { throw new Exception(Exception::PLATFORM_NOT_FOUND); } - $dbForConsole->deleteDocument('platforms', $platformId); + $dbForPlatform->deleteDocument('platforms', $platformId); - $dbForConsole->purgeCachedDocument('projects', $project->getId()); + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); $response->noContent(); }); @@ -1658,10 +1750,10 @@ App::patch('/v1/projects/:projectId/smtp') ->param('password', '', new Text(0, 0), 'SMTP server password', true) ->param('secure', '', new WhiteList(['tls', 'ssl'], true), 'Does SMTP server use secure connection', true) ->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) { + ->inject('dbForPlatform') + ->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 $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -1722,7 +1814,7 @@ App::patch('/v1/projects/:projectId/smtp') ]; } - $project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('smtp', $smtp)); + $project = $dbForPlatform->updateDocument('projects', $project->getId(), $project->setAttribute('smtp', $smtp)); $response->dynamic($project, Response::MODEL_PROJECT); }); @@ -1747,10 +1839,10 @@ App::post('/v1/projects/:projectId/smtp/tests') ->param('password', '', new Text(0, 0), 'SMTP server password', true) ->param('secure', '', new WhiteList(['tls', 'ssl'], true), 'Does SMTP server use secure connection', true) ->inject('response') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('queueForMails') - ->action(function (string $projectId, array $emails, string $senderName, string $senderEmail, string $replyTo, string $host, int $port, string $username, string $password, string $secure, Response $response, Database $dbForConsole, Mail $queueForMails) { - $project = $dbForConsole->getDocument('projects', $projectId); + ->action(function (string $projectId, array $emails, string $senderName, string $senderEmail, string $replyTo, string $host, int $port, string $username, string $password, string $secure, Response $response, Database $dbForPlatform, Mail $queueForMails) { + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -1800,12 +1892,12 @@ App::get('/v1/projects/:projectId/templates/sms/:type/:locale') ->param('type', '', new WhiteList(Config::getParam('locale-templates')['sms'] ?? []), 'Template type') ->param('locale', '', fn ($localeCodes) => new WhiteList($localeCodes), 'Template locale', false, ['localeCodes']) ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForPlatform) { throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED); - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -1841,10 +1933,10 @@ App::get('/v1/projects/:projectId/templates/email/:type/:locale') ->param('type', '', new WhiteList(Config::getParam('locale-templates')['email'] ?? []), 'Template type') ->param('locale', '', fn ($localeCodes) => new WhiteList($localeCodes), 'Template locale', false, ['localeCodes']) ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -1894,12 +1986,12 @@ App::patch('/v1/projects/:projectId/templates/sms/:type/:locale') ->param('locale', '', fn ($localeCodes) => new WhiteList($localeCodes), 'Template locale', false, ['localeCodes']) ->param('message', '', new Text(0), 'Template message') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, string $type, string $locale, string $message, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $type, string $locale, string $message, Response $response, Database $dbForPlatform) { throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED); - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -1910,7 +2002,7 @@ App::patch('/v1/projects/:projectId/templates/sms/:type/:locale') 'message' => $message ]; - $project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('templates', $templates)); + $project = $dbForPlatform->updateDocument('projects', $project->getId(), $project->setAttribute('templates', $templates)); $response->dynamic(new Document([ 'message' => $message, @@ -1938,10 +2030,10 @@ App::patch('/v1/projects/:projectId/templates/email/:type/:locale') ->param('senderEmail', '', new Email(), 'Email of the sender', true) ->param('replyTo', '', new Email(), 'Reply to email', true) ->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) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $type, string $locale, string $subject, string $message, string $senderName, string $senderEmail, string $replyTo, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -1956,7 +2048,7 @@ App::patch('/v1/projects/:projectId/templates/email/:type/:locale') 'message' => $message ]; - $project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('templates', $templates)); + $project = $dbForPlatform->updateDocument('projects', $project->getId(), $project->setAttribute('templates', $templates)); $response->dynamic(new Document([ 'type' => $type, @@ -1983,12 +2075,12 @@ App::delete('/v1/projects/:projectId/templates/sms/:type/:locale') ->param('type', '', new WhiteList(Config::getParam('locale-templates')['sms'] ?? []), 'Template type') ->param('locale', '', fn ($localeCodes) => new WhiteList($localeCodes), 'Template locale', false, ['localeCodes']) ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForPlatform) { throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED); - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -2003,7 +2095,7 @@ App::delete('/v1/projects/:projectId/templates/sms/:type/:locale') unset($template['sms.' . $type . '-' . $locale]); - $project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('templates', $templates)); + $project = $dbForPlatform->updateDocument('projects', $project->getId(), $project->setAttribute('templates', $templates)); $response->dynamic(new Document([ 'type' => $type, @@ -2026,10 +2118,10 @@ App::delete('/v1/projects/:projectId/templates/email/:type/:locale') ->param('type', '', new WhiteList(Config::getParam('locale-templates')['email'] ?? []), 'Template type') ->param('locale', '', fn ($localeCodes) => new WhiteList($localeCodes), 'Template locale', false, ['localeCodes']) ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForPlatform) { - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -2044,7 +2136,7 @@ App::delete('/v1/projects/:projectId/templates/email/:type/:locale') unset($templates['email.' . $type . '-' . $locale]); - $project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('templates', $templates)); + $project = $dbForPlatform->updateDocument('projects', $project->getId(), $project->setAttribute('templates', $templates)); $response->dynamic(new Document([ 'type' => $type, diff --git a/app/controllers/api/proxy.php b/app/controllers/api/proxy.php index 84484a7209..0d2fed8e66 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; @@ -42,9 +43,9 @@ App::post('/v1/proxy/rules') ->inject('project') ->inject('queueForCertificates') ->inject('queueForEvents') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('dbForProject') - ->action(function (string $domain, string $resourceType, string $resourceId, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForConsole, Database $dbForProject) { + ->action(function (string $domain, string $resourceType, string $resourceId, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject) { $mainDomain = System::getEnv('_APP_DOMAIN', ''); if ($domain === $mainDomain) { throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your main domain to specific resource. Please use subdomain or a different domain.'); @@ -59,11 +60,17 @@ App::post('/v1/proxy/rules') throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.'); } - $document = $dbForConsole->findOne('rules', [ - Query::equal('domain', [$domain]), - ]); + // TODO: @christyjacob remove once we migrate the rules in 1.7.x + if (System::getEnv('_APP_RULES_FORMAT') === 'md5') { + $document = $dbForPlatform->getDocument('rules', md5($domain)); + } else { + $document = $dbForPlatform->findOne('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'); @@ -102,7 +109,9 @@ App::post('/v1/proxy/rules') throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.'); } - $ruleId = ID::unique(); + // TODO: @christyjacob remove once we migrate the rules in 1.7.x + $ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique(); + $rule = new Document([ '$id' => $ruleId, 'projectId' => $project->getId(), @@ -136,7 +145,7 @@ App::post('/v1/proxy/rules') } $rule->setAttribute('status', $status); - $rule = $dbForConsole->createDocument('rules', $rule); + $rule = $dbForPlatform->createDocument('rules', $rule); $queueForEvents->setParam('ruleId', $rule->getId()); @@ -162,8 +171,8 @@ App::get('/v1/proxy/rules') ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->inject('response') ->inject('project') - ->inject('dbForConsole') - ->action(function (array $queries, string $search, Response $response, Document $project, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (array $queries, string $search, Response $response, Document $project, Database $dbForPlatform) { try { $queries = Query::parseQueries($queries); } catch (QueryException $e) { @@ -185,8 +194,14 @@ 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); + $cursorDocument = $dbForPlatform->getDocument('rules', $ruleId); if ($cursorDocument->isEmpty()) { throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Rule '{$ruleId}' for the 'cursor' value not found."); @@ -197,16 +212,16 @@ App::get('/v1/proxy/rules') $filterQueries = Query::groupByType($queries)['filters']; - $rules = $dbForConsole->find('rules', $queries); + $rules = $dbForPlatform->find('rules', $queries); foreach ($rules as $rule) { - $certificate = $dbForConsole->getDocument('certificates', $rule->getAttribute('certificateId', '')); + $certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', '')); $rule->setAttribute('logs', $certificate->getAttribute('logs', '')); $rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', '')); } $response->dynamic(new Document([ 'rules' => $rules, - 'total' => $dbForConsole->count('rules', $filterQueries, APP_LIMIT_COUNT), + 'total' => $dbForPlatform->count('rules', $filterQueries, APP_LIMIT_COUNT), ]), Response::MODEL_PROXY_RULE_LIST); }); @@ -224,15 +239,15 @@ App::get('/v1/proxy/rules/:ruleId') ->param('ruleId', '', new UID(), 'Rule ID.') ->inject('response') ->inject('project') - ->inject('dbForConsole') - ->action(function (string $ruleId, Response $response, Document $project, Database $dbForConsole) { - $rule = $dbForConsole->getDocument('rules', $ruleId); + ->inject('dbForPlatform') + ->action(function (string $ruleId, Response $response, Document $project, Database $dbForPlatform) { + $rule = $dbForPlatform->getDocument('rules', $ruleId); if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getInternalId()) { throw new Exception(Exception::RULE_NOT_FOUND); } - $certificate = $dbForConsole->getDocument('certificates', $rule->getAttribute('certificateId', '')); + $certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', '')); $rule->setAttribute('logs', $certificate->getAttribute('logs', '')); $rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', '')); @@ -255,17 +270,17 @@ App::delete('/v1/proxy/rules/:ruleId') ->param('ruleId', '', new UID(), 'Rule ID.') ->inject('response') ->inject('project') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('queueForDeletes') ->inject('queueForEvents') - ->action(function (string $ruleId, Response $response, Document $project, Database $dbForConsole, Delete $queueForDeletes, Event $queueForEvents) { - $rule = $dbForConsole->getDocument('rules', $ruleId); + ->action(function (string $ruleId, Response $response, Document $project, Database $dbForPlatform, Delete $queueForDeletes, Event $queueForEvents) { + $rule = $dbForPlatform->getDocument('rules', $ruleId); if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getInternalId()) { throw new Exception(Exception::RULE_NOT_FOUND); } - $dbForConsole->deleteDocument('rules', $rule->getId()); + $dbForPlatform->deleteDocument('rules', $rule->getId()); $queueForDeletes ->setType(DELETE_TYPE_DOCUMENT) @@ -294,10 +309,10 @@ App::patch('/v1/proxy/rules/:ruleId/verification') ->inject('queueForCertificates') ->inject('queueForEvents') ->inject('project') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('log') - ->action(function (string $ruleId, Response $response, Certificate $queueForCertificates, Event $queueForEvents, Document $project, Database $dbForConsole, Log $log) { - $rule = $dbForConsole->getDocument('rules', $ruleId); + ->action(function (string $ruleId, Response $response, Certificate $queueForCertificates, Event $queueForEvents, Document $project, Database $dbForPlatform, Log $log) { + $rule = $dbForPlatform->getDocument('rules', $ruleId); if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getInternalId()) { throw new Exception(Exception::RULE_NOT_FOUND); @@ -327,7 +342,7 @@ App::patch('/v1/proxy/rules/:ruleId/verification') throw new Exception(Exception::RULE_VERIFICATION_FAILED); } - $dbForConsole->updateDocument('rules', $rule->getId(), $rule->setAttribute('status', 'verifying')); + $dbForPlatform->updateDocument('rules', $rule->getId(), $rule->setAttribute('status', 'verifying')); // Issue a TLS certificate when domain is verified $queueForCertificates @@ -338,7 +353,7 @@ App::patch('/v1/proxy/rules/:ruleId/verification') $queueForEvents->setParam('ruleId', $rule->getId()); - $certificate = $dbForConsole->getDocument('certificates', $rule->getAttribute('certificateId', '')); + $certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', '')); $rule->setAttribute('logs', $certificate->getAttribute('logs', '')); $response->dynamic($rule, Response::MODEL_PROXY_RULE); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 4e30832a67..7d4361a192 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -16,7 +16,8 @@ use Utopia\App; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; -use Utopia\Database\Exception\Duplicate; +use Utopia\Database\Exception\Duplicate as DuplicateException; +use Utopia\Database\Exception\NotFound as NotFoundException; use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; @@ -24,6 +25,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; @@ -48,6 +50,7 @@ App::post('/v1/storage/buckets') ->desc('Create bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.write') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('event', 'buckets.[bucketId].create') ->label('audits.event', 'bucket.create') ->label('audits.resource', 'bucket/{response.$id}') @@ -65,19 +68,20 @@ App::post('/v1/storage/buckets') ->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true) ->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan']) ->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true) - ->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true) + ->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD], true), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true) ->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true) ->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true) ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, string $compression, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) { + ->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, ?string $compression, ?bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) { $bucketId = $bucketId === 'unique()' ? ID::unique() : $bucketId; // Map aggregate permissions into the multiple permissions they represent. $permissions = Permission::aggregate($permissions); - + $compression ??= Compression::NONE; + $encryption ??= true; try { $files = (Config::getParam('collections', [])['buckets'] ?? [])['files'] ?? []; if (empty($files)) { @@ -129,7 +133,7 @@ App::post('/v1/storage/buckets') $bucket = $dbForProject->getDocument('buckets', $bucketId); $dbForProject->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes, permissions: $permissions ?? [], documentSecurity: $fileSecurity); - } catch (Duplicate) { + } catch (DuplicateException) { throw new Exception(Exception::STORAGE_BUCKET_ALREADY_EXISTS); } @@ -146,6 +150,7 @@ App::get('/v1/storage/buckets') ->desc('List buckets') ->groups(['api', 'storage']) ->label('scope', 'buckets.read') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'listBuckets') @@ -178,6 +183,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); @@ -200,6 +211,7 @@ App::get('/v1/storage/buckets/:bucketId') ->desc('Get bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.read') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'getBucket') @@ -225,6 +237,7 @@ App::put('/v1/storage/buckets/:bucketId') ->desc('Update bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.write') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('event', 'buckets.[bucketId].update') ->label('audits.event', 'bucket.update') ->label('audits.resource', 'bucket/{response.$id}') @@ -242,13 +255,13 @@ App::put('/v1/storage/buckets/:bucketId') ->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true) ->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan']) ->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true) - ->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true) + ->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD], true), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true) ->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true) ->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true) ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, string $compression, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) { + ->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, ?string $compression, ?bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) { $bucket = $dbForProject->getDocument('buckets', $bucketId); if ($bucket->isEmpty()) { @@ -261,11 +274,8 @@ App::put('/v1/storage/buckets/:bucketId') $enabled ??= $bucket->getAttribute('enabled', true); $encryption ??= $bucket->getAttribute('encryption', true); $antivirus ??= $bucket->getAttribute('antivirus', true); + $compression ??= $bucket->getAttribute('compression', Compression::NONE); - /** - * Map aggregate permissions into the multiple permissions they represent, - * accounting for the resource type given that some types not allowed specific permissions. - */ // Map aggregate permissions into the multiple permissions they represent. $permissions = Permission::aggregate($permissions); @@ -279,11 +289,11 @@ App::put('/v1/storage/buckets/:bucketId') ->setAttribute('encryption', $encryption) ->setAttribute('compression', $compression) ->setAttribute('antivirus', $antivirus)); + $dbForProject->updateCollection('bucket_' . $bucket->getInternalId(), $permissions, $fileSecurity); $queueForEvents - ->setParam('bucketId', $bucket->getId()) - ; + ->setParam('bucketId', $bucket->getId()); $response->dynamic($bucket, Response::MODEL_BUCKET); }); @@ -292,6 +302,7 @@ App::delete('/v1/storage/buckets/:bucketId') ->desc('Delete bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.write') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('audits.event', 'bucket.delete') ->label('event', 'buckets.[bucketId].delete') ->label('audits.resource', 'bucket/{request.bucketId}') @@ -330,10 +341,11 @@ App::delete('/v1/storage/buckets/:bucketId') }); App::post('/v1/storage/buckets/:bucketId/files') - ->alias('/v1/storage/files', ['bucketId' => 'default']) + ->alias('/v1/storage/files') ->desc('Create file') ->groups(['api', 'storage']) ->label('scope', 'files.write') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('audits.event', 'file.create') ->label('event', 'buckets.[bucketId].files.[fileId].create') ->label('audits.resource', 'file/{response.$id}') @@ -657,7 +669,11 @@ App::post('/v1/storage/buckets/:bucketId/files') 'metadata' => $metadata, ]); - $file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc); + try { + $file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc); + } catch (NotFoundException) { + throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); + } } else { $file = $file ->setAttribute('chunksUploaded', $chunksUploaded) @@ -673,15 +689,19 @@ App::post('/v1/storage/buckets/:bucketId/files') if (!$validator->isValid($bucket->getCreate())) { throw new Exception(Exception::USER_UNAUTHORIZED); } - $file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); + + try { + $file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); + } catch (NotFoundException) { + throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); + } } } $queueForEvents ->setParam('bucketId', $bucket->getId()) ->setParam('fileId', $file->getId()) - ->setContext('bucket', $bucket) - ; + ->setContext('bucket', $bucket); $metadata = null; // was causing leaks as it was passed by reference @@ -691,10 +711,11 @@ App::post('/v1/storage/buckets/:bucketId/files') }); App::get('/v1/storage/buckets/:bucketId/files') - ->alias('/v1/storage/files', ['bucketId' => 'default']) + ->alias('/v1/storage/files') ->desc('List files') ->groups(['api', 'storage']) ->label('scope', 'files.read') + ->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') @@ -725,11 +746,7 @@ App::get('/v1/storage/buckets/:bucketId/files') throw new Exception(Exception::USER_UNAUTHORIZED); } - try { - $queries = Query::parseQueries($queries); - } catch (QueryException $e) { - throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); - } + $queries = Query::parseQueries($queries); if (!empty($search)) { $queries[] = Query::search('search', $search); @@ -744,6 +761,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) { @@ -761,12 +784,16 @@ App::get('/v1/storage/buckets/:bucketId/files') $filterQueries = Query::groupByType($queries)['filters']; - if ($fileSecurity && !$valid) { - $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)); + try { + if ($fileSecurity && !$valid) { + $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)); + } + } catch (NotFoundException) { + throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } $response->dynamic(new Document([ @@ -776,10 +803,11 @@ App::get('/v1/storage/buckets/:bucketId/files') }); App::get('/v1/storage/buckets/:bucketId/files/:fileId') - ->alias('/v1/storage/files/:fileId', ['bucketId' => 'default']) + ->alias('/v1/storage/files/:fileId') ->desc('Get file') ->groups(['api', 'storage']) ->label('scope', 'files.read') + ->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') @@ -823,10 +851,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId') }); App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') - ->alias('/v1/storage/files/:fileId/preview', ['bucketId' => 'default']) + ->alias('/v1/storage/files/:fileId/preview') ->desc('Get file preview') ->groups(['api', 'storage']) ->label('scope', 'files.read') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('cache', true) ->label('cache.resourceType', 'bucket/{request.bucketId}') ->label('cache.resource', 'file/{request.fileId}') @@ -986,7 +1015,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) ; @@ -995,10 +1024,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') }); App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') - ->alias('/v1/storage/files/:fileId/download', ['bucketId' => 'default']) + ->alias('/v1/storage/files/:fileId/download') ->desc('Get file for download') ->groups(['api', 'storage']) ->label('scope', 'files.read') + ->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') @@ -1049,7 +1079,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', '') . '"') ; @@ -1135,10 +1165,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') }); App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') - ->alias('/v1/storage/files/:fileId/view', ['bucketId' => 'default']) + ->alias('/v1/storage/files/:fileId/view') ->desc('Get file for view') ->groups(['api', 'storage']) ->label('scope', 'files.read') + ->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') @@ -1199,7 +1230,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()) ; @@ -1290,6 +1321,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push') ->desc('Get file for push notification') ->groups(['api', 'storage']) ->label('scope', 'public') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', '*/*') ->label('sdk.methodType', 'location') @@ -1353,7 +1385,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); @@ -1440,10 +1472,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push') }); App::put('/v1/storage/buckets/:bucketId/files/:fileId') - ->alias('/v1/storage/files/:fileId', ['bucketId' => 'default']) + ->alias('/v1/storage/files/:fileId') ->desc('Update file') ->groups(['api', 'storage']) ->label('scope', 'files.write') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('event', 'buckets.[bucketId].files.[fileId].update') ->label('audits.event', 'file.update') ->label('audits.resource', 'file/{response.$id}') @@ -1529,10 +1562,14 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') $file->setAttribute('name', $name); } - if ($fileSecurity && !$valid) { - $file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file); - } else { - $file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); + try { + if ($fileSecurity && !$valid) { + $file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file); + } else { + $file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); + } + } catch (NotFoundException) { + throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } $queueForEvents @@ -1548,6 +1585,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') ->desc('Delete file') ->groups(['api', 'storage']) ->label('scope', 'files.write') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('event', 'buckets.[bucketId].files.[fileId].delete') ->label('audits.event', 'file.delete') ->label('audits.resource', 'file/{request.fileId}') @@ -1614,10 +1652,14 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') ->setResource('file/' . $fileId) ; - if ($fileSecurity && !$valid) { - $deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId); - } else { - $deleted = Authorization::skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId)); + try { + if ($fileSecurity && !$valid) { + $deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId); + } else { + $deleted = Authorization::skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId)); + } + } catch (NotFoundException) { + throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } if (!$deleted) { @@ -1641,6 +1683,7 @@ App::get('/v1/storage/usage') ->desc('Get storage usage stats') ->groups(['api', 'storage']) ->label('scope', 'files.read') + ->label('resourceType', RESOURCE_TYPE_BUCKETS) ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'getUsage') @@ -1720,6 +1763,7 @@ App::get('/v1/storage/:bucketId/usage') ->desc('Get bucket usage stats') ->groups(['api', 'storage']) ->label('scope', 'files.read') + ->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/api/teams.php b/app/controllers/api/teams.php index 146b5d5f81..f829800b98 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); @@ -460,17 +467,17 @@ App::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. @@ -485,7 +492,7 @@ App::post('/v1/teams/:teamId/memberships') $identityWithMatchingEmail = $dbForProject->findOne('identities', [ Query::equal('providerEmail', [$email]), ]); - if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { + if (!$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } @@ -720,9 +727,9 @@ App::get('/v1/teams/:teamId/memberships') ->param('queries', [], new Memberships(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Memberships::ALLOWED_ATTRIBUTES), true) ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->inject('response') + ->inject('project') ->inject('dbForProject') - ->action(function (string $teamId, array $queries, string $search, Response $response, Database $dbForProject) { - + ->action(function (string $teamId, array $queries, string $search, Response $response, Document $project, Database $dbForProject) { $team = $dbForProject->getDocument('teams', $teamId); if ($team->isEmpty()) { @@ -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); @@ -776,27 +790,51 @@ App::get('/v1/teams/:teamId/memberships') $memberships = array_filter($memberships, fn (Document $membership) => !empty($membership->getAttribute('userId'))); - $memberships = array_map(function ($membership) use ($dbForProject, $team) { - $user = $dbForProject->getDocument('users', $membership->getAttribute('userId')); + $membershipsPrivacy = [ + 'userName' => $project->getAttribute('auths', [])['membershipsUserName'] ?? true, + 'userEmail' => $project->getAttribute('auths', [])['membershipsUserEmail'] ?? true, + 'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true, + ]; - $mfa = $user->getAttribute('mfa', false); - if ($mfa) { - $totp = TOTP::getAuthenticatorFromUser($user); - $totpEnabled = $totp && $totp->getAttribute('verified', false); - $emailEnabled = $user->getAttribute('email', false) && $user->getAttribute('emailVerification', false); - $phoneEnabled = $user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false); + $roles = Authorization::getRoles(); + $isPrivilegedUser = Auth::isPrivilegedUser($roles); + $isAppUser = Auth::isAppUser($roles); - if (!$totpEnabled && !$emailEnabled && !$phoneEnabled) { - $mfa = false; + $membershipsPrivacy = array_map(function ($privacy) use ($isPrivilegedUser, $isAppUser) { + return $privacy || $isPrivilegedUser || $isAppUser; + }, $membershipsPrivacy); + + $memberships = array_map(function ($membership) use ($dbForProject, $team, $membershipsPrivacy) { + $user = !empty(array_filter($membershipsPrivacy)) + ? $dbForProject->getDocument('users', $membership->getAttribute('userId')) + : new Document(); + + if ($membershipsPrivacy['mfa']) { + $mfa = $user->getAttribute('mfa', false); + + if ($mfa) { + $totp = TOTP::getAuthenticatorFromUser($user); + $totpEnabled = $totp && $totp->getAttribute('verified', false); + $emailEnabled = $user->getAttribute('email', false) && $user->getAttribute('emailVerification', false); + $phoneEnabled = $user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false); + + if (!$totpEnabled && !$emailEnabled && !$phoneEnabled) { + $mfa = false; + } } + + $membership->setAttribute('mfa', $mfa); } - $membership - ->setAttribute('mfa', $mfa) - ->setAttribute('teamName', $team->getAttribute('name')) - ->setAttribute('userName', $user->getAttribute('name')) - ->setAttribute('userEmail', $user->getAttribute('email')) - ; + if ($membershipsPrivacy['userName']) { + $membership->setAttribute('userName', $user->getAttribute('name')); + } + + if ($membershipsPrivacy['userEmail']) { + $membership->setAttribute('userEmail', $user->getAttribute('email')); + } + + $membership->setAttribute('teamName', $team->getAttribute('name')); return $membership; }, $memberships); @@ -823,8 +861,9 @@ App::get('/v1/teams/:teamId/memberships/:membershipId') ->param('teamId', '', new UID(), 'Team ID.') ->param('membershipId', '', new UID(), 'Membership ID.') ->inject('response') + ->inject('project') ->inject('dbForProject') - ->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject) { + ->action(function (string $teamId, string $membershipId, Response $response, Document $project, Database $dbForProject) { $team = $dbForProject->getDocument('teams', $teamId); @@ -838,27 +877,50 @@ App::get('/v1/teams/:teamId/memberships/:membershipId') throw new Exception(Exception::MEMBERSHIP_NOT_FOUND); } - $user = $dbForProject->getDocument('users', $membership->getAttribute('userId')); + $membershipsPrivacy = [ + 'userName' => $project->getAttribute('auths', [])['membershipsUserName'] ?? true, + 'userEmail' => $project->getAttribute('auths', [])['membershipsUserEmail'] ?? true, + 'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true, + ]; - $mfa = $user->getAttribute('mfa', false); + $roles = Authorization::getRoles(); + $isPrivilegedUser = Auth::isPrivilegedUser($roles); + $isAppUser = Auth::isAppUser($roles); - if ($mfa) { - $totp = TOTP::getAuthenticatorFromUser($user); - $totpEnabled = $totp && $totp->getAttribute('verified', false); - $emailEnabled = $user->getAttribute('email', false) && $user->getAttribute('emailVerification', false); - $phoneEnabled = $user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false); + $membershipsPrivacy = array_map(function ($privacy) use ($isPrivilegedUser, $isAppUser) { + return $privacy || $isPrivilegedUser || $isAppUser; + }, $membershipsPrivacy); - if (!$totpEnabled && !$emailEnabled && !$phoneEnabled) { - $mfa = false; + $user = !empty(array_filter($membershipsPrivacy)) + ? $dbForProject->getDocument('users', $membership->getAttribute('userId')) + : new Document(); + + if ($membershipsPrivacy['mfa']) { + $mfa = $user->getAttribute('mfa', false); + + if ($mfa) { + $totp = TOTP::getAuthenticatorFromUser($user); + $totpEnabled = $totp && $totp->getAttribute('verified', false); + $emailEnabled = $user->getAttribute('email', false) && $user->getAttribute('emailVerification', false); + $phoneEnabled = $user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false); + + if (!$totpEnabled && !$emailEnabled && !$phoneEnabled) { + $mfa = false; + } } + + $membership->setAttribute('mfa', $mfa); } - $membership - ->setAttribute('mfa', $mfa) - ->setAttribute('teamName', $team->getAttribute('name')) - ->setAttribute('userName', $user->getAttribute('name')) - ->setAttribute('userEmail', $user->getAttribute('email')) - ; + if ($membershipsPrivacy['userName']) { + $membership->setAttribute('userName', $user->getAttribute('name')); + } + + if ($membershipsPrivacy['userEmail']) { + $membership->setAttribute('userEmail', $user->getAttribute('email')); + } + + $membership->setAttribute('teamName', $team->getAttribute('name')); $response->dynamic($membership, Response::MODEL_MEMBERSHIP); }); @@ -998,7 +1060,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') throw new Exception(Exception::TEAM_INVITE_MISMATCH, 'Invite does not belong to current user (' . $user->getAttribute('email') . ')'); } - if ($user->isEmpty()) { + $hasSession = !$user->isEmpty(); + if (!$hasSession) { $user->setAttributes($dbForProject->getDocument('users', $userId)->getArrayCopy()); // Get user } @@ -1017,39 +1080,64 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') Authorization::skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true))); - // Log user in + // Create session for the user if not logged in + if (!$hasSession) { + Authorization::setRole(Role::user($user->getId())->toString()); - Authorization::setRole(Role::user($user->getId())->toString()); + $detector = new Detector($request->getUserAgent('UNKNOWN')); + $record = $geodb->get($request->getIP()); + $authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; + $expire = DateTime::addSeconds(new \DateTime(), $authDuration); + $secret = Auth::tokenGenerator(); + $session = new Document(array_merge([ + '$id' => ID::unique(), + '$permissions' => [ + Permission::read(Role::user($user->getId())), + Permission::update(Role::user($user->getId())), + Permission::delete(Role::user($user->getId())), + ], + 'userId' => $user->getId(), + 'userInternalId' => $user->getInternalId(), + 'provider' => Auth::SESSION_PROVIDER_EMAIL, + 'providerUid' => $user->getAttribute('email'), + 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak + 'userAgent' => $request->getUserAgent('UNKNOWN'), + 'ip' => $request->getIP(), + 'factors' => ['email'], + 'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--', + 'expire' => DateTime::addSeconds(new \DateTime(), $authDuration) + ], $detector->getOS(), $detector->getClient(), $detector->getDevice())); - $detector = new Detector($request->getUserAgent('UNKNOWN')); - $record = $geodb->get($request->getIP()); - $authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; - $expire = DateTime::addSeconds(new \DateTime(), $authDuration); - $secret = Auth::tokenGenerator(); - $session = new Document(array_merge([ - '$id' => ID::unique(), - 'userId' => $user->getId(), - 'userInternalId' => $user->getInternalId(), - 'provider' => Auth::SESSION_PROVIDER_EMAIL, - 'providerUid' => $user->getAttribute('email'), - 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak - 'userAgent' => $request->getUserAgent('UNKNOWN'), - 'ip' => $request->getIP(), - 'factors' => ['email'], - 'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--', - 'expire' => DateTime::addSeconds(new \DateTime(), $authDuration) - ], $detector->getOS(), $detector->getClient(), $detector->getDevice())); + $session = $dbForProject->createDocument('sessions', $session); - $session = $dbForProject->createDocument('sessions', $session - ->setAttribute('$permissions', [ - Permission::read(Role::user($user->getId())), - Permission::update(Role::user($user->getId())), - Permission::delete(Role::user($user->getId())), - ])); + Authorization::setRole(Role::user($userId)->toString()); - $dbForProject->purgeCachedDocument('users', $user->getId()); + if (!Config::getParam('domainVerification')) { + $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])); + } - Authorization::setRole(Role::user($userId)->toString()); + $response + ->addCookie( + name: Auth::$cookieName . '_legacy', + value: Auth::encodeSession($user->getId(), $secret), + expire: (new \DateTime($expire))->getTimestamp(), + path: '/', + domain: Config::getParam('cookieDomain'), + secure: ('https' === $protocol), + httponly: true + ) + ->addCookie( + name: Auth::$cookieName, + value: Auth::encodeSession($user->getId(), $secret), + expire: (new \DateTime($expire))->getTimestamp(), + path: '/', + domain: Config::getParam('cookieDomain'), + secure: ('https' === $protocol), + httponly: true, + sameSite: Config::getParam('cookieSamesite') + ) + ; + } $membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership); @@ -1063,22 +1151,11 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->setParam('membershipId', $membership->getId()) ; - if (!Config::getParam('domainVerification')) { - $response - ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])) - ; - } - - $response - ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) - ; - $response->dynamic( $membership - ->setAttribute('teamName', $team->getAttribute('name')) - ->setAttribute('userName', $user->getAttribute('name')) - ->setAttribute('userEmail', $user->getAttribute('email')), + ->setAttribute('teamName', $team->getAttribute('name')) + ->setAttribute('userName', $user->getAttribute('name')) + ->setAttribute('userEmail', $user->getAttribute('email')), Response::MODEL_MEMBERSHIP ); }); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 571df4fdb2..bdb24572eb 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; @@ -50,7 +51,7 @@ 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 +function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Hooks $hooks): Document { $plaintextPassword = $password; $hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array @@ -63,7 +64,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 +141,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 +165,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); } } @@ -175,15 +176,12 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e throw new Exception(Exception::USER_ALREADY_EXISTS); } - $queueForEvents->setParam('userId', $user->getId()); - return $user; } App::post('/v1/users') ->desc('Create user') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -202,10 +200,9 @@ App::post('/v1/users') ->inject('response') ->inject('project') ->inject('dbForProject') - ->inject('queueForEvents') ->inject('hooks') - ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) { - $user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $queueForEvents, $hooks); + ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + $user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($user, Response::MODEL_USER); @@ -214,7 +211,6 @@ App::post('/v1/users') App::post('/v1/users/bcrypt') ->desc('Create user with bcrypt password') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -232,10 +228,9 @@ App::post('/v1/users/bcrypt') ->inject('response') ->inject('project') ->inject('dbForProject') - ->inject('queueForEvents') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) { - $user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + $user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -245,7 +240,6 @@ App::post('/v1/users/bcrypt') App::post('/v1/users/md5') ->desc('Create user with MD5 password') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -263,10 +257,9 @@ App::post('/v1/users/md5') ->inject('response') ->inject('project') ->inject('dbForProject') - ->inject('queueForEvents') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) { - $user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + $user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -276,7 +269,6 @@ App::post('/v1/users/md5') App::post('/v1/users/argon2') ->desc('Create user with Argon2 password') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -294,10 +286,9 @@ App::post('/v1/users/argon2') ->inject('response') ->inject('project') ->inject('dbForProject') - ->inject('queueForEvents') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) { - $user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + $user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -307,7 +298,6 @@ App::post('/v1/users/argon2') App::post('/v1/users/sha') ->desc('Create user with SHA password') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -326,16 +316,15 @@ App::post('/v1/users/sha') ->inject('response') ->inject('project') ->inject('dbForProject') - ->inject('queueForEvents') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) { + ->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { $options = '{}'; if (!empty($passwordVersion)) { $options = '{"version":"' . $passwordVersion . '"}'; } - $user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); + $user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -345,7 +334,6 @@ App::post('/v1/users/sha') App::post('/v1/users/phpass') ->desc('Create user with PHPass password') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -363,10 +351,9 @@ App::post('/v1/users/phpass') ->inject('response') ->inject('project') ->inject('dbForProject') - ->inject('queueForEvents') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) { - $user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + $user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -376,7 +363,6 @@ App::post('/v1/users/phpass') App::post('/v1/users/scrypt') ->desc('Create user with Scrypt password') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -399,9 +385,8 @@ App::post('/v1/users/scrypt') ->inject('response') ->inject('project') ->inject('dbForProject') - ->inject('queueForEvents') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) { + ->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { $options = [ 'salt' => $passwordSalt, 'costCpu' => $passwordCpu, @@ -410,7 +395,7 @@ App::post('/v1/users/scrypt') 'length' => $passwordLength ]; - $user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); + $user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -420,7 +405,6 @@ App::post('/v1/users/scrypt') App::post('/v1/users/scrypt-modified') ->desc('Create user with Scrypt modified password') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -441,10 +425,9 @@ App::post('/v1/users/scrypt-modified') ->inject('response') ->inject('project') ->inject('dbForProject') - ->inject('queueForEvents') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) { - $user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); + ->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + $user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -576,6 +559,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 +875,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 +932,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); @@ -1214,7 +1214,7 @@ App::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); } @@ -1485,7 +1485,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) { @@ -1499,8 +1501,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) { @@ -1801,6 +1804,7 @@ App::post('/v1/users/:userId/sessions') 'provider' => Auth::SESSION_PROVIDER_SERVER, 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'userAgent' => $request->getUserAgent('UNKNOWN'), + 'factors' => ['server'], 'ip' => $request->getIP(), 'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--', 'expire' => $expire, @@ -1813,8 +1817,11 @@ App::post('/v1/users/:userId/sessions') $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); $session = $dbForProject->createDocument('sessions', $session); + + $dbForProject->purgeCachedDocument('users', $user->getId()); + $session - ->setAttribute('secret', $secret) + ->setAttribute('secret', Auth::encodeSession($user->getId(), $secret)) ->setAttribute('countryName', $countryName); $queueForEvents diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index f3381490ec..6e81c43ef8 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; @@ -41,7 +42,7 @@ 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) { +$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 $dbForPlatform, Build $queueForBuilds, callable $getProjectDB, Request $request) { $errors = []; foreach ($repositories as $resource) { try { @@ -52,7 +53,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId } $projectId = $resource->getAttribute('projectId'); - $project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId)); + $project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId)); $dbForProject = $getProjectDB($project); $functionId = $resource->getAttribute('resourceId'); @@ -103,13 +104,13 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId $latestCommentId = ''; if (!empty($providerPullRequestId) && $function->getAttribute('providerSilentMode', false) === false) { - $latestComment = Authorization::skip(fn () => $dbForConsole->findOne('vcsComments', [ + $latestComment = Authorization::skip(fn () => $dbForPlatform->findOne('vcsComments', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::equal('providerPullRequestId', [$providerPullRequestId]), 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)); @@ -124,7 +125,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId if (!empty($latestCommentId)) { $teamId = $project->getAttribute('teamId', ''); - $latestComment = Authorization::skip(fn () => $dbForConsole->createDocument('vcsComments', new Document([ + $latestComment = Authorization::skip(fn () => $dbForPlatform->createDocument('vcsComments', new Document([ '$id' => ID::unique(), '$permissions' => [ Permission::read(Role::team(ID::custom($teamId))), @@ -145,7 +146,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId } } } elseif (!empty($providerBranch)) { - $latestComments = Authorization::skip(fn () => $dbForConsole->find('vcsComments', [ + $latestComments = Authorization::skip(fn () => $dbForPlatform->find('vcsComments', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::equal('providerBranch', [$providerBranch]), Query::orderDesc('$createdAt'), @@ -318,8 +319,8 @@ App::get('/v1/vcs/github/callback') ->inject('project') ->inject('request') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $providerInstallationId, string $setupAction, string $state, string $code, GitHub $github, Document $user, Document $project, Request $request, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $providerInstallationId, string $setupAction, string $state, string $code, GitHub $github, Document $user, Document $project, Request $request, Response $response, Database $dbForPlatform) { if (empty($state)) { $error = 'Installation requests from organisation members for the Appwrite GitHub App are currently unsupported. To proceed with the installation, login to the Appwrite Console and install the GitHub App.'; throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $error); @@ -338,7 +339,7 @@ App::get('/v1/vcs/github/callback') $redirectSuccess = $state['success'] ?? ''; $redirectFailure = $state['failure'] ?? ''; - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { $error = 'Project with the ID from state could not be found.'; @@ -367,24 +368,22 @@ App::get('/v1/vcs/github/callback') $oauth2ID = $oauth2->getUserID($accessToken); // Makes sure this email is not already used in another identity - $identity = $dbForConsole->findOne('identities', [ + $identity = $dbForPlatform->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); } - } - if ($identity !== false && !$identity->isEmpty()) { $identity = $identity ->setAttribute('providerAccessToken', $accessToken) ->setAttribute('providerRefreshToken', $refreshToken) ->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$accessTokenExpiry)); - $dbForConsole->updateDocument('identities', $identity->getId(), $identity); + $dbForPlatform->updateDocument('identities', $identity->getId(), $identity); } else { - $identity = $dbForConsole->createDocument('identities', new Document([ + $identity = $dbForPlatform->createDocument('identities', new Document([ '$id' => ID::unique(), '$permissions' => [ Permission::read(Role::any()), @@ -412,12 +411,12 @@ App::get('/v1/vcs/github/callback') $projectInternalId = $project->getInternalId(); - $installation = $dbForConsole->findOne('installations', [ + $installation = $dbForPlatform->findOne('installations', [ Query::equal('providerInstallationId', [$providerInstallationId]), Query::equal('projectInternalId', [$projectInternalId]) ]); - if ($installation === false || $installation->isEmpty()) { + if ($installation->isEmpty()) { $teamId = $project->getAttribute('teamId', ''); $installation = new Document([ @@ -437,12 +436,12 @@ App::get('/v1/vcs/github/callback') 'personal' => $personalSlug === $owner ]); - $installation = $dbForConsole->createDocument('installations', $installation); + $installation = $dbForPlatform->createDocument('installations', $installation); } else { $installation = $installation ->setAttribute('organization', $owner) ->setAttribute('personal', $personalSlug === $owner); - $installation = $dbForConsole->updateDocument('installations', $installation->getId(), $installation); + $installation = $dbForPlatform->updateDocument('installations', $installation->getId(), $installation); } } else { $error = 'Installation of the Appwrite GitHub App on organization accounts is restricted to organization owners. As a member of the organization, you do not have the necessary permissions to install this GitHub App. Please contact the organization owner to create the installation from the Appwrite console.'; @@ -481,9 +480,9 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro ->inject('gitHub') ->inject('response') ->inject('project') - ->inject('dbForConsole') - ->action(function (string $installationId, string $providerRepositoryId, string $providerRootDirectory, GitHub $github, Response $response, Document $project, Database $dbForConsole) { - $installation = $dbForConsole->getDocument('installations', $installationId); + ->inject('dbForPlatform') + ->action(function (string $installationId, string $providerRepositoryId, string $providerRootDirectory, GitHub $github, Response $response, Document $project, Database $dbForPlatform) { + $installation = $dbForPlatform->getDocument('installations', $installationId); if ($installation->isEmpty()) { throw new Exception(Exception::INSTALLATION_NOT_FOUND); @@ -542,9 +541,9 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories/:pr ->inject('gitHub') ->inject('response') ->inject('project') - ->inject('dbForConsole') - ->action(function (string $installationId, string $providerRepositoryId, string $providerRootDirectory, GitHub $github, Response $response, Document $project, Database $dbForConsole) { - $installation = $dbForConsole->getDocument('installations', $installationId); + ->inject('dbForPlatform') + ->action(function (string $installationId, string $providerRepositoryId, string $providerRootDirectory, GitHub $github, Response $response, Document $project, Database $dbForPlatform) { + $installation = $dbForPlatform->getDocument('installations', $installationId); if ($installation->isEmpty()) { throw new Exception(Exception::INSTALLATION_NOT_FOUND); @@ -613,13 +612,13 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories') ->inject('gitHub') ->inject('response') ->inject('project') - ->inject('dbForConsole') - ->action(function (string $installationId, string $search, GitHub $github, Response $response, Document $project, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $installationId, string $search, GitHub $github, Response $response, Document $project, Database $dbForPlatform) { if (empty($search)) { $search = ""; } - $installation = $dbForConsole->getDocument('installations', $installationId); + $installation = $dbForPlatform->getDocument('installations', $installationId); if ($installation->isEmpty()) { throw new Exception(Exception::INSTALLATION_NOT_FOUND); @@ -710,9 +709,9 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories') ->inject('user') ->inject('response') ->inject('project') - ->inject('dbForConsole') - ->action(function (string $installationId, string $name, bool $private, GitHub $github, Document $user, Response $response, Document $project, Database $dbForConsole) { - $installation = $dbForConsole->getDocument('installations', $installationId); + ->inject('dbForPlatform') + ->action(function (string $installationId, string $name, bool $private, GitHub $github, Document $user, Response $response, Document $project, Database $dbForPlatform) { + $installation = $dbForPlatform->getDocument('installations', $installationId); if ($installation->isEmpty()) { throw new Exception(Exception::INSTALLATION_NOT_FOUND); @@ -721,11 +720,11 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories') if ($installation->getAttribute('personal', false) === true) { $oauth2 = new OAuth2Github(System::getEnv('_APP_VCS_GITHUB_CLIENT_ID', ''), System::getEnv('_APP_VCS_GITHUB_CLIENT_SECRET', ''), ""); - $identity = $dbForConsole->findOne('identities', [ + $identity = $dbForPlatform->findOne('identities', [ 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); } @@ -751,7 +750,7 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories') ->setAttribute('providerRefreshToken', $refreshToken) ->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry(''))); - $dbForConsole->updateDocument('identities', $identity->getId(), $identity); + $dbForPlatform->updateDocument('identities', $identity->getId(), $identity); } try { @@ -809,9 +808,9 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro ->inject('gitHub') ->inject('response') ->inject('project') - ->inject('dbForConsole') - ->action(function (string $installationId, string $providerRepositoryId, GitHub $github, Response $response, Document $project, Database $dbForConsole) { - $installation = $dbForConsole->getDocument('installations', $installationId); + ->inject('dbForPlatform') + ->action(function (string $installationId, string $providerRepositoryId, GitHub $github, Response $response, Document $project, Database $dbForPlatform) { + $installation = $dbForPlatform->getDocument('installations', $installationId); if ($installation->isEmpty()) { throw new Exception(Exception::INSTALLATION_NOT_FOUND); @@ -858,9 +857,9 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro ->inject('gitHub') ->inject('response') ->inject('project') - ->inject('dbForConsole') - ->action(function (string $installationId, string $providerRepositoryId, GitHub $github, Response $response, Document $project, Database $dbForConsole) { - $installation = $dbForConsole->getDocument('installations', $installationId); + ->inject('dbForPlatform') + ->action(function (string $installationId, string $providerRepositoryId, GitHub $github, Response $response, Document $project, Database $dbForPlatform) { + $installation = $dbForPlatform->getDocument('installations', $installationId); if ($installation->isEmpty()) { throw new Exception(Exception::INSTALLATION_NOT_FOUND); @@ -898,11 +897,11 @@ App::post('/v1/vcs/github/events') ->inject('gitHub') ->inject('request') ->inject('response') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('getProjectDB') ->inject('queueForBuilds') ->action( - function (GitHub $github, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) { + function (GitHub $github, Request $request, Response $response, Database $dbForPlatform, 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,36 +935,36 @@ App::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 () => $dbForPlatform->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, $dbForPlatform, $queueForBuilds, $getProjectDB, $request); } } elseif ($event == $github::EVENT_INSTALLATION) { if ($parsedPayload["action"] == "deleted") { // TODO: Use worker for this job instead (update function as well) $providerInstallationId = $parsedPayload["installationId"]; - $installations = $dbForConsole->find('installations', [ + $installations = $dbForPlatform->find('installations', [ Query::equal('providerInstallationId', [$providerInstallationId]), Query::limit(1000) ]); foreach ($installations as $installation) { - $repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [ + $repositories = Authorization::skip(fn () => $dbForPlatform->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 () => $dbForPlatform->deleteDocument('repositories', $repository->getId())); } - $dbForConsole->deleteDocument('installations', $installation->getId()); + $dbForPlatform->deleteDocument('installations', $installation->getId()); } } } elseif ($event == $github::EVENT_PULL_REQUEST) { @@ -994,12 +993,12 @@ App::post('/v1/vcs/github/events') $providerCommitAuthor = $commitDetails["commitAuthor"] ?? ''; $providerCommitMessage = $commitDetails["commitMessage"] ?? ''; - $repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [ + $repositories = Authorization::skip(fn () => $dbForPlatform->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, $dbForPlatform, $queueForBuilds, $getProjectDB, $request); } elseif ($parsedPayload["action"] == "closed") { // Allowed external contributions cleanup @@ -1008,7 +1007,7 @@ App::post('/v1/vcs/github/events') $external = $parsedPayload["external"] ?? true; if ($external) { - $repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [ + $repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::orderDesc('$createdAt') ])); @@ -1019,7 +1018,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 = Authorization::skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository)); } } } @@ -1046,8 +1045,8 @@ App::get('/v1/vcs/installations') ->inject('response') ->inject('project') ->inject('dbForProject') - ->inject('dbForConsole') - ->action(function (array $queries, string $search, Response $response, Document $project, Database $dbForProject, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (array $queries, string $search, Response $response, Document $project, Database $dbForProject, Database $dbForPlatform) { try { $queries = Query::parseQueries($queries); } catch (QueryException $e) { @@ -1069,8 +1068,14 @@ 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); + $cursorDocument = $dbForPlatform->getDocument('installations', $installationId); if ($cursorDocument->isEmpty()) { throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Installation '{$installationId}' for the 'cursor' value not found."); @@ -1081,8 +1086,8 @@ App::get('/v1/vcs/installations') $filterQueries = Query::groupByType($queries)['filters']; - $results = $dbForConsole->find('installations', $queries); - $total = $dbForConsole->count('installations', $filterQueries, APP_LIMIT_COUNT); + $results = $dbForPlatform->find('installations', $queries); + $total = $dbForPlatform->count('installations', $filterQueries, APP_LIMIT_COUNT); $response->dynamic(new Document([ 'installations' => $results, @@ -1104,9 +1109,9 @@ App::get('/v1/vcs/installations/:installationId') ->param('installationId', '', new Text(256), 'Installation Id') ->inject('response') ->inject('project') - ->inject('dbForConsole') - ->action(function (string $installationId, Response $response, Document $project, Database $dbForConsole) { - $installation = $dbForConsole->getDocument('installations', $installationId); + ->inject('dbForPlatform') + ->action(function (string $installationId, Response $response, Document $project, Database $dbForPlatform) { + $installation = $dbForPlatform->getDocument('installations', $installationId); if ($installation === false || $installation->isEmpty()) { throw new Exception(Exception::INSTALLATION_NOT_FOUND); @@ -1132,16 +1137,16 @@ App::delete('/v1/vcs/installations/:installationId') ->param('installationId', '', new Text(256), 'Installation Id') ->inject('response') ->inject('project') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('queueForDeletes') - ->action(function (string $installationId, Response $response, Document $project, Database $dbForConsole, Delete $queueForDeletes) { - $installation = $dbForConsole->getDocument('installations', $installationId); + ->action(function (string $installationId, Response $response, Document $project, Database $dbForPlatform, Delete $queueForDeletes) { + $installation = $dbForPlatform->getDocument('installations', $installationId); if ($installation->isEmpty()) { throw new Exception(Exception::INSTALLATION_NOT_FOUND); } - if (!$dbForConsole->deleteDocument('installations', $installation->getId())) { + if (!$dbForPlatform->deleteDocument('installations', $installation->getId())) { throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove installation from DB'); } @@ -1169,17 +1174,17 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor ->inject('request') ->inject('response') ->inject('project') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->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) { - $installation = $dbForConsole->getDocument('installations', $installationId); + ->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForPlatform, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) { + $installation = $dbForPlatform->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 () => $dbForPlatform->getDocument('repositories', $repositoryId, [ Query::equal('projectInternalId', [$project->getInternalId()]) ])); @@ -1196,7 +1201,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 = Authorization::skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository)); $privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY'); $githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID'); @@ -1220,7 +1225,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, $dbForPlatform, $queueForBuilds, $getProjectDB, $request); $response->noContent(); }); diff --git a/app/controllers/general.php b/app/controllers/general.php index b2a07f06f6..32cc68fecf 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -46,20 +46,28 @@ 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 $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked, string $previewHostname) { $utopia->getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml'); $host = $request->getHostname() ?? ''; + if (!empty($previewHostname)) { + $host = $previewHostname; + } - $route = Authorization::skip( - fn () => $dbForConsole->find('rules', [ - Query::equal('domain', [$host]), - Query::limit(1) - ]) - )[0] ?? null; + // TODO: @christyjacob remove once we migrate the rules in 1.7.x + if (System::getEnv('_APP_RULES_FORMAT') === 'md5') { + $route = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($host))); + } else { + $route = Authorization::skip( + fn () => $dbForPlatform->find('rules', [ + Query::equal('domain', [$host]), + Query::limit(1) + ]) + )[0] ?? new Document(); + } - if ($route === null) { + if ($route->isEmpty()) { if ($host === System::getEnv('_APP_DOMAIN_FUNCTIONS', '')) { throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'This domain cannot be used for security reasons. Please use any subdomain instead.'); } @@ -69,7 +77,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo } if (System::getEnv('_APP_OPTIONS_ROUTER_PROTECTION', 'disabled') === 'enabled') { - if ($host !== 'localhost' && $host !== APP_HOSTNAME_INTERNAL) { // localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations + if ($host !== 'localhost' && $host !== APP_HOSTNAME_INTERNAL && $host !== System::getEnv('_APP_CONSOLE_DOMAIN', '')) { throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'Router protection does not allow accessing Appwrite over this domain. Please add it as custom domain to your project or disable _APP_OPTIONS_ROUTER_PROTECTION environment variable.'); } } @@ -81,7 +89,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo $projectId = $route->getAttribute('projectId'); $project = Authorization::skip( - fn () => $dbForConsole->getDocument('projects', $projectId) + fn () => $dbForPlatform->getDocument('projects', $projectId) ); if (array_key_exists('proxy', $project->getAttribute('services', []))) { $status = $project->getAttribute('services', [])['proxy']; @@ -127,7 +135,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo $requestHeaders = $request->getHeaders(); - $project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId)); + $project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId)); $dbForProject = $getProjectDB($project); @@ -137,6 +145,10 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo throw new AppwriteException(AppwriteException::FUNCTION_NOT_FOUND); } + if ($isResourceBlocked($project, RESOURCE_TYPE_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)]; @@ -348,26 +360,6 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo throw $th; } } finally { - $fileSize = 0; - $file = $request->getFiles('file'); - if (!empty($file)) { - $fileSize = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size']; - } - - $queueForUsage - ->addMetric(METRIC_NETWORK_REQUESTS, 1) - ->addMetric(METRIC_NETWORK_INBOUND, $request->getSize() + $fileSize) - ->addMetric(METRIC_NETWORK_OUTBOUND, $response->getSize()) - ->addMetric(METRIC_EXECUTIONS, 1) - ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS), 1) - ->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000)) // per project - ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function - ->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT))) - ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT))) - ->setProject($project) - ->trigger() - ; - $queueForFunctions ->setType(Func::TYPE_ASYNC_WRITE) ->setExecution($execution) @@ -402,6 +394,26 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo ->setStatusCode($execution['responseStatusCode'] ?? 200) ->send($body); + $fileSize = 0; + $file = $request->getFiles('file'); + if (!empty($file)) { + $fileSize = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size']; + } + + $queueForUsage + ->addMetric(METRIC_NETWORK_REQUESTS, 1) + ->addMetric(METRIC_NETWORK_INBOUND, $request->getSize() + $fileSize) + ->addMetric(METRIC_NETWORK_OUTBOUND, $response->getSize()) + ->addMetric(METRIC_EXECUTIONS, 1) + ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS), 1) + ->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000)) // per project + ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function + ->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT))) + ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT))) + ->setProject($project) + ->trigger() + ; + return true; } elseif ($type === 'api') { $utopia->getRoute()?->label('error', ''); @@ -447,7 +459,7 @@ App::init() ->inject('response') ->inject('console') ->inject('project') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('getProjectDB') ->inject('locale') ->inject('localeCodes') @@ -457,15 +469,17 @@ 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') + ->inject('previewHostname') + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, Reader $geodb, Usage $queueForUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, callable $isResourceBlocked, string $previewHostname) { /* * Appwrite Router */ $host = $request->getHostname() ?? ''; $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 ($host !== $mainDomain || !empty($previewHostname)) { + if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname)) { return; } } @@ -513,19 +527,31 @@ App::init() if (!empty($envDomain) && $envDomain !== 'localhost') { $mainDomain = $envDomain; } else { - $domainDocument = $dbForConsole->findOne('rules', [Query::orderAsc('$id')]); - $mainDomain = $domainDocument ? $domainDocument->getAttribute('domain') : $domain->get(); + // TODO: @christyjacob remove once we migrate the rules in 1.7.x + if (System::getEnv('_APP_RULES_FORMAT') === 'md5') { + $domainDocument = $dbForPlatform->getDocument('rules', md5($envDomain)); + } else { + $domainDocument = $dbForPlatform->findOne('rules', [Query::orderAsc('$id')]); + } + $mainDomain = !$domainDocument->isEmpty() ? $domainDocument->getAttribute('domain') : $domain->get(); } if ($mainDomain !== $domain->get()) { Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.'); } else { - $domainDocument = $dbForConsole->findOne('rules', [ - Query::equal('domain', [$domain->get()]) - ]); + // TODO: @christyjacob remove once we migrate the rules in 1.7.x + if (System::getEnv('_APP_RULES_FORMAT') === 'md5') { + $domainDocument = $dbForPlatform->getDocument('rules', md5($domain->get())); + } else { + $domainDocument = $dbForPlatform->findOne('rules', [ + Query::equal('domain', [$domain->get()]) + ]); + } - if (!$domainDocument) { + if ($domainDocument->isEmpty()) { $domainDocument = new Document([ + // TODO: @christyjacob remove once we migrate the rules in 1.7.x + '$id' => System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique(), 'domain' => $domain->get(), 'resourceType' => 'api', 'status' => 'verifying', @@ -533,7 +559,7 @@ App::init() 'projectInternalId' => 'console' ]); - $domainDocument = $dbForConsole->createDocument('rules', $domainDocument); + $domainDocument = $dbForPlatform->createDocument('rules', $domainDocument); Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...'); @@ -669,21 +695,23 @@ App::options() ->inject('swooleRequest') ->inject('request') ->inject('response') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->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, Func $queueForFunctions, Reader $geodb) { + ->inject('isResourceBlocked') + ->inject('previewHostname') + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked, string $previewHostname) { /* * Appwrite Router */ $host = $request->getHostname() ?? ''; $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 ($host !== $mainDomain || !empty($previewHostname)) { + if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname)) { return; } } @@ -769,6 +797,8 @@ App::error() case 'Utopia\Database\Exception\Relationship': $error = new AppwriteException(AppwriteException::RELATIONSHIP_VALUE_INVALID, $error->getMessage(), previous: $error); break; + case 'Utopia\Database\Exception\NotFound': + $error = new AppwriteException(AppwriteException::COLLECTION_NOT_FOUND, $error->getMessage(), previous: $error); } $code = $error->getCode(); @@ -836,6 +866,8 @@ App::error() if (isset($user) && !$user->isEmpty()) { $log->setUser(new User($user->getId())); + } else { + $log->setUser(new User('guest-' . hash('sha256', $request->getIP()))); } try { @@ -846,7 +878,7 @@ App::error() } $log->setNamespace("http"); - $log->setServer(\gethostname()); + $log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname())); $log->setVersion($version); $log->setType(Log::TYPE_ERROR); $log->setMessage($error->getMessage()); @@ -867,6 +899,7 @@ App::error() $action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD"); $log->setAction($action); + $log->addTag('service', $action); $isProduction = System::getEnv('_APP_ENV', 'development') === 'production'; $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING); @@ -961,21 +994,23 @@ App::get('/robots.txt') ->inject('swooleRequest') ->inject('request') ->inject('response') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->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, Func $queueForFunctions, Reader $geodb) { + ->inject('isResourceBlocked') + ->inject('previewHostname') + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked, string $previewHostname) { $host = $request->getHostname() ?? ''; $mainDomain = System::getEnv('_APP_DOMAIN', ''); - if ($host === $mainDomain || $host === 'localhost') { + if (($host === $mainDomain || $host === 'localhost') && empty($previewHostname)) { $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, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname); } }); @@ -987,21 +1022,23 @@ App::get('/humans.txt') ->inject('swooleRequest') ->inject('request') ->inject('response') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->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, Func $queueForFunctions, Reader $geodb) { + ->inject('isResourceBlocked') + ->inject('previewHostname') + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked, string $previewHostname) { $host = $request->getHostname() ?? ''; $mainDomain = System::getEnv('_APP_DOMAIN', ''); - if ($host === $mainDomain || $host === 'localhost') { + if (($host === $mainDomain || $host === 'localhost') && empty($previewHostname)) { $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, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname); } }); @@ -1065,9 +1102,9 @@ App::get('/v1/ping') ->label('event', 'projects.[projectId].ping') ->inject('response') ->inject('project') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('queueForEvents') - ->action(function (Response $response, Document $project, Database $dbForConsole, Event $queueForEvents) { + ->action(function (Response $response, Document $project, Database $dbForPlatform, Event $queueForEvents) { if ($project->isEmpty()) { throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND); } @@ -1079,8 +1116,8 @@ App::get('/v1/ping') ->setAttribute('pingCount', $pingCount) ->setAttribute('pingedAt', $pingedAt); - Authorization::skip(function () use ($dbForConsole, $project) { - $dbForConsole->updateDocument('projects', $project->getId(), $project); + Authorization::skip(function () use ($dbForPlatform, $project) { + $dbForPlatform->updateDocument('projects', $project->getId(), $project); }); $queueForEvents diff --git a/app/controllers/mock.php b/app/controllers/mock.php index bc071fc885..afbb176b7e 100644 --- a/app/controllers/mock.php +++ b/app/controllers/mock.php @@ -162,15 +162,15 @@ App::post('/v1/mock/api-key-unprefixed') ->label('docs', false) ->param('projectId', '', new UID(), 'Project ID.') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $projectId, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $projectId, Response $response, Database $dbForPlatform) { $isDevelopment = System::getEnv('_APP_ENV', 'development') === 'development'; if (!$isDevelopment) { throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED); } - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); @@ -195,9 +195,9 @@ App::post('/v1/mock/api-key-unprefixed') 'secret' => \bin2hex(\random_bytes(128)), ]); - $key = $dbForConsole->createDocument('keys', $key); + $key = $dbForPlatform->createDocument('keys', $key); - $dbForConsole->purgeCachedDocument('projects', $project->getId()); + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -214,15 +214,15 @@ App::get('/v1/mock/github/callback') ->inject('gitHub') ->inject('project') ->inject('response') - ->inject('dbForConsole') - ->action(function (string $providerInstallationId, string $projectId, GitHub $github, Document $project, Response $response, Database $dbForConsole) { + ->inject('dbForPlatform') + ->action(function (string $providerInstallationId, string $projectId, GitHub $github, Document $project, Response $response, Database $dbForPlatform) { $isDevelopment = System::getEnv('_APP_ENV', 'development') === 'development'; if (!$isDevelopment) { throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED); } - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); if ($project->isEmpty()) { $error = 'Project with the ID from state could not be found.'; @@ -256,7 +256,7 @@ App::get('/v1/mock/github/callback') 'personal' => false ]); - $installation = $dbForConsole->createDocument('installations', $installation); + $installation = $dbForPlatform->createDocument('installations', $installation); } $response->json([ diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index f0d896c95a..a0f65eb484 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -11,10 +11,11 @@ use Appwrite\Event\Delete; use Appwrite\Event\Event; use Appwrite\Event\Func; use Appwrite\Event\Messaging; +use Appwrite\Event\Realtime; use Appwrite\Event\Usage; +use Appwrite\Event\Webhook; use Appwrite\Extend\Exception; use Appwrite\Extend\Exception as AppwriteException; -use Appwrite\Messaging\Adapter\Realtime; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use Utopia\Abuse\Abuse; @@ -28,6 +29,7 @@ use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\Role; use Utopia\Database\Validator\Authorization; +use Utopia\Queue\Connection; use Utopia\System\System; use Utopia\Validator\WhiteList; @@ -57,8 +59,36 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar return $label; }; -$databaseListener = function (string $event, Document $document, Document $project, Usage $queueForUsage, Database $dbForProject) { +$eventDatabaseListener = function (Document $document, Response $response, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime) { + // Only trigger events for user creation with the database listener. + if ($document->getCollection() !== 'users') { + return; + } + $queueForEvents + ->setEvent('users.[userId].create') + ->setParam('userId', $document->getId()) + ->setPayload($response->output($document, Response::MODEL_USER)); + + // Trigger functions, webhooks, and realtime events + $queueForFunctions + ->from($queueForEvents) + ->trigger(); + + $queueForWebhooks + ->from($queueForEvents) + ->trigger(); + + if ($queueForEvents->getProject()->getId() === 'console') { + return; + } + + $queueForRealtime + ->from($queueForEvents) + ->trigger(); +}; + +$usageDatabaseListener = function (string $event, Document $document, Usage $queueForUsage) { $value = 1; if ($event === Database::EVENT_DOCUMENT_DELETE) { $value = -1; @@ -154,14 +184,15 @@ App::init() ->groups(['api']) ->inject('utopia') ->inject('request') - ->inject('dbForConsole') + ->inject('dbForPlatform') + ->inject('dbForProject') ->inject('project') ->inject('user') ->inject('session') ->inject('servers') ->inject('mode') ->inject('team') - ->action(function (App $utopia, Request $request, Database $dbForConsole, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team) { + ->action(function (App $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team) { $route = $utopia->getRoute(); if ($project->isEmpty()) { @@ -253,8 +284,8 @@ App::init() $accessedAt = $key->getAttribute('accessedAt', ''); if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) { $key->setAttribute('accessedAt', DateTime::now()); - $dbForConsole->updateDocument('keys', $key->getId(), $key); - $dbForConsole->purgeCachedDocument('projects', $project->getId()); + $dbForPlatform->updateDocument('keys', $key->getId(), $key); + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); } $sdkValidator = new WhiteList($servers, true); @@ -267,8 +298,8 @@ App::init() /** Update access time as well */ $key->setAttribute('accessedAt', Datetime::now()); - $dbForConsole->updateDocument('keys', $key->getId(), $key); - $dbForConsole->purgeCachedDocument('projects', $project->getId()); + $dbForPlatform->updateDocument('keys', $key->getId(), $key); + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); } } } @@ -305,6 +336,33 @@ App::init() Authorization::setRole($authRole); } + /** + * Update project last activity + */ + if (!$project->isEmpty() && $project->getId() !== 'console') { + $accessedAt = $project->getAttribute('accessedAt', ''); + if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) { + $project->setAttribute('accessedAt', DateTime::now()); + Authorization::skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project)); + } + } + + /** + * Update user last activity + */ + if (!empty($user->getId())) { + $accessedAt = $user->getAttribute('accessedAt', ''); + if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCESS)) > $accessedAt) { + $user->setAttribute('accessedAt', DateTime::now()); + + if (APP_MODE_ADMIN !== $mode) { + $dbForProject->updateDocument('users', $user->getId(), $user); + } else { + $dbForPlatform->updateDocument('users', $user->getId(), $user); + } + } + } + /** Do not allow access to disabled services */ $service = $route->getLabel('sdk.namespace', ''); if (!empty($service)) { @@ -353,6 +411,7 @@ App::init() ->inject('response') ->inject('project') ->inject('user') + ->inject('queue') ->inject('queueForEvents') ->inject('queueForMessaging') ->inject('queueForAudits') @@ -362,7 +421,7 @@ 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) { + ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Connection $queue, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode) use ($usageDatabaseListener, $eventDatabaseListener) { $route = $utopia->getRoute(); @@ -456,9 +515,24 @@ App::init() $queueForBuilds->setProject($project); $queueForMessaging->setProject($project); + // Clone the queues, to prevent events triggered by the database listener + // from overwriting the events that are supposed to be triggered in the shutdown hook. + $queueForEventsClone = new Event($queue); + $queueForFunctions = new Func($queue); + $queueForWebhooks = new Webhook($queue); + $queueForRealtime = new Realtime(); + $dbForProject - ->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $databaseListener($event, $document, $project, $queueForUsage, $dbForProject)) - ->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $databaseListener($event, $document, $project, $queueForUsage, $dbForProject)); + ->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForUsage)) + ->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForUsage)) + ->on(Database::EVENT_DOCUMENT_CREATE, 'create-trigger-events', fn ($event, $document) => $eventDatabaseListener( + $document, + $response, + $queueForEventsClone->from($queueForEvents), + $queueForFunctions->from($queueForEvents), + $queueForWebhooks->from($queueForEvents), + $queueForRealtime->from($queueForEvents) + )); $useCache = $route->getLabel('cache', false); if ($useCache) { @@ -508,7 +582,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 +590,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') ; } @@ -591,11 +665,11 @@ App::shutdown() ->inject('queueForDatabase') ->inject('queueForBuilds') ->inject('queueForMessaging') - ->inject('dbForProject') ->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('queueForWebhooks') + ->inject('queueForRealtime') + ->inject('dbForProject') + ->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, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject) use ($parseLabel) { $responsePayload = $response->getPayload(); @@ -604,54 +678,18 @@ App::shutdown() $queueForEvents->setPayload($responsePayload); } - /** - * Trigger functions. - */ - if (!$queueForEvents->isPaused()) { - $queueForFunctions - ->from($queueForEvents) - ->trigger(); - } - /** - * Trigger webhooks. - */ - $queueForEvents - ->setClass(Event::WEBHOOK_CLASS_NAME) - ->setQueue(Event::WEBHOOK_QUEUE_NAME) + $queueForWebhooks + ->from($queueForEvents) + ->trigger(); + + $queueForFunctions + ->from($queueForEvents) ->trigger(); - /** - * Trigger realtime. - */ if ($project->getId() !== 'console') { - $allEvents = Event::generateEvents($queueForEvents->getEvent(), $queueForEvents->getParams()); - $payload = new Document($queueForEvents->getPayload()); - - $db = $queueForEvents->getContext('database'); - $collection = $queueForEvents->getContext('collection'); - $bucket = $queueForEvents->getContext('bucket'); - - $target = Realtime::fromPayload( - // Pass first, most verbose event pattern - event: $allEvents[0], - payload: $payload, - project: $project, - database: $db, - collection: $collection, - bucket: $bucket, - ); - - Realtime::send( - projectId: $target['projectId'] ?? $project->getId(), - payload: $queueForEvents->getRealtimePayload(), - events: $allEvents, - channels: $target['channels'], - roles: $target['roles'], - options: [ - 'permissionsChanged' => $target['permissionsChanged'], - 'userId' => $queueForEvents->getParam('userId') - ] - ); + $queueForRealtime + ->from($queueForEvents) + ->trigger(); } } @@ -751,8 +789,6 @@ App::shutdown() } } - - if ($project->getId() !== 'console') { if (!Auth::isPrivilegedUser(Authorization::getRoles())) { $fileSize = 0; @@ -771,33 +807,6 @@ App::shutdown() ->setProject($project) ->trigger(); } - - /** - * Update project last activity - */ - if (!$project->isEmpty() && $project->getId() !== 'console') { - $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)); - } - } - - /** - * Update user last activity - */ - if (!$user->isEmpty()) { - $accessedAt = $user->getAttribute('accessedAt', ''); - if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCESS)) > $accessedAt) { - $user->setAttribute('accessedAt', DateTime::now()); - - if (APP_MODE_ADMIN !== $mode) { - $dbForProject->updateDocument('users', $user->getId(), $user); - } else { - $dbForConsole->updateDocument('users', $user->getId(), $user); - } - } - } }); App::init() diff --git a/app/http.php b/app/http.php index bec772c770..74b829c384 100644 --- a/app/http.php +++ b/app/http.php @@ -9,16 +9,21 @@ use Swoole\Http\Request as SwooleRequest; use Swoole\Http\Response as SwooleResponse; use Swoole\Http\Server; use Swoole\Process; +use Swoole\Table; use Utopia\Abuse\Adapters\Database\TimeLimit; use Utopia\App; use Utopia\Audit\Audit; +use Utopia\Cache\Cache; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Database\Database; +use Utopia\Database\DateTime; use Utopia\Database\Document; +use Utopia\Database\Exception\Duplicate; 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\Logger\Log; use Utopia\Logger\Log\User; @@ -26,6 +31,12 @@ use Utopia\Pools\Group; use Utopia\Swoole\Files; use Utopia\System\System; +const DOMAIN_SYNC_TIMER = 30; // 30 seconds + +$domains = new Table(1_000_000); // 1 million rows +$domains->column('value', Table::TYPE_INT, 1); +$domains->create(); + $http = new Server( host: "0.0.0.0", port: System::getEnv('PORT', 80), @@ -33,16 +44,17 @@ $http = new Server( ); $payloadSize = 12 * (1024 * 1024); // 12MB - adding slight buffer for headers and other data that might be sent with the payload - update later with valid testing -$workerNumber = swoole_cpu_num() * intval(System::getEnv('_APP_WORKER_PER_CORE', 6)); +$totalWorkers = swoole_cpu_num() * intval(System::getEnv('_APP_WORKER_PER_CORE', 6)); $http ->set([ - 'worker_num' => $workerNumber, + 'worker_num' => $totalWorkers, + 'dispatch_func' => 'dispatch', 'open_http2_protocol' => true, - 'http_compression' => true, - 'http_compression_level' => 6, + 'http_compression' => false, 'package_max_length' => $payloadSize, 'buffer_output_size' => $payloadSize, + 'task_worker_num' => 1, // required for the task to fetch domains background ]); $http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) { @@ -57,6 +69,93 @@ $http->on(Constant::EVENT_AFTER_RELOAD, function ($server, $workerId) { Console::success('Reload completed...'); }); +/** + * Assigns HTTP requests to worker threads by analyzing its payload/content. + * + * Routes requests as 'safe' or 'risky' based on specific content patterns (like POST actions or certain domains) + * to optimize load distribution between the workers. Utilizes `$safeThreadsPercent` to manage risk by assigning + * riskier tasks to a dedicated worker subset. Prefers idle workers, with fallback to random selection if necessary. + * doc: https://openswoole.com/docs/modules/swoole-server/configuration#dispatch_func + * + * @param Server $server Swoole server instance. + * @param int $fd client ID + * @param int $type the type of data and its current state + * @param string|null $data Request content for categorization. + * @global int $totalThreads Total number of workers. + * @return int Chosen worker ID for the request. + */ +function dispatch(Server $server, int $fd, int $type, $data = null): int +{ + global $totalWorkers, $domains; + + // If data is not set we can send request to any worker + // first we try to pick idle worker, if not we randomly pick a worker + if ($data === null) { + for ($i = 0; $i < $totalWorkers; $i++) { + if ($server->getWorkerStatus($i) === SWOOLE_WORKER_IDLE) { + return $i; + } + } + return rand(0, $totalWorkers - 1); + } + + $riskyWorkersPercent = intval(System::getEnv('_APP_RISKY_WORKERS_PERCENT', 80)) / 100; // Decimal form 0 to 1 + + // Each worker has numeric ID, starting from 0 and incrementing + // From 0 to riskyWorkers, we consider safe workers + // From riskyWorkers to totalWorkers, we consider risky workers + $riskyWorkers = (int) floor($totalWorkers * $riskyWorkersPercent); // Absolute amount of risky workers + + $domain = ''; + // max up to 3 as first line has request details and second line has host + $lines = explode("\n", $data, 3); + $request = $lines[0]; + if (count($lines) > 1) { + $domain = trim(explode('Host: ', $lines[1])[1]); + } + + // Sync executions are considered risky + $risky = false; + if (str_starts_with($request, 'POST') && str_contains($request, '/executions')) { + $risky = true; + } elseif (str_ends_with($domain, System::getEnv('_APP_DOMAIN_FUNCTIONS'))) { + $risky = true; + } elseif ($domains->get(md5($domain), 'value') === 1) { + // executions request coming from custom domain + $risky = true; + } + + if ($risky) { + // If risky request, only consider risky workers + for ($j = $riskyWorkers; $j < $totalWorkers; $j++) { + /** Reference https://openswoole.com/docs/modules/swoole-server-getWorkerStatus#description */ + if ($server->getWorkerStatus($j) === SWOOLE_WORKER_IDLE) { + // If idle worker found, give to him + return $j; + } + } + + // If no idle workers, give to random risky worker + $worker = rand($riskyWorkers, $totalWorkers - 1); + Console::warning("swoole_dispatch: Risky branch: did not find a idle worker, picking random worker {$worker}"); + return $worker; + } + + // If safe request, give to any idle worker + // Its fine to pick risky worker here, because it's idle. Idle is never actually risky + for ($i = 0; $i < $totalWorkers; $i++) { + if ($server->getWorkerStatus($i) === SWOOLE_WORKER_IDLE) { + return $i; + } + } + + // If no idle worker found, give to random safe worker + // We avoid risky workers here, as it could be in work - not idle. Thats exactly when they are risky. + $worker = rand(0, $riskyWorkers - 1); + Console::warning("swoole_dispatch: Non-risky branch: did not find a idle worker, picking random worker {$worker}"); + return $worker; +} + include __DIR__ . '/controllers/general.php'; $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $register) { @@ -75,8 +174,8 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg do { try { $attempts++; - $dbForConsole = $app->getResource('dbForConsole'); - /** @var Utopia\Database\Database $dbForConsole */ + $dbForPlatform = $app->getResource('dbForPlatform'); + /** @var Utopia\Database\Database $dbForPlatform */ break; // leave the do-while if successful } catch (\Throwable $e) { Console::warning("Database not ready. Retrying connection ({$attempts})..."); @@ -90,19 +189,19 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg Console::success('[Setup] - Server database init started...'); try { - Console::success('[Setup] - Creating database: appwrite...'); - $dbForConsole->create(); - } catch (\Throwable $e) { + Console::success('[Setup] - Creating console database...'); + $dbForPlatform->create(); + } catch (Duplicate) { Console::success('[Setup] - Skip: metadata table already exists'); } - if ($dbForConsole->getCollection(Audit::COLLECTION)->isEmpty()) { - $audit = new Audit($dbForConsole); + if ($dbForPlatform->getCollection(Audit::COLLECTION)->isEmpty()) { + $audit = new Audit($dbForPlatform); $audit->setup(); } - if ($dbForConsole->getCollection(TimeLimit::COLLECTION)->isEmpty()) { - $adapter = new TimeLimit("", 0, 1, $dbForConsole); + if ($dbForPlatform->getCollection(TimeLimit::COLLECTION)->isEmpty()) { + $adapter = new TimeLimit("", 0, 1, $dbForPlatform); $adapter->setup(); } @@ -113,45 +212,21 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg if (($collection['$collection'] ?? '') !== Database::METADATA) { continue; } - if (!$dbForConsole->getCollection($key)->isEmpty()) { + if (!$dbForPlatform->getCollection($key)->isEmpty()) { continue; } - Console::success('[Setup] - Creating collection: ' . $collection['$id'] . '...'); + Console::success('[Setup] - Creating console collection: ' . $collection['$id'] . '...'); - $attributes = []; - $indexes = []; + $attributes = \array_map(fn ($attribute) => new Document($attribute), $collection['attributes']); + $indexes = \array_map(fn (array $index) => new Document($index), $collection['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); + $dbForPlatform->createCollection($key, $attributes, $indexes); } - if ($dbForConsole->getDocument('buckets', 'default')->isEmpty() && !$dbForConsole->exists($dbForConsole->getDatabase(), 'bucket_1')) { + if ($dbForPlatform->getDocument('buckets', 'default')->isEmpty() && !$dbForPlatform->exists($dbForPlatform->getDatabase(), 'bucket_1')) { Console::success('[Setup] - Creating default bucket...'); - $dbForConsole->createDocument('buckets', new Document([ + $dbForPlatform->createDocument('buckets', new Document([ '$id' => ID::custom('default'), '$collection' => ID::custom('buckets'), 'name' => 'Default', @@ -171,7 +246,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg 'search' => 'buckets Default', ])); - $bucket = $dbForConsole->getDocument('buckets', 'default'); + $bucket = $dbForPlatform->getDocument('buckets', 'default'); Console::success('[Setup] - Creating files collection for default bucket...'); $files = $collections['buckets']['files'] ?? []; @@ -179,34 +254,53 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg throw new Exception('Files collection is not configured.'); } - $attributes = []; - $indexes = []; + $attributes = \array_map(fn ($attribute) => new Document($attribute), $files['attributes']); + $indexes = \array_map(fn (array $index) => new Document($index), $files['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'] ?? '' - ]); + $dbForPlatform->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes); + } + + $projectCollections = $collections['projects']; + $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); + $sharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES_V1', '')); + $sharedTablesV2 = \array_diff($sharedTables, $sharedTablesV1); + + $cache = $app->getResource('cache'); + + foreach ($sharedTablesV2 as $hostname) { + $adapter = $pools + ->get($hostname) + ->pop() + ->getResource(); + + $dbForProject = (new Database($adapter, $cache)) + ->setDatabase('appwrite') + ->setSharedTables(true) + ->setTenant(null) + ->setNamespace(System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', '')); + + try { + Console::success('[Setup] - Creating project database: ' . $hostname . '...'); + $dbForProject->create(); + } catch (Duplicate) { + Console::success('[Setup] - Skip: metadata table already exists'); } - foreach ($files['indexes'] as $index) { - $indexes[] = new Document([ - '$id' => ID::custom($index['$id']), - 'type' => $index['type'], - 'attributes' => $index['attributes'], - 'lengths' => $index['lengths'], - 'orders' => $index['orders'], - ]); - } + foreach ($projectCollections as $key => $collection) { + if (($collection['$collection'] ?? '') !== Database::METADATA) { + continue; + } + if (!$dbForProject->getCollection($key)->isEmpty()) { + continue; + } - $dbForConsole->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes); + $attributes = \array_map(fn ($attribute) => new Document($attribute), $collection['attributes']); + $indexes = \array_map(fn (array $index) => new Document($index), $collection['indexes']); + + Console::success('[Setup] - Creating project collection: ' . $collection['$id'] . '...'); + + $dbForProject->createCollection($key, $attributes, $indexes); + } } $pools->reclaim(); @@ -217,6 +311,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg Console::success('Server started successfully (max payload is ' . number_format($payloadSize) . ' bytes)'); Console::info("Master pid {$http->master_pid}, manager pid {$http->manager_pid}"); + // Start the task that starts fetching custom domains + $http->task([], 0); + // listen ctrl + c Process::signal(2, function () use ($http) { Console::log('Stop by Ctrl+C'); @@ -224,7 +321,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg }); }); -$http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register) { +$http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register) { App::setResource('swooleRequest', fn () => $swooleRequest); App::setResource('swooleResponse', fn () => $swooleResponse); @@ -244,6 +341,8 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo } $app = new App('UTC'); + $app->setCompression(true); + $app->setCompressionMinSize(intval(System::getEnv('_APP_COMPRESSION_MIN_SIZE_BYTES', '1024'))); // 1KB $pools = $register->get('pools'); App::setResource('pools', fn () => $pools); @@ -271,10 +370,12 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo if (isset($user) && !$user->isEmpty()) { $log->setUser(new User($user->getId())); + } else { + $log->setUser(new User('guest-' . hash('sha256', $request->getIP()))); } $log->setNamespace("http"); - $log->setServer(\gethostname()); + $log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname())); $log->setVersion($version); $log->setType(Log::TYPE_ERROR); $log->setMessage($th->getMessage()); @@ -294,6 +395,7 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo $action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD"); $log->setAction($action); + $log->addTag('service', $action); $isProduction = System::getEnv('_APP_ENV', 'development') === 'production'; $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING); @@ -332,4 +434,59 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo } }); +// Fetch domains every `DOMAIN_SYNC_TIMER` seconds and update in the memory +$http->on('Task', function () use ($register, $domains) { + $lastSyncUpdate = null; + $pools = $register->get('pools'); + App::setResource('pools', fn () => $pools); + $app = new App('UTC'); + + /** @var Utopia\Database\Database $dbForPlatform */ + $dbForPlatform = $app->getResource('dbForPlatform'); + + Console::loop(function () use ($dbForPlatform, $domains, &$lastSyncUpdate) { + try { + $time = DateTime::now(); + $limit = 1000; + $sum = $limit; + $latestDocument = null; + + while ($sum === $limit) { + $queries = [Query::limit($limit)]; + if ($latestDocument !== null) { + $queries[] = Query::cursorAfter($latestDocument); + } + if ($lastSyncUpdate != null) { + $queries[] = Query::greaterThanEqual('$updatedAt', $lastSyncUpdate); + } + $queries[] = Query::equal('resourceType', ['function']); + $results = []; + try { + $results = Authorization::skip(fn () => $dbForPlatform->find('rules', $queries)); + } catch (Throwable $th) { + Console::error($th->getMessage()); + } + + $sum = count($results); + foreach ($results as $document) { + $domain = $document->getAttribute('domain'); + if (str_ends_with($domain, System::getEnv('_APP_DOMAIN_FUNCTIONS'))) { + continue; + } + $domains->set(md5($domain), ['value' => 1]); + } + $latestDocument = !empty(array_key_last($results)) ? $results[array_key_last($results)] : null; + } + $lastSyncUpdate = $time; + if ($sum > 0) { + Console::log("Sync domains tick: {$sum} domains were updated"); + } + } catch (Throwable $th) { + Console::error($th->getMessage()); + } + }, DOMAIN_SYNC_TIMER, 0, function ($error) { + Console::error($error); + }); +}); + $http->start(); diff --git a/app/init.php b/app/init.php index c2777f36f1..109849fdce 100644 --- a/app/init.php +++ b/app/init.php @@ -31,7 +31,9 @@ use Appwrite\Event\Func; use Appwrite\Event\Mail; use Appwrite\Event\Messaging; use Appwrite\Event\Migration; +use Appwrite\Event\Realtime; use Appwrite\Event\Usage; +use Appwrite\Event\Webhook; use Appwrite\Extend\Exception; use Appwrite\Functions\Specification; use Appwrite\GraphQL\Promises\Adapter\Swoole; @@ -40,6 +42,7 @@ use Appwrite\Hooks\Hooks; use Appwrite\Network\Validator\Email; use Appwrite\Network\Validator\Origin; use Appwrite\OpenSSL\OpenSSL; +use Appwrite\PubSub\Adapter\Redis as PubSub; use Appwrite\URL\URL as AppwriteURL; use Appwrite\Utopia\Request; use MaxMind\Db\Reader; @@ -120,7 +123,7 @@ const APP_USER_ACCESS = 24 * 60 * 60; // 24 hours const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours const APP_CACHE_BUSTER = 4318; -const APP_VERSION_STABLE = '1.6.0'; +const APP_VERSION_STABLE = '1.6.1'; const APP_DATABASE_ATTRIBUTE_EMAIL = 'email'; const APP_DATABASE_ATTRIBUTE_ENUM = 'enum'; const APP_DATABASE_ATTRIBUTE_IP = 'ip'; @@ -130,6 +133,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_MAX_VALUES = 500; const APP_STORAGE_UPLOADS = '/storage/uploads'; const APP_STORAGE_FUNCTIONS = '/storage/functions'; const APP_STORAGE_BUILDS = '/storage/builds'; @@ -149,7 +153,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; const APP_PLATFORM_SERVER = 'server'; @@ -285,6 +289,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)); @@ -959,7 +974,10 @@ $register->set('pools', function () { $adapter->setDatabase($dsn->getPath()); break; case 'pubsub': - $adapter = $resource(); + $adapter = match ($dsn->getScheme()) { + 'redis' => new PubSub($resource()), + default => null + }; break; case 'queue': $adapter = match ($dsn->getScheme()) { @@ -1122,6 +1140,12 @@ App::setResource('queueForDeletes', function (Connection $queue) { App::setResource('queueForEvents', function (Connection $queue) { return new Event($queue); }, ['queue']); +App::setResource('queueForWebhooks', function (Connection $queue) { + return new Webhook($queue); +}, ['queue']); +App::setResource('queueForRealtime', function () { + return new Realtime(); +}, []); App::setResource('queueForAudits', function (Connection $queue) { return new Audit($queue); }, ['queue']); @@ -1189,12 +1213,12 @@ App::setResource('clients', function ($request, $console, $project) { return \array_unique($clients); }, ['request', 'console', 'project']); -App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForConsole) { +App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform) { /** @var Appwrite\Utopia\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $project */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Utopia\Database\Database $dbForConsole */ + /** @var Utopia\Database\Database $dbForPlatform */ /** @var string $mode */ Authorization::setDefaultStatus(true); @@ -1243,13 +1267,13 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons $user = new Document([]); } else { if ($project->getId() === 'console') { - $user = $dbForConsole->getDocument('users', Auth::$unique); + $user = $dbForPlatform->getDocument('users', Auth::$unique); } else { $user = $dbForProject->getDocument('users', Auth::$unique); } } } else { - $user = $dbForConsole->getDocument('users', Auth::$unique); + $user = $dbForPlatform->getDocument('users', Auth::$unique); } if ( @@ -1292,14 +1316,14 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons } $dbForProject->setMetadata('user', $user->getId()); - $dbForConsole->setMetadata('user', $user->getId()); + $dbForPlatform->setMetadata('user', $user->getId()); return $user; -}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForConsole']); +}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform']); -App::setResource('project', function ($dbForConsole, $request, $console) { +App::setResource('project', function ($dbForPlatform, $request, $console) { /** @var Appwrite\Utopia\Request $request */ - /** @var Utopia\Database\Database $dbForConsole */ + /** @var Utopia\Database\Database $dbForPlatform */ /** @var Utopia\Database\Document $console */ $projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', '')); @@ -1308,10 +1332,10 @@ App::setResource('project', function ($dbForConsole, $request, $console) { return $console; } - $project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId)); + $project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId)); return $project; -}, ['dbForConsole', 'request', 'console']); +}, ['dbForPlatform', 'request', 'console']); App::setResource('session', function (Document $user) { if ($user->isEmpty()) { @@ -1376,9 +1400,9 @@ App::setResource('console', function () { ]); }, []); -App::setResource('dbForProject', function (Group $pools, Database $dbForConsole, Cache $cache, Document $project) { +App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project) { if ($project->isEmpty() || $project->getId() === 'console') { - return $dbForConsole; + return $dbForPlatform; } try { @@ -1398,16 +1422,12 @@ 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_MAX_VALUES); - try { - $dsn = new DSN($project->getAttribute('database')); - } catch (\InvalidArgumentException) { - // TODO: Temporary until all projects are using shared tables - $dsn = new DSN('mysql://' . $project->getAttribute('database')); - } + $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + if (\in_array($dsn->getHost(), $sharedTables)) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -1420,9 +1440,9 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForConsole, } return $database; -}, ['pools', 'dbForConsole', 'cache', 'project']); +}, ['pools', 'dbForPlatform', 'cache', 'project']); -App::setResource('dbForConsole', function (Group $pools, Cache $cache) { +App::setResource('dbForPlatform', function (Group $pools, Cache $cache) { $dbAdapter = $pools ->get('console') ->pop() @@ -1434,17 +1454,18 @@ 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_MAX_VALUES); return $database; }, ['pools', 'cache']); -App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) { +App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) { $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools - return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases) { + return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) { if ($project->isEmpty() || $project->getId() === 'console') { - return $dbForConsole; + return $dbForPlatform; } try { @@ -1458,9 +1479,12 @@ 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_MAX_VALUES); - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); + + if (\in_array($dsn->getHost(), $sharedTables)) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -1490,7 +1514,7 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, return $database; }; -}, ['pools', 'dbForConsole', 'cache']); +}, ['pools', 'dbForPlatform', 'cache']); App::setResource('cache', function (Group $pools) { $list = Config::getParam('pools-cache', []); @@ -1767,12 +1791,12 @@ 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) { +App::setResource('team', function (Document $project, Database $dbForPlatform, App $utopia, Request $request) { $teamInternalId = ''; if ($project->getId() !== 'console') { $teamInternalId = $project->getAttribute('teamInternalId', ''); @@ -1782,23 +1806,36 @@ App::setResource('team', function (Document $project, Database $dbForConsole, Ap if (str_starts_with($path, '/v1/projects/:projectId')) { $uri = $request->getURI(); $pid = explode('/', $uri)[3]; - $p = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $pid)); + $p = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $pid)); $teamInternalId = $p->getAttribute('teamInternalId', ''); } elseif ($path === '/v1/projects') { $teamId = $request->getParam('teamId', ''); - $team = Authorization::skip(fn () => $dbForConsole->getDocument('teams', $teamId)); + $team = Authorization::skip(fn () => $dbForPlatform->getDocument('teams', $teamId)); return $team; } } - $team = Authorization::skip(function () use ($dbForConsole, $teamInternalId) { - return $dbForConsole->findOne('teams', [ + $team = Authorization::skip(function () use ($dbForPlatform, $teamInternalId) { + return $dbForPlatform->findOne('teams', [ Query::equal('$internalId', [$teamInternalId]), ]); }); - if (!$team) { - $team = new Document([]); - } return $team; -}, ['project', 'dbForConsole', 'utopia', 'request']); +}, ['project', 'dbForPlatform', 'utopia', 'request']); + +App::setResource( + 'isResourceBlocked', + fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false +); + +App::setResource('previewHostname', function (Request $request) { + if (App::isDevelopment()) { + $host = $request->getQuery('appwrite-hostname') ?? ''; + if (!empty($host)) { + return $host; + } + } + + return ''; +}, ['request']); diff --git a/app/realtime.php b/app/realtime.php index 1b59eb3bc7..54fd1e05f7 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -29,6 +29,7 @@ use Utopia\Database\Validator\Authorization; use Utopia\DSN\DSN; use Utopia\Logger\Log; use Utopia\System\System; +use Utopia\Telemetry\Adapter\None as NoTelemetry; use Utopia\WebSocket\Adapter; use Utopia\WebSocket\Server; @@ -92,7 +93,9 @@ if (!function_exists('getProjectDB')) { $database = new Database($adapter, getCache()); - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); + + if (\in_array($dsn->getHost(), $sharedTables)) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -142,6 +145,13 @@ if (!function_exists('getRealtime')) { } } +if (!function_exists('getTelemetry')) { + function getTelemetry(int $workerId): Utopia\Telemetry\Adapter + { + return new NoTelemetry(); + } +} + $realtime = getRealtime(); /** @@ -174,7 +184,7 @@ $logError = function (Throwable $error, string $action) use ($register) { $log = new Log(); $log->setNamespace("realtime"); - $log->setServer(gethostname()); + $log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname())); $log->setVersion($version); $log->setType(Log::TYPE_ERROR); $log->setMessage($error->getMessage()); @@ -274,6 +284,12 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $realtime, $logError) { Console::success('Worker ' . $workerId . ' started successfully'); + $telemetry = getTelemetry($workerId); + $register->set('telemetry', fn () => $telemetry); + $register->set('telemetry.connectionCounter', fn () => $telemetry->createUpDownCounter('realtime.server.open_connections')); + $register->set('telemetry.connectionCreatedCounter', fn () => $telemetry->createCounter('realtime.server.connection.created')); + $register->set('telemetry.messageSentCounter', fn () => $telemetry->createCounter('realtime.server.message.sent')); + $attempts = 0; $start = time(); @@ -365,17 +381,16 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, } $start = time(); - $redis = $register->get('pools')->get('pubsub')->pop()->getResource(); /** @var Redis $redis */ - $redis->setOption(Redis::OPT_READ_TIMEOUT, -1); - - if ($redis->ping(true)) { + /** @var \Appwrite\PubSub\Adapter $pubsub */ + $pubsub = $register->get('pools')->get('pubsub')->pop()->getResource(); + if ($pubsub->ping(true)) { $attempts = 0; Console::success('Pub/sub connection established (worker: ' . $workerId . ')'); } else { Console::error('Pub/sub failed (worker: ' . $workerId . ')'); } - $redis->subscribe(['realtime'], function (Redis $redis, string $channel, string $payload) use ($server, $workerId, $stats, $register, $realtime) { + $pubsub->subscribe(['realtime'], function (mixed $redis, string $channel, string $payload) use ($server, $workerId, $stats, $register, $realtime) { $event = json_decode($payload, true); if ($event['permissionsChanged'] && isset($event['userId'])) { @@ -417,6 +432,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, ); if (($num = count($receivers)) > 0) { + $register->get('telemetry.messageSentCounter')->add($num); $stats->incr($event['project'], 'messages', $num); } }); @@ -520,6 +536,9 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, ] ])); + $register->get('telemetry.connectionCounter')->add(1); + $register->get('telemetry.connectionCreatedCounter')->add(1); + $stats->set($project->getId(), [ 'projectId' => $project->getId(), 'teamId' => $project->getAttribute('teamId') @@ -593,9 +612,12 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re } switch ($message['type']) { - /** - * This type is used to authenticate. - */ + case 'ping': + $server->send([$connection], json_encode([ + 'type' => 'pong' + ])); + + break; case 'authentication': if (!array_key_exists('session', $message['data'])) { throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Payload is not valid.'); @@ -653,9 +675,10 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re } }); -$server->onClose(function (int $connection) use ($realtime, $stats) { +$server->onClose(function (int $connection) use ($realtime, $stats, $register) { if (array_key_exists($connection, $realtime->connections)) { $stats->decr($realtime->connections[$connection]['projectId'], 'connectionsTotal'); + $register->get('telemetry.connectionCounter')->add(-1); } $realtime->unsubscribe($connection); diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml index ad35135a6f..ad6d883c4c 100644 --- a/app/views/install/compose.phtml +++ b/app/views/install/compose.phtml @@ -73,6 +73,7 @@ $image = $this->getParam('image', ''); - _APP_ENV - _APP_WORKER_PER_CORE - _APP_LOCALE + - _APP_COMPRESSION_MIN_SIZE_BYTES - _APP_CONSOLE_WHITELIST_ROOT - _APP_CONSOLE_WHITELIST_EMAILS - _APP_CONSOLE_SESSION_ALERTS @@ -166,7 +167,7 @@ $image = $this->getParam('image', ''); appwrite-console: <<: *x-logging container_name: appwrite-console - image: /console:5.0.12 + image: /console:5.2.27 restart: unless-stopped networks: - appwrite diff --git a/app/worker.php b/app/worker.php index 2d59259284..2c7d8acb22 100644 --- a/app/worker.php +++ b/app/worker.php @@ -2,6 +2,7 @@ require_once __DIR__ . '/init.php'; +use Appwrite\Certificates\LetsEncrypt; use Appwrite\Event\Audit; use Appwrite\Event\Build; use Appwrite\Event\Certificate; @@ -16,7 +17,6 @@ use Appwrite\Event\Usage; use Appwrite\Event\UsageDump; use Appwrite\Platform\Appwrite; use Swoole\Runtime; -use Utopia\App; use Utopia\Cache\Adapter\Sharding; use Utopia\Cache\Cache; use Utopia\CLI\Console; @@ -41,7 +41,7 @@ Runtime::enableCoroutine(SWOOLE_HOOK_ALL); Server::setResource('register', fn () => $register); -Server::setResource('dbForConsole', function (Cache $cache, Registry $register) { +Server::setResource('dbForPlatform', function (Cache $cache, Registry $register) { $pools = $register->get('pools'); $database = $pools ->get('console') @@ -54,20 +54,20 @@ Server::setResource('dbForConsole', function (Cache $cache, Registry $register) return $adapter; }, ['cache', 'register']); -Server::setResource('project', function (Message $message, Database $dbForConsole) { +Server::setResource('project', function (Message $message, Database $dbForPlatform) { $payload = $message->getPayload() ?? []; $project = new Document($payload['project'] ?? []); - if ($project->getId() === 'console' || $project->isEmpty() || ! empty($project->getInternalId())) { + if ($project->getId() === 'console') { return $project; } - return $dbForConsole->getDocument('projects', $project->getId()); -}, ['message', 'dbForConsole']); + return $dbForPlatform->getDocument('projects', $project->getId()); +}, ['message', 'dbForPlatform']); -Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForConsole) { +Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForPlatform) { if ($project->isEmpty() || $project->getId() === 'console') { - return $dbForConsole; + return $dbForPlatform; } $pools = $register->get('pools'); @@ -93,7 +93,9 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register, $dsn = new DSN('mysql://' . $project->getAttribute('database')); } - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); + + if (\in_array($dsn->getHost(), $sharedTables)) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -106,14 +108,14 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register, } return $database; -}, ['cache', 'register', 'message', 'project', 'dbForConsole']); +}, ['cache', 'register', 'message', 'project', 'dbForPlatform']); -Server::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) { +Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) { $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools - return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases): Database { + return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases): Database { if ($project->isEmpty() || $project->getId() === 'console') { - return $dbForConsole; + return $dbForPlatform; } try { @@ -126,7 +128,9 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForConso if (isset($databases[$dsn->getHost()])) { $database = $databases[$dsn->getHost()]; - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); + + if (\in_array($dsn->getHost(), $sharedTables)) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -150,7 +154,9 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForConso $databases[$dsn->getHost()] = $database; - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); + + if (\in_array($dsn->getHost(), $sharedTables)) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -164,7 +170,7 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForConso return $database; }; -}, ['pools', 'dbForConsole', 'cache']); +}, ['pools', 'dbForPlatform', 'cache']); Server::setResource('abuseRetention', function () { return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400)); @@ -272,6 +278,64 @@ 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 +); + +Server::setResource('certificates', function () { + $email = System::getEnv('_APP_EMAIL_CERTIFICATES', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS')); + if (empty($email)) { + throw new Exception('You must set a valid security email address (_APP_EMAIL_CERTIFICATES) to issue a LetsEncrypt SSL certificate.'); + } + + return new LetsEncrypt($email); +}); + +Server::setResource('logError', function (Registry $register, Document $project) { + return function (Throwable $error, string $namespace, string $action, ?array $extras) use ($register, $project) { + $logger = $register->get('logger'); + + if ($logger) { + $version = System::getEnv('_APP_VERSION', 'UNKNOWN'); + + $log = new Log(); + $log->setNamespace($namespace); + $log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \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->addTag('projectId', $project->getId() ?? ''); + + $log->addExtra('file', $error->getFile()); + $log->addExtra('line', $error->getLine()); + $log->addExtra('trace', $error->getTraceAsString()); + + + foreach ($extras as $key => $value) { + $log->addExtra($key, $value); + } + + $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', 'project']); $pools = $register->get('pools'); $platform = new Appwrite(); @@ -330,7 +394,7 @@ $worker if ($logger) { $log->setNamespace("appwrite-worker"); - $log->setServer(\gethostname()); + $log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname())); $log->setVersion($version); $log->setType(Log::TYPE_ERROR); $log->setMessage($error->getMessage()); diff --git a/composer.json b/composer.json index c3c26368cd..59c9299877 100644 --- a/composer.json +++ b/composer.json @@ -1,100 +1,106 @@ { + "name": "appwrite/server-ce", "description": "End to end backend server for frontend and mobile apps.", "type": "project", "license": "BSD-3-Clause", "authors": [ - { - "name": "Eldad Fux", - "email": "eldad@appwrite.io" - } + { + "name": "Eldad Fux", + "email": "eldad@appwrite.io" + } ], "scripts": { - "test": "vendor/bin/phpunit", - "lint": "vendor/bin/pint --test", - "format": "vendor/bin/pint", - "bench": "vendor/bin/phpbench run --report=benchmark" + "test": "vendor/bin/phpunit", + "lint": "vendor/bin/pint --test", + "format": "vendor/bin/pint", + "bench": "vendor/bin/phpbench run --report=benchmark" }, "autoload": { - "psr-4": { - "Appwrite\\": "src/Appwrite", - "Executor\\": "src/Executor" - } + "psr-4": { + "Appwrite\\": "src/Appwrite", + "Executor\\": "src/Executor" + } }, "autoload-dev": { - "psr-4": { - "Tests\\E2E\\": "tests/e2e", - "Tests\\Unit\\": "tests/unit", - "Appwrite\\Tests\\": "tests/extensions" - } + "psr-4": { + "Tests\\E2E\\": "tests/e2e", + "Tests\\Unit\\": "tests/unit", + "Appwrite\\Tests\\": "tests/extensions" + } }, "require": { - "php": ">=8.0.0", - "ext-curl": "*", - "ext-imagick": "*", - "ext-mbstring": "*", - "ext-json": "*", - "ext-yaml": "*", - "ext-dom": "*", - "ext-redis": "*", - "ext-swoole": "*", - "ext-pdo": "*", - "ext-openssl": "*", - "ext-zlib": "*", - "ext-sockets": "*", - "appwrite/php-runtimes": "0.16.*", - "appwrite/php-clamav": "2.0.*", - "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.15.*", - "utopia-php/config": "0.2.*", - "utopia-php/database": "0.53.6", - "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.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.7.*", - "utopia-php/registry": "0.5.*", - "utopia-php/storage": "0.18.*", - "utopia-php/swoole": "0.8.*", - "utopia-php/system": "0.9.*", - "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", - "chillerlan/php-qrcode": "4.3.4", - "adhocore/jwt": "1.1.2", - "spomky-labs/otphp": "^10.0", - "webonyx/graphql-php": "14.11.*", - "league/csv": "9.14.*" + "php": ">=8.0.0", + "ext-curl": "*", + "ext-imagick": "*", + "ext-mbstring": "*", + "ext-json": "*", + "ext-yaml": "*", + "ext-dom": "*", + "ext-redis": "*", + "ext-swoole": "*", + "ext-pdo": "*", + "ext-openssl": "*", + "ext-zlib": "*", + "ext-sockets": "*", + "appwrite/php-runtimes": "0.16.*", + "appwrite/php-clamav": "2.0.*", + "utopia-php/abuse": "0.43.*", + "utopia-php/analytics": "0.10.*", + "utopia-php/audit": "0.43.*", + "utopia-php/cache": "0.11.*", + "utopia-php/cli": "0.15.*", + "utopia-php/config": "0.2.*", + "utopia-php/database": "0.53.200", + "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.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.7.*", + "utopia-php/registry": "0.5.*", + "utopia-php/storage": "0.18.*", + "utopia-php/swoole": "0.8.*", + "utopia-php/system": "0.9.*", + "utopia-php/telemetry": "0.1.*", + "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", + "chillerlan/php-qrcode": "4.3.4", + "adhocore/jwt": "1.1.2", + "spomky-labs/otphp": "^10.0", + "webonyx/graphql-php": "14.11.*", + "league/csv": "9.14.*" }, "require-dev": { - "ext-fileinfo": "*", - "appwrite/sdk-generator": "dev-master", - "phpunit/phpunit": "9.5.20", - "swoole/ide-helper": "5.1.2", - "textalk/websocket": "1.5.7", - "laravel/pint": "^1.14", - "phpbench/phpbench": "^1.2" + "ext-fileinfo": "*", + "appwrite/sdk-generator": "0.39.*", + "phpunit/phpunit": "9.5.20", + "swoole/ide-helper": "5.1.2", + "textalk/websocket": "1.5.7", + "laravel/pint": "^1.14", + "phpbench/phpbench": "^1.2" }, "provide": { - "ext-phpiredis": "*" + "ext-phpiredis": "*" }, "config": { - "platform": { - "php": "8.3" - } + "platform": { + "php": "8.3" + }, + "allow-plugins": { + "php-http/discovery": false, + "tbachert/spi": false + } } -} +} \ No newline at end of file diff --git a/composer.lock b/composer.lock index 5558365940..4ffa74ece8 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": "865034db0a7ce59cae1c9f9b2c71d7b7", + "content-hash": "fae350df93342992edd8f639948e1570", "packages": [ { "name": "adhocore/jwt", @@ -157,16 +157,16 @@ }, { "name": "appwrite/php-runtimes", - "version": "0.16.2", + "version": "0.16.5", "source": { "type": "git", "url": "https://github.com/appwrite/runtimes.git", - "reference": "c33005e3eaaf2d427e9fd1077d5335e31f4d36f9" + "reference": "1e430646fdf847a7caf3c611dcf3d6d5a28c3fd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/runtimes/zipball/c33005e3eaaf2d427e9fd1077d5335e31f4d36f9", - "reference": "c33005e3eaaf2d427e9fd1077d5335e31f4d36f9", + "url": "https://api.github.com/repos/appwrite/runtimes/zipball/1e430646fdf847a7caf3c611dcf3d6d5a28c3fd9", + "reference": "1e430646fdf847a7caf3c611dcf3d6d5a28c3fd9", "shasum": "" }, "require": { @@ -206,22 +206,22 @@ ], "support": { "issues": "https://github.com/appwrite/runtimes/issues", - "source": "https://github.com/appwrite/runtimes/tree/0.16.2" + "source": "https://github.com/appwrite/runtimes/tree/0.16.5" }, - "time": "2024-10-09T15:02:52+00:00" + "time": "2024-11-25T15:17:06+00:00" }, { "name": "beberlei/assert", - "version": "v3.3.2", + "version": "v3.3.3", "source": { "type": "git", "url": "https://github.com/beberlei/assert.git", - "reference": "cb70015c04be1baee6f5f5c953703347c0ac1655" + "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/beberlei/assert/zipball/cb70015c04be1baee6f5f5c953703347c0ac1655", - "reference": "cb70015c04be1baee6f5f5c953703347c0ac1655", + "url": "https://api.github.com/repos/beberlei/assert/zipball/b5fd8eacd8915a1b627b8bfc027803f1939734dd", + "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd", "shasum": "" }, "require": { @@ -229,7 +229,7 @@ "ext-json": "*", "ext-mbstring": "*", "ext-simplexml": "*", - "php": "^7.0 || ^8.0" + "php": "^7.1 || ^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "*", @@ -273,9 +273,69 @@ ], "support": { "issues": "https://github.com/beberlei/assert/issues", - "source": "https://github.com/beberlei/assert/tree/v3.3.2" + "source": "https://github.com/beberlei/assert/tree/v3.3.3" }, - "time": "2021-12-16T21:41:27+00:00" + "time": "2024-07-15T13:18:35+00:00" + }, + { + "name": "brick/math", + "version": "0.12.1", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "5.16.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.12.1" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2023-11-29T23:19:16+00:00" }, { "name": "chillerlan/php-qrcode", @@ -422,6 +482,87 @@ ], "time": "2024-07-17T01:04:28+00:00" }, + { + "name": "composer/semver", + "version": "3.4.3", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-09-19T14:15:21+00:00" + }, { "name": "dragonmantank/cron-expression", "version": "v3.3.2", @@ -567,29 +708,73 @@ "time": "2024-05-03T06:31:11+00:00" }, { - "name": "jean85/pretty-package-versions", - "version": "2.0.6", + "name": "google/protobuf", + "version": "v4.29.1", "source": { "type": "git", - "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4" + "url": "https://github.com/protocolbuffers/protobuf-php.git", + "reference": "6042b5483f8029e42473faeb8ef75ba266278381" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/f9fdd29ad8e6d024f52678b570e5593759b550b4", - "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/6042b5483f8029e42473faeb8ef75ba266278381", + "reference": "6042b5483f8029e42473faeb8ef75ba266278381", "shasum": "" }, "require": { - "composer-runtime-api": "^2.0.0", - "php": "^7.1|^8.0" + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": ">=5.0.0" + }, + "suggest": { + "ext-bcmath": "Need to support JSON deserialization" + }, + "type": "library", + "autoload": { + "psr-4": { + "Google\\Protobuf\\": "src/Google/Protobuf", + "GPBMetadata\\Google\\Protobuf\\": "src/GPBMetadata/Google/Protobuf" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "proto library for PHP", + "homepage": "https://developers.google.com/protocol-buffers/", + "keywords": [ + "proto" + ], + "support": { + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.29.1" + }, + "time": "2024-12-03T22:07:45+00:00" + }, + { + "name": "jean85/pretty-package-versions", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", + "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.1.0", + "php": "^7.4|^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", "jean85/composer-provided-replaced-stub-package": "^1.0", "phpstan/phpstan": "^1.4", - "phpunit/phpunit": "^7.5|^8.5|^9.4", - "vimeo/psalm": "^4.3" + "phpunit/phpunit": "^7.5|^8.5|^9.6", + "vimeo/psalm": "^4.3 || ^5.0" }, "type": "library", "extra": { @@ -621,9 +806,9 @@ ], "support": { "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.6" + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.0" }, - "time": "2024-03-08T09:58:59+00:00" + "time": "2024-11-18T16:19:46+00:00" }, { "name": "league/csv", @@ -906,6 +1091,553 @@ }, "time": "2019-09-10T13:16:29+00:00" }, + { + "name": "nyholm/psr7", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7.git", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0", + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.9", + "php-http/message-factory": "^1.0", + "php-http/psr7-integration-tests": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", + "symfony/error-handler": "^4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "https://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7/issues", + "source": "https://github.com/Nyholm/psr7/tree/1.8.2" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2024-09-09T07:06:30+00:00" + }, + { + "name": "nyholm/psr7-server", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7-server.git", + "reference": "4335801d851f554ca43fa6e7d2602141538854dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7-server/zipball/4335801d851f554ca43fa6e7d2602141538854dc", + "reference": "4335801d851f554ca43fa6e7d2602141538854dc", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "require-dev": { + "nyholm/nsa": "^1.1", + "nyholm/psr7": "^1.3", + "phpunit/phpunit": "^7.0 || ^8.5 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Nyholm\\Psr7Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "Helper classes to handle PSR-7 server requests", + "homepage": "http://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7-server/issues", + "source": "https://github.com/Nyholm/psr7-server/tree/1.1.0" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2023-11-08T09:30:43+00:00" + }, + { + "name": "open-telemetry/api", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/api.git", + "reference": "04c85a1e41a3d59fa9bdc801a5de1df6624b95ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/04c85a1e41a3d59fa9bdc801a5de1df6624b95ed", + "reference": "04c85a1e41a3d59fa9bdc801a5de1df6624b95ed", + "shasum": "" + }, + "require": { + "open-telemetry/context": "^1.0", + "php": "^8.1", + "psr/log": "^1.1|^2.0|^3.0", + "symfony/polyfill-php82": "^1.26" + }, + "conflict": { + "open-telemetry/sdk": "<=1.0.8" + }, + "type": "library", + "extra": { + "spi": { + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" + ] + }, + "branch-alias": { + "dev-main": "1.1.x-dev" + } + }, + "autoload": { + "files": [ + "Trace/functions.php" + ], + "psr-4": { + "OpenTelemetry\\API\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "API for OpenTelemetry PHP.", + "keywords": [ + "Metrics", + "api", + "apm", + "logging", + "opentelemetry", + "otel", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2024-11-16T04:32:30+00:00" + }, + { + "name": "open-telemetry/context", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/context.git", + "reference": "0cba875ea1953435f78aec7f1d75afa87bdbf7f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/0cba875ea1953435f78aec7f1d75afa87bdbf7f3", + "reference": "0cba875ea1953435f78aec7f1d75afa87bdbf7f3", + "shasum": "" + }, + "require": { + "php": "^8.1", + "symfony/polyfill-php82": "^1.26" + }, + "suggest": { + "ext-ffi": "To allow context switching in Fibers" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.0.x-dev" + } + }, + "autoload": { + "files": [ + "fiber/initialize_fiber_handler.php" + ], + "psr-4": { + "OpenTelemetry\\Context\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "Context implementation for OpenTelemetry PHP.", + "keywords": [ + "Context", + "opentelemetry", + "otel" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2024-08-21T00:29:20+00:00" + }, + { + "name": "open-telemetry/exporter-otlp", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/exporter-otlp.git", + "reference": "9b6de12204f25f8ab9540b46d6e7b5151897ce18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/9b6de12204f25f8ab9540b46d6e7b5151897ce18", + "reference": "9b6de12204f25f8ab9540b46d6e7b5151897ce18", + "shasum": "" + }, + "require": { + "open-telemetry/api": "^1.0", + "open-telemetry/gen-otlp-protobuf": "^1.1", + "open-telemetry/sdk": "^1.0", + "php": "^8.1", + "php-http/discovery": "^1.14" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.0.x-dev" + } + }, + "autoload": { + "files": [ + "_register.php" + ], + "psr-4": { + "OpenTelemetry\\Contrib\\Otlp\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "OTLP exporter for OpenTelemetry.", + "keywords": [ + "Metrics", + "exporter", + "gRPC", + "http", + "opentelemetry", + "otel", + "otlp", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2024-04-30T18:28:30+00:00" + }, + { + "name": "open-telemetry/gen-otlp-protobuf", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/gen-otlp-protobuf.git", + "reference": "66c3b98e998a726691c92e6405a82e6e7b8b169d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/66c3b98e998a726691c92e6405a82e6e7b8b169d", + "reference": "66c3b98e998a726691c92e6405a82e6e7b8b169d", + "shasum": "" + }, + "require": { + "google/protobuf": "^3.22 || ^4.0", + "php": "^8.0" + }, + "suggest": { + "ext-protobuf": "For better performance, when dealing with the protobuf format" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opentelemetry\\Proto\\": "Opentelemetry/Proto/", + "GPBMetadata\\Opentelemetry\\": "GPBMetadata/Opentelemetry/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "PHP protobuf files for communication with OpenTelemetry OTLP collectors/servers.", + "keywords": [ + "Metrics", + "apm", + "gRPC", + "logging", + "opentelemetry", + "otel", + "otlp", + "protobuf", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2024-10-30T11:49:49+00:00" + }, + { + "name": "open-telemetry/sdk", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/sdk.git", + "reference": "fb0ff8d8279a3776bd604791e2531dd0cc147e8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/fb0ff8d8279a3776bd604791e2531dd0cc147e8b", + "reference": "fb0ff8d8279a3776bd604791e2531dd0cc147e8b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "nyholm/psr7-server": "^1.1", + "open-telemetry/api": "~1.0 || ~1.1", + "open-telemetry/context": "^1.0", + "open-telemetry/sem-conv": "^1.0", + "php": "^8.1", + "php-http/discovery": "^1.14", + "psr/http-client": "^1.0", + "psr/http-client-implementation": "^1.0", + "psr/http-factory-implementation": "^1.0", + "psr/http-message": "^1.0.1|^2.0", + "psr/log": "^1.1|^2.0|^3.0", + "ramsey/uuid": "^3.0 || ^4.0", + "symfony/polyfill-mbstring": "^1.23", + "symfony/polyfill-php82": "^1.26", + "tbachert/spi": "^1.0.1" + }, + "suggest": { + "ext-gmp": "To support unlimited number of synchronous metric readers", + "ext-mbstring": "To increase performance of string operations", + "open-telemetry/sdk-configuration": "File-based OpenTelemetry SDK configuration" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.0.x-dev" + }, + "spi": { + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" + ] + } + }, + "autoload": { + "files": [ + "Common/Util/functions.php", + "Logs/Exporter/_register.php", + "Metrics/MetricExporter/_register.php", + "Propagation/_register.php", + "Trace/SpanExporter/_register.php", + "Common/Dev/Compatibility/_load.php", + "_autoload.php" + ], + "psr-4": { + "OpenTelemetry\\SDK\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "SDK for OpenTelemetry PHP.", + "keywords": [ + "Metrics", + "apm", + "logging", + "opentelemetry", + "otel", + "sdk", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2024-10-18T21:01:35+00:00" + }, + { + "name": "open-telemetry/sem-conv", + "version": "1.27.1", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/sem-conv.git", + "reference": "1dba705fea74bc0718d04be26090e3697e56f4e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/1dba705fea74bc0718d04be26090e3697e56f4e6", + "reference": "1dba705fea74bc0718d04be26090e3697e56f4e6", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "OpenTelemetry\\SemConv\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "Semantic conventions for OpenTelemetry PHP.", + "keywords": [ + "Metrics", + "apm", + "logging", + "opentelemetry", + "otel", + "semantic conventions", + "semconv", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2024-08-28T09:20:31+00:00" + }, { "name": "paragonie/constant_time_encoding", "version": "v2.7.0", @@ -973,6 +1705,85 @@ }, "time": "2024-05-08T12:18:48+00:00" }, + { + "name": "php-http/discovery", + "version": "1.20.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/discovery.git", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "nyholm/psr7": "<1.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "*", + "psr/http-factory-implementation": "*", + "psr/http-message-implementation": "*" + }, + "require-dev": { + "composer/composer": "^1.0.2|^2.0", + "graham-campbell/phpspec-skip-example-extension": "^5.0", + "php-http/httplug": "^1.0 || ^2.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", + "sebastian/comparator": "^3.0.5 || ^4.0.8", + "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1" + }, + "type": "composer-plugin", + "extra": { + "class": "Http\\Discovery\\Composer\\Plugin", + "plugin-optional": true + }, + "autoload": { + "psr-4": { + "Http\\Discovery\\": "src/" + }, + "exclude-from-classmap": [ + "src/Composer/Plugin.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations", + "homepage": "http://php-http.org", + "keywords": [ + "adapter", + "client", + "discovery", + "factory", + "http", + "message", + "psr17", + "psr7" + ], + "support": { + "issues": "https://github.com/php-http/discovery/issues", + "source": "https://github.com/php-http/discovery/tree/1.20.0" + }, + "time": "2024-10-02T11:20:13+00:00" + }, { "name": "phpmailer/phpmailer", "version": "v6.9.1", @@ -1054,6 +1865,450 @@ ], "time": "2023-11-25T22:23:28+00:00" }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "ramsey/collection", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.28.3", + "fakerphp/faker": "^1.21", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^1.0", + "mockery/mockery": "^1.5", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpcsstandards/phpcsutils": "^1.0.0-rc1", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.18.4", + "ramsey/coding-standard": "^2.0.3", + "ramsey/conventional-commits": "^1.3", + "vimeo/psalm": "^5.4" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", + "type": "tidelift" + } + ], + "time": "2022-12-31T21:50:55+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.7.6", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "91039bc1faa45ba123c4328958e620d382ec7088" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", + "reference": "91039bc1faa45ba123c4328958e620d382ec7088", + "shasum": "" + }, + "require": { + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", + "ext-json": "*", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.10", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.8", + "ergebnis/composer-normalize": "^2.15", + "mockery/mockery": "^1.3", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.2", + "php-mock/php-mock-mockery": "^1.3", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpbench/phpbench": "^1.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^8.5 || ^9", + "ramsey/composer-repl": "^1.4", + "slevomat/coding-standard": "^8.4", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.9" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.7.6" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", + "type": "tidelift" + } + ], + "time": "2024-04-27T21:32:50+00:00" + }, { "name": "spomky-labs/otphp", "version": "v10.0.3", @@ -1088,9 +2343,9 @@ "type": "library", "extra": { "branch-alias": { - "v10.0": "10.0.x-dev", + "v8.3": "8.3.x-dev", "v9.0": "9.0.x-dev", - "v8.3": "8.3.x-dev" + "v10.0": "10.0.x-dev" } }, "autoload": { @@ -1129,6 +2384,246 @@ }, "time": "2022-03-17T08:00:35+00:00" }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/http-client", + "version": "v7.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "ff4df2b68d1c67abb9fef146e6540ea16b58d99e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/ff4df2b68d1c67abb9fef146e6540ea16b58d99e", + "reference": "ff4df2b68d1c67abb9fef146e6540ea16b58d99e", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "amphp/amp": "<2.5", + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.4" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/http-client": "^4.2.1|^5.0", + "amphp/http-tunnel": "^1.0|^2.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/amphp-http-client-meta": "^1.0|^2.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v7.2.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-07T08:50:44+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645", + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-07T08:49:48+00:00" + }, { "name": "symfony/polyfill-mbstring", "version": "v1.31.0", @@ -1155,8 +2650,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -1229,8 +2724,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -1289,6 +2784,217 @@ ], "time": "2024-09-09T11:45:10+00:00" }, + { + "name": "symfony/polyfill-php82", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php82.git", + "reference": "5d2ed36f7734637dacc025f179698031951b1692" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/5d2ed36f7734637dacc025f179698031951b1692", + "reference": "5d2ed36f7734637dacc025f179698031951b1692", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php82\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php82/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "tbachert/spi", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/Nevay/spi.git", + "reference": "2ddfaf815dafb45791a61b08170de8d583c16062" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nevay/spi/zipball/2ddfaf815dafb45791a61b08170de8d583c16062", + "reference": "2ddfaf815dafb45791a61b08170de8d583c16062", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "composer/semver": "^1.0 || ^2.0 || ^3.0", + "php": "^8.1" + }, + "require-dev": { + "composer/composer": "^2.0", + "infection/infection": "^0.27.9", + "phpunit/phpunit": "^10.5", + "psalm/phar": "^5.18" + }, + "type": "composer-plugin", + "extra": { + "class": "Nevay\\SPI\\Composer\\Plugin", + "branch-alias": { + "dev-main": "0.2.x-dev" + }, + "plugin-optional": true + }, + "autoload": { + "psr-4": { + "Nevay\\SPI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Service provider loading facility", + "keywords": [ + "service provider" + ], + "support": { + "issues": "https://github.com/Nevay/spi/issues", + "source": "https://github.com/Nevay/spi/tree/v1.0.2" + }, + "time": "2024-10-04T16:36:12+00:00" + }, { "name": "thecodingmachine/safe", "version": "v2.5.0", @@ -1430,16 +3136,16 @@ }, { "name": "utopia-php/abuse", - "version": "0.43.0", + "version": "0.43.2", "source": { "type": "git", "url": "https://github.com/utopia-php/abuse.git", - "reference": "6346a3b4c5177a43160035a7289e30fdfb0790d6" + "reference": "374536b86d8d39066960a7da161d444a099bbc56" }, "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/374536b86d8d39066960a7da161d444a099bbc56", + "reference": "374536b86d8d39066960a7da161d444a099bbc56", "shasum": "" }, "require": { @@ -1447,7 +3153,7 @@ "ext-pdo": "*", "ext-redis": "*", "php": ">=8.0", - "utopia-php/database": "0.53.*" + "utopia-php/database": "0.53.200" }, "require-dev": { "laravel/pint": "1.5.*", @@ -1475,9 +3181,9 @@ ], "support": { "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/0.43.0" + "source": "https://github.com/utopia-php/abuse/tree/0.43.2" }, - "time": "2024-08-30T05:17:23+00:00" + "time": "2024-12-12T19:43:24+00:00" }, { "name": "utopia-php/analytics", @@ -1527,16 +3233,16 @@ }, { "name": "utopia-php/audit", - "version": "0.43.0", + "version": "0.43.1", "source": { "type": "git", "url": "https://github.com/utopia-php/audit.git", - "reference": "cef22b5dc6a6d28fcd522f41c7bf7ded4a4dfd3e" + "reference": "04a47dd1f5f92e2d50e971a06bcc9e759325d277" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/audit/zipball/cef22b5dc6a6d28fcd522f41c7bf7ded4a4dfd3e", - "reference": "cef22b5dc6a6d28fcd522f41c7bf7ded4a4dfd3e", + "url": "https://api.github.com/repos/utopia-php/audit/zipball/04a47dd1f5f92e2d50e971a06bcc9e759325d277", + "reference": "04a47dd1f5f92e2d50e971a06bcc9e759325d277", "shasum": "" }, "require": { @@ -1568,22 +3274,22 @@ ], "support": { "issues": "https://github.com/utopia-php/audit/issues", - "source": "https://github.com/utopia-php/audit/tree/0.43.0" + "source": "https://github.com/utopia-php/audit/tree/0.43.1" }, - "time": "2024-08-30T05:17:36+00:00" + "time": "2024-10-23T04:27:59+00:00" }, { "name": "utopia-php/cache", - "version": "0.10.2", + "version": "0.11.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "b22c6eb6d308de246b023efd0fc9758aee8b8247" + "reference": "8ebcab5aac7606331cef69b0081f6c9eff2e58bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/b22c6eb6d308de246b023efd0fc9758aee8b8247", - "reference": "b22c6eb6d308de246b023efd0fc9758aee8b8247", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/8ebcab5aac7606331cef69b0081f6c9eff2e58bc", + "reference": "8ebcab5aac7606331cef69b0081f6c9eff2e58bc", "shasum": "" }, "require": { @@ -1594,7 +3300,7 @@ }, "require-dev": { "laravel/pint": "1.2.*", - "phpstan/phpstan": "1.9.x-dev", + "phpstan/phpstan": "^1.12", "phpunit/phpunit": "^9.3", "vimeo/psalm": "4.13.1" }, @@ -1618,9 +3324,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.10.2" + "source": "https://github.com/utopia-php/cache/tree/0.11.0" }, - "time": "2024-06-25T20:36:35+00:00" + "time": "2024-11-05T16:53:58+00:00" }, { "name": "utopia-php/cli", @@ -1671,6 +3377,52 @@ }, "time": "2024-10-04T13:55:36+00:00" }, + { + "name": "utopia-php/compression", + "version": "0.1.2", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/compression.git", + "reference": "6062f70596415f8d5de40a589367b0eb2a435f98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/compression/zipball/6062f70596415f8d5de40a589367b0eb2a435f98", + "reference": "6062f70596415f8d5de40a589367b0eb2a435f98", + "shasum": "" + }, + "require": { + "php": ">=8.0" + }, + "require-dev": { + "laravel/pint": "1.2.*", + "phpunit/phpunit": "^9.3", + "vimeo/psalm": "4.0.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Compression\\": "src/Compression" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A simple Compression library to handle file compression", + "keywords": [ + "compression", + "framework", + "php", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/compression/issues", + "source": "https://github.com/utopia-php/compression/tree/0.1.2" + }, + "time": "2024-11-08T14:59:54+00:00" + }, { "name": "utopia-php/config", "version": "0.2.2", @@ -1724,32 +3476,32 @@ }, { "name": "utopia-php/database", - "version": "0.53.6", + "version": "0.53.200", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "feddc8e808eaea9a11c65cca3f01683def422f52" + "reference": "570c63a3760d0e1404679ddfacd9484af40bd9fc" }, "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/570c63a3760d0e1404679ddfacd9484af40bd9fc", + "reference": "570c63a3760d0e1404679ddfacd9484af40bd9fc", "shasum": "" }, "require": { "ext-mbstring": "*", "ext-pdo": "*", - "php": ">=8.0", - "utopia-php/cache": "0.10.*", + "php": ">=8.3", + "utopia-php/cache": "0.11.*", "utopia-php/framework": "0.33.*", "utopia-php/mongo": "0.3.*" }, "require-dev": { "fakerphp/faker": "1.23.*", - "laravel/pint": "1.17.*", - "pcov/clobber": "2.0.*", - "phpstan/phpstan": "1.11.*", - "phpunit/phpunit": "9.6.*", + "laravel/pint": "1.*", + "pcov/clobber": "2.*", + "phpstan/phpstan": "1.*", + "phpunit/phpunit": "9.*", "rregeer/phpunit-coverage-check": "0.3.*", "swoole/ide-helper": "5.1.3", "utopia-php/cli": "0.14.*" @@ -1774,9 +3526,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.200" }, - "time": "2024-10-08T02:18:46+00:00" + "time": "2024-12-01T07:59:15+00:00" }, { "name": "utopia-php/domains", @@ -1926,20 +3678,22 @@ }, { "name": "utopia-php/framework", - "version": "0.33.8", + "version": "0.33.15", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "a7f577540a25cb90896fef2b64767bf8d700f3c5" + "reference": "83b0628900c2c53e8c3efbf069f3e13050295edc" }, "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/83b0628900c2c53e8c3efbf069f3e13050295edc", + "reference": "83b0628900c2c53e8c3efbf069f3e13050295edc", "shasum": "" }, "require": { - "php": ">=8.0" + "php": ">=8.1", + "utopia-php/compression": "0.1.*", + "utopia-php/telemetry": "0.1.*" }, "require-dev": { "laravel/pint": "^1.2", @@ -1965,9 +3719,9 @@ ], "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/0.33.15" }, - "time": "2024-08-15T14:10:09+00:00" + "time": "2024-12-10T13:07:04+00:00" }, { "name": "utopia-php/image", @@ -2124,16 +3878,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,22 +3923,22 @@ ], "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", - "version": "0.6.8", + "version": "0.6.13", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "c3740de52c1b616aa7f054d0fadb9207895b5279" + "reference": "68d9b0a9477755afcda607e7e8109785cae17a13" }, "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/68d9b0a9477755afcda607e7e8109785cae17a13", + "reference": "68d9b0a9477755afcda607e7e8109785cae17a13", "shasum": "" }, "require": { @@ -2225,9 +3979,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.13" }, - "time": "2024-10-10T08:09:19+00:00" + "time": "2024-11-26T13:57:53+00:00" }, { "name": "utopia-php/mongo", @@ -2341,16 +4095,16 @@ }, { "name": "utopia-php/platform", - "version": "0.7.0", + "version": "0.7.1", "source": { "type": "git", "url": "https://github.com/utopia-php/platform.git", - "reference": "beeea0f2c9bce14a6869fc5c87a1047cdecb5c52" + "reference": "3433a0f1a54988f2a59c735f507745cb2c24638a" }, "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/3433a0f1a54988f2a59c735f507745cb2c24638a", + "reference": "3433a0f1a54988f2a59c735f507745cb2c24638a", "shasum": "" }, "require": { @@ -2385,9 +4139,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.7.1" }, - "time": "2024-05-08T17:00:55+00:00" + "time": "2024-10-22T10:27:49+00:00" }, { "name": "utopia-php/pools", @@ -2495,22 +4249,23 @@ }, { "name": "utopia-php/queue", - "version": "0.7.0", + "version": "0.7.3", "source": { "type": "git", "url": "https://github.com/utopia-php/queue.git", - "reference": "917565256eb94bcab7246f7a746b1a486813761b" + "reference": "16074a98ee7d6212bc1228de200e13db470c098a" }, "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/16074a98ee7d6212bc1228de200e13db470c098a", + "reference": "16074a98ee7d6212bc1228de200e13db470c098a", "shasum": "" }, "require": { - "php": ">=8.0", + "php": ">=8.1", "utopia-php/cli": "0.15.*", - "utopia-php/framework": "0.*.*" + "utopia-php/framework": "0.*.*", + "utopia-php/telemetry": "0.1.*" }, "require-dev": { "laravel/pint": "^0.2.3", @@ -2550,9 +4305,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.7.3" }, - "time": "2024-01-17T19:00:43+00:00" + "time": "2024-11-13T12:47:48+00:00" }, { "name": "utopia-php/registry", @@ -2608,16 +4363,16 @@ }, { "name": "utopia-php/storage", - "version": "0.18.5", + "version": "0.18.8", "source": { "type": "git", "url": "https://github.com/utopia-php/storage.git", - "reference": "7d355c5e3ccc8ecebc0266f8ddd30088a43be919" + "reference": "84737afa634e6a833fc4f8b0c967553234d3f215" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/storage/zipball/7d355c5e3ccc8ecebc0266f8ddd30088a43be919", - "reference": "7d355c5e3ccc8ecebc0266f8ddd30088a43be919", + "url": "https://api.github.com/repos/utopia-php/storage/zipball/84737afa634e6a833fc4f8b0c967553234d3f215", + "reference": "84737afa634e6a833fc4f8b0c967553234d3f215", "shasum": "" }, "require": { @@ -2657,9 +4412,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.18.8" }, - "time": "2024-09-04T08:57:27+00:00" + "time": "2024-12-04T08:30:35+00:00" }, { "name": "utopia-php/swoole", @@ -2769,23 +4524,73 @@ "time": "2024-10-09T14:44:01+00:00" }, { - "name": "utopia-php/vcs", - "version": "0.8.2", + "name": "utopia-php/telemetry", + "version": "0.1.0", "source": { "type": "git", - "url": "https://github.com/utopia-php/vcs.git", - "reference": "eb9b7eade1a46a4f660e0d5a6304f7fa26ec9d18" + "url": "https://github.com/utopia-php/telemetry.git", + "reference": "d35f2f0632f4ee0be63fb7ace6a94a6adda71a80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/vcs/zipball/eb9b7eade1a46a4f660e0d5a6304f7fa26ec9d18", - "reference": "eb9b7eade1a46a4f660e0d5a6304f7fa26ec9d18", + "url": "https://api.github.com/repos/utopia-php/telemetry/zipball/d35f2f0632f4ee0be63fb7ace6a94a6adda71a80", + "reference": "d35f2f0632f4ee0be63fb7ace6a94a6adda71a80", + "shasum": "" + }, + "require": { + "ext-opentelemetry": "*", + "ext-protobuf": "*", + "nyholm/psr7": "^1.8", + "open-telemetry/exporter-otlp": "^1.1", + "open-telemetry/sdk": "^1.1", + "php": ">=8.0", + "symfony/http-client": "^7.1" + }, + "require-dev": { + "laravel/pint": "^1.2", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.25" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "keywords": [ + "framework", + "php", + "upf" + ], + "support": { + "issues": "https://github.com/utopia-php/telemetry/issues", + "source": "https://github.com/utopia-php/telemetry/tree/0.1.0" + }, + "time": "2024-11-13T10:29:53+00:00" + }, + { + "name": "utopia-php/vcs", + "version": "0.8.6", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/vcs.git", + "reference": "b10225f54d5670f09f83e82e09de9d820ada6931" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/vcs/zipball/b10225f54d5670f09f83e82e09de9d820ada6931", + "reference": "b10225f54d5670f09f83e82e09de9d820ada6931", "shasum": "" }, "require": { "adhocore/jwt": "^1.1", "php": ">=8.0", - "utopia-php/cache": "^0.10.0", + "utopia-php/cache": "^0.11.0", "utopia-php/framework": "0.*.*" }, "require-dev": { @@ -2813,9 +4618,9 @@ ], "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.8.6" }, - "time": "2024-08-13T14:36:30+00:00" + "time": "2024-12-10T13:13:23+00:00" }, { "name": "utopia-php/websocket", @@ -3002,16 +4807,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "dev-master", + "version": "0.39.26", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "a810403a7e37c43523663e9537e070fe62ceb3e7" + "reference": "39768deacb4913f93548c46fa0149c3fadc62d0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/a810403a7e37c43523663e9537e070fe62ceb3e7", - "reference": "a810403a7e37c43523663e9537e070fe62ceb3e7", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/39768deacb4913f93548c46fa0149c3fadc62d0c", + "reference": "39768deacb4913f93548c46fa0149c3fadc62d0c", "shasum": "" }, "require": { @@ -3019,7 +4824,7 @@ "ext-json": "*", "ext-mbstring": "*", "matthiasmullie/minify": "1.3.*", - "php": ">=8.0", + "php": ">=8.3", "twig/twig": "3.14.*" }, "require-dev": { @@ -3027,7 +4832,6 @@ "phpunit/phpunit": "11.*", "squizlabs/php_codesniffer": "3.*" }, - "default-branch": true, "type": "library", "autoload": { "psr-4": { @@ -3048,9 +4852,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/master" + "source": "https://github.com/appwrite/sdk-generator/tree/0.39.26" }, - "time": "2024-10-14T16:51:18+00:00" + "time": "2024-12-12T10:51:34+00:00" }, { "name": "doctrine/annotations", @@ -3130,29 +4934,27 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.3", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" + "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/31610dbb31faa98e6b5447b62340826f54fbc4e9", + "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9", - "phpstan/phpstan": "1.4.10 || 1.10.15", - "phpstan/phpstan-phpunit": "^1.0", + "doctrine/coding-standard": "^9 || ^12", + "phpstan/phpstan": "1.4.10 || 2.0.3", + "phpstan/phpstan-phpunit": "^1.0 || ^2", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psalm/plugin-phpunit": "0.18.4", - "psr/log": "^1 || ^2 || ^3", - "vimeo/psalm": "4.30.0 || 5.12.0" + "psr/log": "^1 || ^2 || ^3" }, "suggest": { "psr/log": "Allows logging deprecations via PSR-3 logger implementation" @@ -3160,7 +4962,7 @@ "type": "library", "autoload": { "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + "Doctrine\\Deprecations\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3171,9 +4973,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.3" + "source": "https://github.com/doctrine/deprecations/tree/1.1.4" }, - "time": "2024-01-30T19:34:25+00:00" + "time": "2024-12-07T21:18:45+00:00" }, { "name": "doctrine/instantiator", @@ -3324,16 +5126,16 @@ }, { "name": "laravel/pint", - "version": "v1.18.1", + "version": "v1.18.3", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9" + "reference": "cef51821608239040ab841ad6e1c6ae502ae3026" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/35c00c05ec43e6b46d295efc0f4386ceb30d50d9", - "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9", + "url": "https://api.github.com/repos/laravel/pint/zipball/cef51821608239040ab841ad6e1c6ae502ae3026", + "reference": "cef51821608239040ab841ad6e1c6ae502ae3026", "shasum": "" }, "require": { @@ -3344,13 +5146,13 @@ "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.64.0", - "illuminate/view": "^10.48.20", - "larastan/larastan": "^2.9.8", + "friendsofphp/php-cs-fixer": "^3.65.0", + "illuminate/view": "^10.48.24", + "larastan/larastan": "^2.9.11", "laravel-zero/framework": "^10.4.0", "mockery/mockery": "^1.6.12", - "nunomaduro/termwind": "^1.15.1", - "pestphp/pest": "^2.35.1" + "nunomaduro/termwind": "^1.17.0", + "pestphp/pest": "^2.36.0" }, "bin": [ "builds/pint" @@ -3386,7 +5188,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2024-09-24T17:22:50+00:00" + "time": "2024-11-26T15:34:00+00:00" }, { "name": "matthiasmullie/minify", @@ -3514,16 +5316,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.12.0", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", "shasum": "" }, "require": { @@ -3562,7 +5364,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" }, "funding": [ { @@ -3570,7 +5372,7 @@ "type": "tidelift" } ], - "time": "2024-06-12T14:39:25+00:00" + "time": "2024-11-08T17:47:46+00:00" }, { "name": "nikic/php-parser", @@ -4005,16 +5807,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.4.1", + "version": "5.6.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" + "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", - "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", + "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", "shasum": "" }, "require": { @@ -4023,17 +5825,17 @@ "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.2", "phpdocumentor/type-resolver": "^1.7", - "phpstan/phpdoc-parser": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "~1.3.5", + "mockery/mockery": "~1.3.5 || ~1.6.0", "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^1.8", "phpstan/phpstan-mockery": "^1.1", "phpstan/phpstan-webmozart-assert": "^1.2", "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^5.13" + "psalm/phar": "^5.26" }, "type": "library", "extra": { @@ -4063,29 +5865,29 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.1" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.1" }, - "time": "2024-05-21T05:55:05+00:00" + "time": "2024-12-07T09:39:29+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.8.2", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "153ae662783729388a584b4361f2545e4d841e3c" + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", - "reference": "153ae662783729388a584b4361f2545e4d841e3c", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", "shasum": "" }, "require": { "doctrine/deprecations": "^1.0", "php": "^7.3 || ^8.0", "phpdocumentor/reflection-common": "^2.0", - "phpstan/phpdoc-parser": "^1.13" + "phpstan/phpdoc-parser": "^1.18|^2.0" }, "require-dev": { "ext-tokenizer": "*", @@ -4121,32 +5923,33 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" }, - "time": "2024-02-23T11:10:43+00:00" + "time": "2024-11-09T15:12:26+00:00" }, { "name": "phpspec/prophecy", - "version": "v1.19.0", + "version": "v1.20.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "67a759e7d8746d501c41536ba40cd9c0a07d6a87" + "reference": "a0165c648cab6a80311c74ffc708a07bb53ecc93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/67a759e7d8746d501c41536ba40cd9c0a07d6a87", - "reference": "67a759e7d8746d501c41536ba40cd9c0a07d6a87", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/a0165c648cab6a80311c74ffc708a07bb53ecc93", + "reference": "a0165c648cab6a80311c74ffc708a07bb53ecc93", "shasum": "" }, "require": { "doctrine/instantiator": "^1.2 || ^2.0", - "php": "^7.2 || 8.0.* || 8.1.* || 8.2.* || 8.3.*", + "php": "^7.2 || 8.0.* || 8.1.* || 8.2.* || 8.3.* || 8.4.*", "phpdocumentor/reflection-docblock": "^5.2", "sebastian/comparator": "^3.0 || ^4.0 || ^5.0 || ^6.0", "sebastian/recursion-context": "^3.0 || ^4.0 || ^5.0 || ^6.0" }, "require-dev": { + "friendsofphp/php-cs-fixer": "^3.40", "phpspec/phpspec": "^6.0 || ^7.0", "phpstan/phpstan": "^1.9", "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0" @@ -4190,36 +5993,36 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.19.0" + "source": "https://github.com/phpspec/prophecy/tree/v1.20.0" }, - "time": "2024-02-29T11:52:51+00:00" + "time": "2024-11-19T13:12:41+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "1.33.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140" + "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/82a311fd3690fb2bf7b64d5c98f912b3dd746140", - "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/c00d78fb6b29658347f9d37ebe104bffadf36299", + "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { "doctrine/annotations": "^2.0", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^5.3.0", "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.5", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^9.5", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", "symfony/process": "^5.2" }, "type": "library", @@ -4237,9 +6040,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.33.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.0" }, - "time": "2024-10-13T11:25:22+00:00" + "time": "2024-10-13T11:29:49+00:00" }, { "name": "phpunit/php-code-coverage", @@ -4712,109 +6515,6 @@ }, "time": "2021-02-03T23:26:27+00:00" }, - { - "name": "psr/container", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" - }, - "time": "2021-11-05T16:47:00+00:00" - }, - { - "name": "psr/log", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.2" - }, - "time": "2024-09-11T13:17:53+00:00" - }, { "name": "sebastian/cli-parser", "version": "1.0.2", @@ -5876,16 +7576,16 @@ }, { "name": "symfony/console", - "version": "v7.1.5", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee" + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0fa539d12b3ccf068a722bbbffa07ca7079af9ee", - "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee", + "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", "shasum": "" }, "require": { @@ -5949,7 +7649,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.5" + "source": "https://github.com/symfony/console/tree/v7.2.1" }, "funding": [ { @@ -5965,87 +7665,20 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:28:38+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.5.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-12-11T03:49:26+00:00" }, { "name": "symfony/filesystem", - "version": "v7.1.5", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "61fe0566189bf32e8cfee78335d8776f64a66f5a" + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/61fe0566189bf32e8cfee78335d8776f64a66f5a", - "reference": "61fe0566189bf32e8cfee78335d8776f64a66f5a", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", "shasum": "" }, "require": { @@ -6082,7 +7715,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.2.0" }, "funding": [ { @@ -6098,20 +7731,20 @@ "type": "tidelift" } ], - "time": "2024-09-17T09:16:35+00:00" + "time": "2024-10-25T15:15:23+00:00" }, { "name": "symfony/finder", - "version": "v7.1.4", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "d95bbf319f7d052082fb7af147e0f835a695e823" + "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/d95bbf319f7d052082fb7af147e0f835a695e823", - "reference": "d95bbf319f7d052082fb7af147e0f835a695e823", + "url": "https://api.github.com/repos/symfony/finder/zipball/6de263e5868b9a137602dd1e33e4d48bfae99c49", + "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49", "shasum": "" }, "require": { @@ -6146,7 +7779,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.1.4" + "source": "https://github.com/symfony/finder/tree/v7.2.0" }, "funding": [ { @@ -6162,20 +7795,20 @@ "type": "tidelift" } ], - "time": "2024-08-13T14:28:19+00:00" + "time": "2024-10-23T06:56:12+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.1.1", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55" + "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/47aa818121ed3950acd2b58d1d37d08a94f9bf55", - "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/7da8fbac9dcfef75ffc212235d76b2754ce0cf50", + "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50", "shasum": "" }, "require": { @@ -6213,7 +7846,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.1.1" + "source": "https://github.com/symfony/options-resolver/tree/v7.2.0" }, "funding": [ { @@ -6229,7 +7862,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-11-20T11:17:29+00:00" }, { "name": "symfony/polyfill-ctype", @@ -6257,8 +7890,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -6333,8 +7966,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -6411,8 +8044,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -6489,8 +8122,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -6547,16 +8180,16 @@ }, { "name": "symfony/process", - "version": "v7.1.5", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "5c03ee6369281177f07f7c68252a280beccba847" + "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/5c03ee6369281177f07f7c68252a280beccba847", - "reference": "5c03ee6369281177f07f7c68252a280beccba847", + "url": "https://api.github.com/repos/symfony/process/zipball/d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", + "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", "shasum": "" }, "require": { @@ -6588,7 +8221,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.2.0" }, "funding": [ { @@ -6604,103 +8237,20 @@ "type": "tidelift" } ], - "time": "2024-09-19T21:48:23+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v3.5.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-11-06T14:24:19+00:00" }, { "name": "symfony/string", - "version": "v7.1.5", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306" + "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/d66f9c343fa894ec2037cc928381df90a7ad4306", - "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306", + "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82", + "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82", "shasum": "" }, "require": { @@ -6758,7 +8308,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.5" + "source": "https://github.com/symfony/string/tree/v7.2.0" }, "funding": [ { @@ -6774,7 +8324,7 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:28:38+00:00" + "time": "2024-11-13T13:31:26+00:00" }, { "name": "textalk/websocket", @@ -6877,16 +8427,16 @@ }, { "name": "twig/twig", - "version": "v3.14.0", + "version": "v3.14.2", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72" + "reference": "0b6f9d8370bb3b7f1ce5313ed8feb0fafd6e399a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/126b2c97818dbff0cdf3fbfc881aedb3d40aae72", - "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/0b6f9d8370bb3b7f1ce5313ed8feb0fafd6e399a", + "reference": "0b6f9d8370bb3b7f1ce5313ed8feb0fafd6e399a", "shasum": "" }, "require": { @@ -6940,7 +8490,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.14.0" + "source": "https://github.com/twigphp/Twig/tree/v3.14.2" }, "funding": [ { @@ -6952,7 +8502,7 @@ "type": "tidelift" } ], - "time": "2024-09-09T17:55:12+00:00" + "time": "2024-11-07T12:36:22+00:00" }, { "name": "webmozart/glob", @@ -7006,9 +8556,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "appwrite/sdk-generator": 20 - }, + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/docker-compose.yml b/docker-compose.yml index d091687952..6c0c299add 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -97,6 +97,7 @@ services: - _APP_EDITION - _APP_WORKER_PER_CORE - _APP_LOCALE + - _APP_COMPRESSION_MIN_SIZE_BYTES - _APP_CONSOLE_WHITELIST_ROOT - _APP_CONSOLE_WHITELIST_EMAILS - _APP_CONSOLE_SESSION_ALERTS @@ -193,11 +194,14 @@ services: - _APP_EXPERIMENT_LOGGING_PROVIDER - _APP_EXPERIMENT_LOGGING_CONFIG - _APP_DATABASE_SHARED_TABLES + - _APP_DATABASE_SHARED_TABLES_V1 + - _APP_DATABASE_SHARED_NAMESPACE + - _APP_FUNCTIONS_CREATION_ABUSE_LIMIT appwrite-console: <<: *x-logging container_name: appwrite-console - image: appwrite/console:5.0.12 + image: appwrite/console:5.2.27 restart: unless-stopped networks: - appwrite @@ -382,6 +386,8 @@ services: - _APP_EXECUTOR_SECRET - _APP_EXECUTOR_HOST - _APP_DATABASE_SHARED_TABLES + - _APP_DATABASE_SHARED_TABLES_V1 + - _APP_EMAIL_CERTIFICATES appwrite-worker-databases: entrypoint: worker-databases @@ -864,7 +870,7 @@ services: appwrite-assistant: container_name: appwrite-assistant - image: appwrite/assistant:0.4.0 + image: appwrite/assistant:0.5.0 networks: - appwrite environment: @@ -1054,4 +1060,4 @@ volumes: appwrite-certificates: appwrite-functions: appwrite-builds: - appwrite-config: + appwrite-config: \ No newline at end of file diff --git a/docs/references/account/create-token-magic-url.md b/docs/references/account/create-token-magic-url.md index 6ebe4154b8..99ad6dba5e 100644 --- a/docs/references/account/create-token-magic-url.md +++ b/docs/references/account/create-token-magic-url.md @@ -1,3 +1,3 @@ -Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST /v1/account/sessions/token](https://appwrite.io/docs/references/cloud/client-web/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default. +Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST /v1/account/sessions/token](https://appwrite.io/docs/references/cloud/client-web/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). diff --git a/docs/references/teams/get-team-member.md b/docs/references/teams/get-team-member.md index fab52c1a75..91bf44c73b 100644 --- a/docs/references/teams/get-team-member.md +++ b/docs/references/teams/get-team-member.md @@ -1 +1 @@ -Get a team member by the membership unique id. All team members have read access for this resource. \ No newline at end of file +Get a team member by the membership unique id. All team members have read access for this resource. Hide sensitive attributes from the response by toggling membership privacy in the Console. \ No newline at end of file diff --git a/docs/references/teams/list-team-members.md b/docs/references/teams/list-team-members.md index d7dd04977f..540a665d98 100644 --- a/docs/references/teams/list-team-members.md +++ b/docs/references/teams/list-team-members.md @@ -1 +1 @@ -Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. \ No newline at end of file +Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. Hide sensitive attributes from the response by toggling membership privacy in the Console. \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml index 90ebd4225f..4c4e55ea4e 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -33,6 +33,7 @@ ./tests/e2e/Services/Storage ./tests/e2e/Services/Webhooks ./tests/e2e/Services/Messaging + ./tests/e2e/Services/Migrations ./tests/e2e/Services/Functions/FunctionsBase.php ./tests/e2e/Services/Functions/FunctionsCustomServerTest.php ./tests/e2e/Services/Functions/FunctionsCustomClientTest.php diff --git a/src/Appwrite/Certificates/Adapter.php b/src/Appwrite/Certificates/Adapter.php new file mode 100644 index 0000000000..711e4c09b9 --- /dev/null +++ b/src/Appwrite/Certificates/Adapter.php @@ -0,0 +1,14 @@ +email = $email; + } + + + public function issueCertificate(string $certName, string $domain): ?string + { + $stdout = ''; + $stderr = ''; + + $staging = (App::isProduction()) ? '' : ' --dry-run'; + $exit = Console::execute( + "certbot certonly -v --webroot --noninteractive --agree-tos{$staging}" + . " --email " . $this->email + . " --cert-name " . $certName + . " -w " . APP_STORAGE_CERTIFICATES + . " -d {$domain}", + '', + $stdout, + $stderr + ); + + // Unexpected error, usually 5XX, API limits, ... + if ($exit !== 0) { + throw new Exception('Failed to issue a certificate with message: ' . $stderr); + } + + // Prepare folder in storage for domain + $path = APP_STORAGE_CERTIFICATES . '/' . $domain; + if (!\is_readable($path)) { + if (!\mkdir($path, 0755, true)) { + throw new Exception('Failed to create path for certificate.'); + } + } + + // Move generated files + if (!@\rename('/etc/letsencrypt/live/' . $certName . '/cert.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem')) { + throw new Exception('Failed to rename certificate cert.pem. Let\'s Encrypt log: ' . $stderr . ' ; ' . $stdout); + } + + if (!@\rename('/etc/letsencrypt/live/' . $certName . '/chain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/chain.pem')) { + throw new Exception('Failed to rename certificate chain.pem. Let\'s Encrypt log: ' . $stderr . ' ; ' . $stdout); + } + + if (!@\rename('/etc/letsencrypt/live/' . $certName . '/fullchain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/fullchain.pem')) { + throw new Exception('Failed to rename certificate fullchain.pem. Let\'s Encrypt log: ' . $stderr . ' ; ' . $stdout); + } + + if (!@\rename('/etc/letsencrypt/live/' . $certName . '/privkey.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/privkey.pem')) { + throw new Exception('Failed to rename certificate privkey.pem. Let\'s Encrypt log: ' . $stderr . ' ; ' . $stdout); + } + + $config = \implode(PHP_EOL, [ + "tls:", + " certificates:", + " - certFile: /storage/certificates/{$domain}/fullchain.pem", + " keyFile: /storage/certificates/{$domain}/privkey.pem" + ]); + + // Save configuration into Traefik using our new cert files + if (!\file_put_contents(APP_STORAGE_CONFIG . '/' . $domain . '.yml', $config)) { + throw new Exception('Failed to save Traefik configuration.'); + } + + $certPath = APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem'; + $certData = openssl_x509_parse(file_get_contents($certPath)); + $validTo = $certData['validTo_time_t'] ?? null; + $dt = (new \DateTime())->setTimestamp($validTo); + return DateTime::addSeconds($dt, -60 * 60 * 24 * 30); + } + + public function isRenewRequired(string $domain, Log $log): bool + { + $certPath = APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem'; + if (\file_exists($certPath)) { + $certData = openssl_x509_parse(file_get_contents($certPath)); + $validTo = $certData['validTo_time_t'] ?? 0; + + if (empty($validTo)) { + $log->addTag('certificateDomain', $domain); + throw new Exception('Unable to read certificate file (cert.pem).'); + } + + // LetsEncrypt allows renewal 30 days before expiry + $expiryInAdvance = (60 * 60 * 24 * 30); + if ($validTo - $expiryInAdvance > \time()) { + $log->addTag('certificateDomain', $domain); + $log->addExtra('certificateData', \is_array($certData) ? \json_encode($certData) : \strval($certData)); + return false; + } + } + + return true; + } + + public function deleteCertificate(string $domain): void + { + $directory = APP_STORAGE_CERTIFICATES . '/' . $domain; + $checkTraversal = realpath($directory) === $directory; + + if ($checkTraversal && is_dir($directory)) { + // Delete files, so Traefik is aware of change + array_map('unlink', glob($directory . '/*.*')); + rmdir($directory); + Console::info("Deleted certificate files for {$domain}"); + } else { + Console::info("No certificate files found for {$domain}"); + } + } +} diff --git a/src/Appwrite/Event/Audit.php b/src/Appwrite/Event/Audit.php index 4b02849970..17506bfe6c 100644 --- a/src/Appwrite/Event/Audit.php +++ b/src/Appwrite/Event/Audit.php @@ -121,6 +121,10 @@ class Audit extends Event */ public function trigger(): string|bool { + if ($this->paused) { + return false; + } + $client = new Client($this->queue, $this->connection); return $client->enqueue([ diff --git a/src/Appwrite/Event/Build.php b/src/Appwrite/Event/Build.php index b8cb62a6f8..1fbf20a9f9 100644 --- a/src/Appwrite/Event/Build.php +++ b/src/Appwrite/Event/Build.php @@ -112,6 +112,10 @@ class Build extends Event */ public function trigger(): string|bool { + if ($this->paused) { + return false; + } + $client = new Client($this->queue, $this->connection); return $client->enqueue([ diff --git a/src/Appwrite/Event/Certificate.php b/src/Appwrite/Event/Certificate.php index 85058c96fe..5d30c3d5ac 100644 --- a/src/Appwrite/Event/Certificate.php +++ b/src/Appwrite/Event/Certificate.php @@ -74,6 +74,10 @@ class Certificate extends Event */ public function trigger(): string|bool { + if ($this->paused) { + return false; + } + $client = new Client($this->queue, $this->connection); return $client->enqueue([ diff --git a/src/Appwrite/Event/Database.php b/src/Appwrite/Event/Database.php index f9eb7d9a7d..1b0ea6851c 100644 --- a/src/Appwrite/Event/Database.php +++ b/src/Appwrite/Event/Database.php @@ -108,6 +108,10 @@ class Database extends Event */ public function trigger(): string|bool { + if ($this->paused) { + return false; + } + try { $dsn = new DSN($this->getProject()->getAttribute('database')); } catch (\InvalidArgumentException) { diff --git a/src/Appwrite/Event/Delete.php b/src/Appwrite/Event/Delete.php index 064fbcefa9..1a4c9318e3 100644 --- a/src/Appwrite/Event/Delete.php +++ b/src/Appwrite/Event/Delete.php @@ -140,6 +140,10 @@ class Delete extends Event */ public function trigger(): string|bool { + if ($this->paused) { + return false; + } + $client = new Client($this->queue, $this->connection); return $client->enqueue([ diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php index 43eda511df..e3a2e394cf 100644 --- a/src/Appwrite/Event/Event.php +++ b/src/Appwrite/Event/Event.php @@ -65,6 +65,24 @@ class Event { } + /** + * Set paused state for this event. + */ + public function setPaused(bool $paused): self + { + $this->paused = $paused; + + return $this; + } + + /** + * Get paused state for this event. + */ + public function getPaused(): bool + { + return $this->paused; + } + /** * Set queue used for this event. * @@ -204,19 +222,6 @@ class Event return $this->payload; } - public function getRealtimePayload(): array - { - $payload = []; - - foreach ($this->payload as $key => $value) { - if (!isset($this->sensitive[$key])) { - $payload[$key] = $value; - } - } - - return $payload; - } - /** * Set context for this event. * @@ -530,20 +535,21 @@ class Event } /** - * Get the value of paused + * Generate a function event from a base event + * + * @param Event $event + * + * @return self + * */ - public function isPaused(): bool + public function from(Event $event): self { - return $this->paused; - } - - /** - * Set the value of paused - */ - public function setPaused(bool $paused): self - { - $this->paused = $paused; - + $this->project = $event->getProject(); + $this->user = $event->getUser(); + $this->payload = $event->getPayload(); + $this->event = $event->getEvent(); + $this->params = $event->getParams(); + $this->context = $event->context; return $this; } } diff --git a/src/Appwrite/Event/Func.php b/src/Appwrite/Event/Func.php index 4dad5802f7..0ad639a9f5 100644 --- a/src/Appwrite/Event/Func.php +++ b/src/Appwrite/Event/Func.php @@ -238,22 +238,4 @@ class Func extends Event 'method' => $this->method, ]); } - - /** - * Generate a function event from a base event - * - * @param Event $event - * - * @return self - * - */ - public function from(Event $event): self - { - $this->project = $event->getProject(); - $this->user = $event->getUser(); - $this->payload = $event->getPayload(); - $this->event = $event->getEvent(); - $this->params = $event->getParams(); - return $this; - } } diff --git a/src/Appwrite/Event/Mail.php b/src/Appwrite/Event/Mail.php index 9bdbf6044d..a0fca75688 100644 --- a/src/Appwrite/Event/Mail.php +++ b/src/Appwrite/Event/Mail.php @@ -404,6 +404,10 @@ class Mail extends Event */ public function trigger(): string|bool { + if ($this->paused) { + return false; + } + $client = new Client($this->queue, $this->connection); return $client->enqueue([ diff --git a/src/Appwrite/Event/Messaging.php b/src/Appwrite/Event/Messaging.php index f97ff02d21..ab9b1bee6b 100644 --- a/src/Appwrite/Event/Messaging.php +++ b/src/Appwrite/Event/Messaging.php @@ -182,6 +182,10 @@ class Messaging extends Event */ public function trigger(): string | bool { + if ($this->paused) { + return false; + } + $client = new Client($this->queue, $this->connection); return $client->enqueue([ diff --git a/src/Appwrite/Event/Migration.php b/src/Appwrite/Event/Migration.php index e57ac3c87c..789b8e2160 100644 --- a/src/Appwrite/Event/Migration.php +++ b/src/Appwrite/Event/Migration.php @@ -75,6 +75,9 @@ class Migration extends Event */ public function trigger(): string|bool { + if ($this->paused) { + return false; + } $client = new Client($this->queue, $this->connection); diff --git a/src/Appwrite/Event/Realtime.php b/src/Appwrite/Event/Realtime.php new file mode 100644 index 0000000000..f4f00b59d4 --- /dev/null +++ b/src/Appwrite/Event/Realtime.php @@ -0,0 +1,70 @@ +payload as $key => $value) { + if (!isset($this->sensitive[$key])) { + $payload[$key] = $value; + } + } + + return $payload; + } + + /** + * Execute Event. + * + * @return string|bool + * @throws InvalidArgumentException + */ + public function trigger(): string|bool + { + if ($this->paused || empty($this->event)) { + return false; + } + + $allEvents = Event::generateEvents($this->getEvent(), $this->getParams()); + $payload = new Document($this->getPayload()); + + $db = $this->getContext('database'); + $collection = $this->getContext('collection'); + $bucket = $this->getContext('bucket'); + + $target = RealtimeAdapter::fromPayload( + // Pass first, most verbose event pattern + event: $allEvents[0], + payload: $payload, + project: $this->getProject(), + database: $db, + collection: $collection, + bucket: $bucket, + ); + + RealtimeAdapter::send( + projectId: $target['projectId'] ?? $this->getProject()->getId(), + payload: $this->getRealtimePayload(), + events: $allEvents, + channels: $target['channels'], + roles: $target['roles'], + options: [ + 'permissionsChanged' => $target['permissionsChanged'], + 'userId' => $this->getParam('userId') + ] + ); + + return true; + } +} diff --git a/src/Appwrite/Event/Usage.php b/src/Appwrite/Event/Usage.php index 4426f4ab1b..161c251c8e 100644 --- a/src/Appwrite/Event/Usage.php +++ b/src/Appwrite/Event/Usage.php @@ -57,6 +57,10 @@ class Usage extends Event */ public function trigger(): string|bool { + if ($this->paused) { + return false; + } + $client = new Client($this->queue, $this->connection); return $client->enqueue([ 'project' => $this->getProject(), diff --git a/src/Appwrite/Event/UsageDump.php b/src/Appwrite/Event/UsageDump.php index 8f87908849..2998e4e104 100644 --- a/src/Appwrite/Event/UsageDump.php +++ b/src/Appwrite/Event/UsageDump.php @@ -38,6 +38,10 @@ class UsageDump extends Event */ public function trigger(): string|bool { + if ($this->paused) { + return false; + } + $client = new Client($this->queue, $this->connection); return $client->enqueue([ diff --git a/src/Appwrite/Event/Webhook.php b/src/Appwrite/Event/Webhook.php new file mode 100644 index 0000000000..125c9a78d5 --- /dev/null +++ b/src/Appwrite/Event/Webhook.php @@ -0,0 +1,17 @@ +setQueue(Event::WEBHOOK_QUEUE_NAME) + ->setClass(Event::WEBHOOK_CLASS_NAME); + } +} 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'; diff --git a/src/Appwrite/Messaging/Adapter/Realtime.php b/src/Appwrite/Messaging/Adapter/Realtime.php index c437d4d487..dceafacf6e 100644 --- a/src/Appwrite/Messaging/Adapter/Realtime.php +++ b/src/Appwrite/Messaging/Adapter/Realtime.php @@ -7,7 +7,6 @@ use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; -use Utopia\System\System; class Realtime extends Adapter { @@ -139,20 +138,26 @@ class Realtime extends Adapter $permissionsChanged = array_key_exists('permissionsChanged', $options) && $options['permissionsChanged']; $userId = array_key_exists('userId', $options) ? $options['userId'] : null; - $redis = new \Redis(); //TODO: make this part of the constructor - $redis->connect(System::getEnv('_APP_REDIS_HOST', ''), System::getEnv('_APP_REDIS_PORT', '')); - $redis->publish('realtime', json_encode([ - 'project' => $projectId, - 'roles' => $roles, - 'permissionsChanged' => $permissionsChanged, - 'userId' => $userId, - 'data' => [ - 'events' => $events, - 'channels' => $channels, - 'timestamp' => DateTime::formatTz(DateTime::now()), - 'payload' => $payload - ] - ])); + global $register; + $pubsub = $register->get('pools')->get('pubsub')->pop(); + try { + /** @var \Appwrite\PubSub\Adapter $redis */ + $redis = $pubsub->getResource(); + $redis->publish('realtime', json_encode([ + 'project' => $projectId, + 'roles' => $roles, + 'permissionsChanged' => $permissionsChanged, + 'userId' => $userId, + 'data' => [ + 'events' => $events, + 'channels' => $channels, + 'timestamp' => DateTime::formatTz(DateTime::now()), + 'payload' => $payload + ] + ])); + } finally { + $pubsub->reclaim(); + } } /** diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php index cee1b2d263..19f69b1a4f 100644 --- a/src/Appwrite/Migration/Migration.php +++ b/src/Appwrite/Migration/Migration.php @@ -91,6 +91,7 @@ abstract class Migration '1.5.10' => 'V20', '1.5.11' => 'V20', '1.6.0' => 'V21', + '1.6.1' => 'V21', ]; /** diff --git a/src/Appwrite/Platform/Tasks/Doctor.php b/src/Appwrite/Platform/Tasks/Doctor.php index 82d1ca2d59..c43afea527 100644 --- a/src/Appwrite/Platform/Tasks/Doctor.php +++ b/src/Appwrite/Platform/Tasks/Doctor.php @@ -3,6 +3,7 @@ namespace Appwrite\Platform\Tasks; use Appwrite\ClamAV\Network; +use Appwrite\PubSub\Adapter; use Utopia\App; use Utopia\CLI\Console; use Utopia\Config\Config; @@ -158,6 +159,7 @@ class Doctor extends Action foreach ($configs as $key => $config) { foreach ($config as $pool) { try { + /** @var Adapter $adapter */ $adapter = $pools->get($pool)->pop()->getResource(); if ($adapter->ping()) { diff --git a/src/Appwrite/Platform/Tasks/Maintenance.php b/src/Appwrite/Platform/Tasks/Maintenance.php index afb38f35fc..55f4f25843 100644 --- a/src/Appwrite/Platform/Tasks/Maintenance.php +++ b/src/Appwrite/Platform/Tasks/Maintenance.php @@ -23,13 +23,13 @@ class Maintenance extends Action { $this ->desc('Schedules maintenance tasks and publishes them to our queues') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('queueForCertificates') ->inject('queueForDeletes') - ->callback(fn (Database $dbForConsole, Certificate $queueForCertificates, Delete $queueForDeletes) => $this->action($dbForConsole, $queueForCertificates, $queueForDeletes)); + ->callback(fn (Database $dbForPlatform, Certificate $queueForCertificates, Delete $queueForDeletes) => $this->action($dbForPlatform, $queueForCertificates, $queueForDeletes)); } - public function action(Database $dbForConsole, Certificate $queueForCertificates, Delete $queueForDeletes): void + public function action(Database $dbForPlatform, Certificate $queueForCertificates, Delete $queueForDeletes): void { Console::title('Maintenance V1'); Console::success(APP_NAME . ' maintenance process v1 has started'); @@ -41,19 +41,19 @@ class Maintenance extends Action $cacheRetention = (int) System::getEnv('_APP_MAINTENANCE_RETENTION_CACHE', '2592000'); // 30 days $schedulesDeletionRetention = (int) System::getEnv('_APP_MAINTENANCE_RETENTION_SCHEDULES', '86400'); // 1 Day - Console::loop(function () use ($interval, $cacheRetention, $schedulesDeletionRetention, $usageStatsRetentionHourly, $dbForConsole, $queueForDeletes, $queueForCertificates) { + Console::loop(function () use ($interval, $cacheRetention, $schedulesDeletionRetention, $usageStatsRetentionHourly, $dbForPlatform, $queueForDeletes, $queueForCertificates) { $time = DateTime::now(); Console::info("[{$time}] Notifying workers with maintenance tasks every {$interval} seconds"); - $this->foreachProject($dbForConsole, function (Document $project) use ($queueForDeletes, $usageStatsRetentionHourly) { + $this->foreachProject($dbForPlatform, function (Document $project) use ($queueForDeletes, $usageStatsRetentionHourly) { $queueForDeletes->setProject($project); $this->notifyProjects($queueForDeletes, $usageStatsRetentionHourly); }); $this->notifyDeleteConnections($queueForDeletes); - $this->renewCertificates($dbForConsole, $queueForCertificates); + $this->renewCertificates($dbForPlatform, $queueForCertificates); $this->notifyDeleteCache($cacheRetention, $queueForDeletes); $this->notifyDeleteSchedules($schedulesDeletionRetention, $queueForDeletes); }, $interval, $delay); @@ -72,7 +72,7 @@ class Maintenance extends Action $this->notifyDeleteExpiredSessions($queueForDeletes); } - protected function foreachProject(Database $dbForConsole, callable $callback): void + protected function foreachProject(Database $dbForPlatform, callable $callback): void { // TODO: @Meldiron name of this method no longer matches. It does not delete, and it gives whole document $count = 0; @@ -82,7 +82,7 @@ class Maintenance extends Action $executionStart = \microtime(true); while ($sum === $limit) { - $projects = $dbForConsole->find('projects', [Query::limit($limit), Query::offset($chunk * $limit)]); + $projects = $dbForPlatform->find('projects', [Query::limit($limit), Query::offset($chunk * $limit)]); $chunk++; @@ -143,12 +143,13 @@ class Maintenance extends Action ->trigger(); } - private function renewCertificates(Database $dbForConsole, Certificate $queueForCertificate): void + private function renewCertificates(Database $dbForPlatform, Certificate $queueForCertificate): void { $time = DateTime::now(); - $certificates = $dbForConsole->find('certificates', [ + $certificates = $dbForPlatform->find('certificates', [ Query::lessThan('attempts', 5), // Maximum 5 attempts + Query::isNotNull('renewDate'), Query::lessThanEqual('renewDate', $time), // includes 60 days cooldown (we have 30 days to renew) Query::limit(200), // Limit 200 comes from LetsEncrypt (300 orders per 3 hours, keeping some for new domains) ]); diff --git a/src/Appwrite/Platform/Tasks/Migrate.php b/src/Appwrite/Platform/Tasks/Migrate.php index dcba59bb1d..4efa78ed4b 100644 --- a/src/Appwrite/Platform/Tasks/Migrate.php +++ b/src/Appwrite/Platform/Tasks/Migrate.php @@ -30,12 +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('dbForConsole') + ->inject('dbForPlatform') ->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); + ->callback(function ($version, $dbForPlatform, $getProjectDB, Registry $register) { + \Co\run(function () use ($version, $dbForPlatform, $getProjectDB, $register) { + $this->action($version, $dbForPlatform, $getProjectDB, $register); }); }); } @@ -58,7 +58,7 @@ class Migrate extends Action } } - public function action(string $version, Database $dbForConsole, callable $getProjectDB, Registry $register) + public function action(string $version, Database $dbForPlatform, callable $getProjectDB, Registry $register) { Authorization::disable(); if (!array_key_exists($version, Migration::$versions)) { @@ -93,10 +93,10 @@ class Migrate extends Action $count = 0; try { - $totalProjects = $dbForConsole->count('projects') + 1; + $totalProjects = $dbForPlatform->count('projects') + 1; } catch (\Throwable $th) { - $dbForConsole->setNamespace('_console'); - $totalProjects = $dbForConsole->count('projects') + 1; + $dbForPlatform->setNamespace('_console'); + $totalProjects = $dbForPlatform->count('projects') + 1; } $class = 'Appwrite\\Migration\\Version\\' . Migration::$versions[$version]; @@ -120,7 +120,7 @@ class Migrate extends Action $projectDB = $getProjectDB($project); $projectDB->disableValidation(); $migration - ->setProject($project, $projectDB, $dbForConsole) + ->setProject($project, $projectDB, $dbForPlatform) ->setPDO($register->get('db', true)) ->execute(); } catch (\Throwable $th) { @@ -132,7 +132,7 @@ class Migrate extends Action } $sum = \count($projects); - $projects = $dbForConsole->find('projects', [Query::limit($limit), Query::offset($offset)]); + $projects = $dbForPlatform->find('projects', [Query::limit($limit), Query::offset($offset)]); $offset = $offset + $limit; $count = $count + $sum; diff --git a/src/Appwrite/Platform/Tasks/SDKs.php b/src/Appwrite/Platform/Tasks/SDKs.php index 65d2f7717c..6e37f270a4 100644 --- a/src/Appwrite/Platform/Tasks/SDKs.php +++ b/src/Appwrite/Platform/Tasks/SDKs.php @@ -25,6 +25,9 @@ use Appwrite\Spec\Swagger2; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Platform\Action; +use Utopia\Validator\Nullable; +use Utopia\Validator\Text; +use Utopia\Validator\WhiteList; class SDKs extends Action { @@ -37,23 +40,35 @@ class SDKs extends Action { $this ->desc('Generate Appwrite SDKs') - ->callback(fn () => $this->action()); + ->param('platform', null, new Nullable(new Text(256)), 'Selected Platform', optional: true) + ->param('sdk', null, new Nullable(new Text(256)), 'Selected SDK', optional:true) + ->param('version', null, new Nullable(new Text(256)), 'Selected SDK', optional:true) + ->param('git', null, new Nullable(new WhiteList(['yes', 'no'])), 'Should we use git push?', optional: true) + ->param('production', null, new Nullable(new WhiteList(['yes', 'no'])), 'Should we push to production?', optional:true) + ->param('message', null, new Nullable(new Text(256)), 'Commit Message', optional:true) + ->callback([$this, 'action']); } - public function action(): void + public function action(?string $selectedPlatform, ?string $selectedSDK, ?string $version, ?string $git, ?string $production, ?string $message) { - $platforms = Config::getParam('platforms'); - $selectedPlatform = Console::confirm('Choose Platform ("' . APP_PLATFORM_CLIENT . '", "' . APP_PLATFORM_SERVER . '", "' . APP_PLATFORM_CONSOLE . '" or "*" for all):'); - $selectedSDK = \strtolower(Console::confirm('Choose SDK ("*" for all):')); - $version = Console::confirm('Choose an Appwrite version'); - $git = (Console::confirm('Should we use git push? (yes/no)') == 'yes'); - $production = ($git) ? (Console::confirm('Type "Appwrite" to push code to production git repos') == 'Appwrite') : false; - $message = ($git) ? Console::confirm('Please enter your commit message:') : ''; + $selectedPlatform ??= Console::confirm('Choose Platform ("' . APP_PLATFORM_CLIENT . '", "' . APP_PLATFORM_SERVER . '", "' . APP_PLATFORM_CONSOLE . '" or "*" for all):'); + $selectedSDK ??= \strtolower(Console::confirm('Choose SDK ("*" for all):')); + $version ??= Console::confirm('Choose an Appwrite version'); + + $git ??= Console::confirm('Should we use git push? (yes/no)'); + $git = $git === 'yes'; + + if ($git) { + $production ??= Console::confirm('Type "Appwrite" to push code to production git repos'); + $production = $production === 'Appwrite'; + $message ??= Console::confirm('Please enter your commit message:'); + } if (!in_array($version, ['0.6.x', '0.7.x', '0.8.x', '0.9.x', '0.10.x', '0.11.x', '0.12.x', '0.13.x', '0.14.x', '0.15.x', '1.0.x', '1.1.x', '1.2.x', '1.3.x', '1.4.x', '1.5.x', '1.6.x', 'latest'])) { throw new \Exception('Unknown version given'); } + $platforms = Config::getParam('platforms'); foreach ($platforms as $key => $platform) { if ($selectedPlatform !== $key && $selectedPlatform !== '*') { continue; diff --git a/src/Appwrite/Platform/Tasks/ScheduleBase.php b/src/Appwrite/Platform/Tasks/ScheduleBase.php index a1b85c341f..10623e7fa5 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleBase.php +++ b/src/Appwrite/Platform/Tasks/ScheduleBase.php @@ -25,7 +25,7 @@ abstract class ScheduleBase extends Action abstract public static function getName(): string; abstract public static function getSupportedResource(): string; abstract public static function getCollectionId(): string; - abstract protected function enqueueResources(Group $pools, Database $dbForConsole, callable $getProjectDB): void; + abstract protected function enqueueResources(Group $pools, Database $dbForPlatform, callable $getProjectDB): void; public function __construct() { @@ -34,9 +34,9 @@ abstract class ScheduleBase extends Action $this ->desc("Execute {$type}s scheduled in Appwrite") ->inject('pools') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('getProjectDB') - ->callback(fn (Group $pools, Database $dbForConsole, callable $getProjectDB) => $this->action($pools, $dbForConsole, $getProjectDB)); + ->callback(fn (Group $pools, Database $dbForPlatform, callable $getProjectDB) => $this->action($pools, $dbForPlatform, $getProjectDB)); } /** @@ -44,7 +44,7 @@ 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(Group $pools, Database $dbForPlatform, callable $getProjectDB): void { Console::title(\ucfirst(static::getSupportedResource()) . ' scheduler V1'); Console::success(APP_NAME . ' ' . \ucfirst(static::getSupportedResource()) . ' scheduler v1 has started'); @@ -56,8 +56,8 @@ abstract class ScheduleBase extends Action * @throws Exception * @var Document $schedule */ - $getSchedule = function (Document $schedule) use ($dbForConsole, $getProjectDB): array { - $project = $dbForConsole->getDocument('projects', $schedule->getAttribute('projectId')); + $getSchedule = function (Document $schedule) use ($dbForPlatform, $getProjectDB): array { + $project = $dbForPlatform->getDocument('projects', $schedule->getAttribute('projectId')); $resource = $getProjectDB($project)->getDocument( static::getCollectionId(), @@ -91,7 +91,7 @@ abstract class ScheduleBase extends Action $paginationQueries[] = Query::cursorAfter($latestDocument); } - $results = $dbForConsole->find('schedules', \array_merge($paginationQueries, [ + $results = $dbForPlatform->find('schedules', \array_merge($paginationQueries, [ Query::equal('region', [System::getEnv('_APP_REGION', 'default')]), Query::equal('resourceType', [static::getSupportedResource()]), Query::equal('active', [true]), @@ -119,11 +119,11 @@ abstract class ScheduleBase extends Action Console::success("Starting timers at " . DateTime::now()); - run(function () use ($dbForConsole, &$lastSyncUpdate, $getSchedule, $pools, $getProjectDB) { + run(function () use ($dbForPlatform, &$lastSyncUpdate, $getSchedule, $pools, $getProjectDB) { /** * The timer synchronize $schedules copy with database collection. */ - Timer::tick(static::UPDATE_TIMER * 1000, function () use ($dbForConsole, &$lastSyncUpdate, $getSchedule, $pools) { + Timer::tick(static::UPDATE_TIMER * 1000, function () use ($dbForPlatform, &$lastSyncUpdate, $getSchedule, $pools) { $time = DateTime::now(); $timerStart = \microtime(true); @@ -141,7 +141,7 @@ abstract class ScheduleBase extends Action $paginationQueries[] = Query::cursorAfter($latestDocument); } - $results = $dbForConsole->find('schedules', \array_merge($paginationQueries, [ + $results = $dbForPlatform->find('schedules', \array_merge($paginationQueries, [ Query::equal('region', [System::getEnv('_APP_REGION', 'default')]), Query::equal('resourceType', [static::getSupportedResource()]), Query::greaterThanEqual('resourceUpdatedAt', $lastSyncUpdate), @@ -179,10 +179,10 @@ abstract class ScheduleBase extends Action Timer::tick( static::ENQUEUE_TIMER * 1000, - fn () => $this->enqueueResources($pools, $dbForConsole, $getProjectDB) + fn () => $this->enqueueResources($pools, $dbForPlatform, $getProjectDB) ); - $this->enqueueResources($pools, $dbForConsole, $getProjectDB); + $this->enqueueResources($pools, $dbForPlatform, $getProjectDB); }); } } diff --git a/src/Appwrite/Platform/Tasks/ScheduleExecutions.php b/src/Appwrite/Platform/Tasks/ScheduleExecutions.php index 73a2814397..50beb48e9d 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleExecutions.php +++ b/src/Appwrite/Platform/Tasks/ScheduleExecutions.php @@ -27,7 +27,7 @@ class ScheduleExecutions extends ScheduleBase return 'executions'; } - protected function enqueueResources(Group $pools, Database $dbForConsole, callable $getProjectDB): void + protected function enqueueResources(Group $pools, Database $dbForPlatform, callable $getProjectDB): void { $queue = $pools->get('queue')->pop(); $connection = $queue->getResource(); @@ -36,7 +36,7 @@ class ScheduleExecutions extends ScheduleBase foreach ($this->schedules as $schedule) { if (!$schedule['active']) { - $dbForConsole->deleteDocument( + $dbForPlatform->deleteDocument( 'schedules', $schedule['$id'], ); @@ -50,7 +50,7 @@ class ScheduleExecutions extends ScheduleBase continue; } - $data = $dbForConsole->getDocument( + $data = $dbForPlatform->getDocument( 'schedules', $schedule['$id'], )->getAttribute('data', []); @@ -74,7 +74,7 @@ class ScheduleExecutions extends ScheduleBase ->trigger(); }); - $dbForConsole->deleteDocument( + $dbForPlatform->deleteDocument( 'schedules', $schedule['$id'], ); diff --git a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php index 4d57902330..156ff1e31d 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php +++ b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php @@ -31,7 +31,7 @@ class ScheduleFunctions extends ScheduleBase return 'functions'; } - protected function enqueueResources(Group $pools, Database $dbForConsole, callable $getProjectDB): void + protected function enqueueResources(Group $pools, Database $dbForPlatform, callable $getProjectDB): void { $timerStart = \microtime(true); $time = DateTime::now(); diff --git a/src/Appwrite/Platform/Tasks/ScheduleMessages.php b/src/Appwrite/Platform/Tasks/ScheduleMessages.php index b9d8e2a282..2136e62f08 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleMessages.php +++ b/src/Appwrite/Platform/Tasks/ScheduleMessages.php @@ -26,7 +26,7 @@ class ScheduleMessages extends ScheduleBase return 'messages'; } - protected function enqueueResources(Group $pools, Database $dbForConsole, callable $getProjectDB): void + protected function enqueueResources(Group $pools, Database $dbForPlatform, callable $getProjectDB): void { foreach ($this->schedules as $schedule) { if (!$schedule['active']) { @@ -40,7 +40,7 @@ class ScheduleMessages extends ScheduleBase continue; } - \go(function () use ($schedule, $pools, $dbForConsole) { + \go(function () use ($schedule, $pools, $dbForPlatform) { $queue = $pools->get('queue')->pop(); $connection = $queue->getResource(); $queueForMessaging = new Messaging($connection); @@ -51,7 +51,7 @@ class ScheduleMessages extends ScheduleBase ->setProject($schedule['project']) ->trigger(); - $dbForConsole->deleteDocument( + $dbForPlatform->deleteDocument( 'schedules', $schedule['$id'], ); diff --git a/src/Appwrite/Platform/Tasks/Specs.php b/src/Appwrite/Platform/Tasks/Specs.php index f71de98d95..0f1332d821 100644 --- a/src/Appwrite/Platform/Tasks/Specs.php +++ b/src/Appwrite/Platform/Tasks/Specs.php @@ -61,7 +61,7 @@ class Specs extends Action // Mock dependencies 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('dbForPlatform', fn () => new Database(new MySQL(''), new Cache(new None()))); App::setResource('dbForProject', fn () => new Database(new MySQL(''), new Cache(new None()))); $platforms = [ diff --git a/src/Appwrite/Platform/Workers/Builds.php b/src/Appwrite/Platform/Workers/Builds.php index 5dd2f7f886..bef78a7514 100644 --- a/src/Appwrite/Platform/Workers/Builds.php +++ b/src/Appwrite/Platform/Workers/Builds.php @@ -46,7 +46,7 @@ class Builds extends Action $this ->desc('Builds worker') ->inject('message') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('queueForEvents') ->inject('queueForFunctions') ->inject('queueForUsage') @@ -54,12 +54,12 @@ 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)); + ->callback(fn ($message, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions, Usage $usage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, Log $log) => $this->action($message, $dbForPlatform, $queueForEvents, $queueForFunctions, $usage, $cache, $dbForProject, $deviceForFunctions, $log)); } /** * @param Message $message - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param Event $queueForEvents * @param Func $queueForFunctions * @param Usage $queueForUsage @@ -70,7 +70,7 @@ class Builds extends Action * @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 $dbForPlatform, Event $queueForEvents, Func $queueForFunctions, Usage $queueForUsage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, Log $log): void { $payload = $message->getPayload() ?? []; @@ -92,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); + $this->buildDeployment($deviceForFunctions, $queueForFunctions, $queueForEvents, $queueForUsage, $dbForPlatform, $dbForProject, $github, $project, $resource, $deployment, $template, $log); break; default: @@ -105,7 +105,7 @@ class Builds extends Action * @param Func $queueForFunctions * @param Event $queueForEvents * @param Usage $queueForUsage - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param Database $dbForProject * @param GitHub $github * @param Document $project @@ -117,7 +117,7 @@ class Builds extends Action * @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 $dbForPlatform, Database $dbForProject, GitHub $github, Document $project, Document $function, Document $deployment, Document $template, Log $log): void { $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST')); @@ -199,7 +199,7 @@ class Builds extends Action $repositoryName = ''; if ($isVcsEnabled) { - $installation = $dbForConsole->getDocument('installations', $installationId); + $installation = $dbForPlatform->getDocument('installations', $installationId); $providerInstallationId = $installation->getAttribute('providerInstallationId'); $privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY'); $githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID'); @@ -209,8 +209,7 @@ class Builds extends Action try { if ($isNewBuild && !$isVcsEnabled) { - // Non-vcs+Template - + // Non-VCS + Template $templateRepositoryName = $template->getAttribute('repositoryName', ''); $templateOwnerName = $template->getAttribute('ownerName', ''); $templateVersion = $template->getAttribute('version', ''); @@ -233,6 +232,8 @@ class Builds extends Action throw new \Exception('Unable to clone code repository: ' . $stderr); } + Console::execute('find ' . \escapeshellarg($tmpTemplateDirectory) . ' -type d -name ".git" -exec rm -rf {} +', '', $stdout, $stderr); + // Ensure directories Console::execute('mkdir -p ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory), '', $stdout, $stderr); @@ -398,6 +399,8 @@ class Builds extends Action throw new \Exception('Repository directory size should be less than ' . number_format($functionsSizeLimit / 1048576, 2) . ' MBs.'); } + Console::execute('find ' . \escapeshellarg($tmpDirectory) . ' -type d -name ".git" -exec rm -rf {} +', '', $stdout, $stderr); + $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 @@ -415,7 +418,7 @@ class Builds extends Action $directorySize = $deviceForFunctions->getFileSize($source); $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment->setAttribute('path', $source)->setAttribute('size', $directorySize)); - $this->runGitAction('processing', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForConsole); + $this->runGitAction('processing', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForPlatform); } /** Request the executor to build the code... */ @@ -423,7 +426,7 @@ class Builds extends Action $build = $dbForProject->updateDocument('builds', $buildId, $build); if ($isVcsEnabled) { - $this->runGitAction('building', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForConsole); + $this->runGitAction('building', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForPlatform); } /** Trigger Webhook */ @@ -637,7 +640,7 @@ class Builds extends Action $build = $dbForProject->updateDocument('builds', $buildId, $build); if ($isVcsEnabled) { - $this->runGitAction('ready', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForConsole); + $this->runGitAction('ready', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForPlatform); } Console::success("Build id: $buildId created"); @@ -658,12 +661,12 @@ class Builds extends Action /** Update function schedule */ // Inform scheduler if function is still active - $schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId')); + $schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId')); $schedule ->setAttribute('resourceUpdatedAt', DateTime::now()) ->setAttribute('schedule', $function->getAttribute('schedule')) ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment'))); - Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); + Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule)); } catch (\Throwable $th) { if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') { Console::info('Build has been canceled'); @@ -680,7 +683,7 @@ class Builds extends Action $build = $dbForProject->updateDocument('builds', $buildId, $build); if ($isVcsEnabled) { - $this->runGitAction('failed', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForConsole); + $this->runGitAction('failed', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForPlatform); } } finally { /** @@ -739,7 +742,7 @@ class Builds extends Action * @param Document $function * @param string $deploymentId * @param Database $dbForProject - * @param Database $dbForConsole + * @param Database $dbForPlatform * @return void * @throws Structure * @throws \Utopia\Database\Exception @@ -747,7 +750,7 @@ class Builds extends Action * @throws Conflict * @throws Restricted */ - protected function runGitAction(string $status, GitHub $github, string $providerCommitHash, string $owner, string $repositoryName, Document $project, Document $function, string $deploymentId, Database $dbForProject, Database $dbForConsole): void + protected function runGitAction(string $status, GitHub $github, string $providerCommitHash, string $owner, string $repositoryName, Document $project, Document $function, string $deploymentId, Database $dbForProject, Database $dbForPlatform): void { if ($function->getAttribute('providerSilentMode', false) === true) { return; @@ -792,7 +795,7 @@ class Builds extends Action $retries++; try { - $dbForConsole->createDocument('vcsCommentLocks', new Document([ + $dbForPlatform->createDocument('vcsCommentLocks', new Document([ '$id' => $commentId ])); break; @@ -812,7 +815,7 @@ class Builds extends Action $comment->addBuild($project, $function, $status, $deployment->getId(), ['type' => 'logs']); $github->updateComment($owner, $repositoryName, $commentId, $comment->generateComment()); } finally { - $dbForConsole->deleteDocument('vcsCommentLocks', $commentId); + $dbForPlatform->deleteDocument('vcsCommentLocks', $commentId); } } } diff --git a/src/Appwrite/Platform/Workers/Certificates.php b/src/Appwrite/Platform/Workers/Certificates.php index 58dc1dd28a..0ae40b2df0 100644 --- a/src/Appwrite/Platform/Workers/Certificates.php +++ b/src/Appwrite/Platform/Workers/Certificates.php @@ -2,6 +2,7 @@ namespace Appwrite\Platform\Workers; +use Appwrite\Certificates\Adapter as CertificatesAdapter; use Appwrite\Event\Event; use Appwrite\Event\Func; use Appwrite\Event\Mail; @@ -11,7 +12,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; @@ -43,26 +43,31 @@ class Certificates extends Action $this ->desc('Certificates worker') ->inject('message') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('queueForMails') ->inject('queueForEvents') ->inject('queueForFunctions') ->inject('log') - ->callback(fn (Message $message, Database $dbForConsole, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, Log $log) => $this->action($message, $dbForConsole, $queueForMails, $queueForEvents, $queueForFunctions, $log)); + ->inject('certificates') + ->callback( + fn (Message $message, Database $dbForPlatform, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, Log $log, CertificatesAdapter $certificates) => + $this->action($message, $dbForPlatform, $queueForMails, $queueForEvents, $queueForFunctions, $log, $certificates) + ); } /** * @param Message $message - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param Mail $queueForMails * @param Event $queueForEvents * @param Func $queueForFunctions * @param Log $log + * @param CertificatesAdapter $certificates * @return void * @throws Throwable * @throws \Utopia\Database\Exception */ - public function action(Message $message, Database $dbForConsole, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, Log $log): void + public function action(Message $message, Database $dbForPlatform, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, Log $log, CertificatesAdapter $certificates): void { $payload = $message->getPayload() ?? []; @@ -76,33 +81,33 @@ class Certificates extends Action $log->addTag('domain', $domain->get()); - $this->execute($domain, $dbForConsole, $queueForMails, $queueForEvents, $queueForFunctions, $log, $skipRenewCheck); + $this->execute($domain, $dbForPlatform, $queueForMails, $queueForEvents, $queueForFunctions, $log, $certificates, $skipRenewCheck); } /** * @param Domain $domain - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param Mail $queueForMails * @param Event $queueForEvents * @param Func $queueForFunctions + * @param CertificatesAdapter $certificates * @param bool $skipRenewCheck * @return void * @throws Throwable * @throws \Utopia\Database\Exception */ - private function execute(Domain $domain, Database $dbForConsole, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, Log $log, bool $skipRenewCheck = false): void + private function execute(Domain $domain, Database $dbForPlatform, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, Log $log, CertificatesAdapter $certificates, bool $skipRenewCheck = false): void { /** * 1. Read arguments and validate domain * 2. Get main domain * 3. Validate CNAME DNS if parameter is not main domain (meaning it's custom domain) - * 4. Validate security email. Cannot be empty, required by LetsEncrypt - * 5. Validate renew date with certificate file, unless requested to skip by parameter - * 6. Issue a certificate using certbot CLI - * 7. Update 'log' attribute on certificate document with Certbot message - * 8. Create storage folder for certificate, if not ready already - * 9. Move certificates from Certbot location to our Storage - * 10. Create/Update our Storage with new Traefik config with new certificate paths + * 4. Validate renew date with certificate file, unless requested to skip by parameter + * 5. Issue a certificate using certbot CLI + * 6. Update 'log' attribute on certificate document with Certbot message + * 7. Create storage folder for certificate, if not ready already + * 8. Move certificates from Certbot location to our Storage + * 9. Create/Update our Storage with new Traefik config with new certificate paths * 11. Read certificate file and update 'renewDate' on certificate document * 12. Update 'issueDate' and 'attempts' on certificate * @@ -119,14 +124,14 @@ class Certificates extends Action * 2. Save document to database * 3. Update all domains documents with current certificate ID * - * Note: Renewals are checked and scheduled from maintenence worker + * Note: Renewals are checked and scheduled from maintenance worker */ // Get current certificate - $certificate = $dbForConsole->findOne('certificates', [Query::equal('domain', [$domain->get()])]); + $certificate = $dbForPlatform->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()); } @@ -134,40 +139,27 @@ class Certificates extends Action $success = false; try { - // Email for alerts is required by LetsEncrypt - $email = System::getEnv('_APP_EMAIL_CERTIFICATES', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS')); - if (empty($email)) { - throw new Exception('You must set a valid security email address (_APP_EMAIL_CERTIFICATES) to issue an SSL certificate.'); - } - // Validate domain and DNS records. Skip if job is forced if (!$skipRenewCheck) { $mainDomain = $this->getMainDomain(); $isMainDomain = !isset($mainDomain) || $domain->get() === $mainDomain; $this->validateDomain($domain, $isMainDomain, $log); + + // If certificate exists already, double-check expiry date. Skip if job is forced + if (!$certificates->isRenewRequired($domain->get(), $log)) { + throw new Exception('Renew isn\'t required.'); + } } - // If certificate exists already, double-check expiry date. Skip if job is forced - if (!$skipRenewCheck && !$this->isRenewRequired($domain->get(), $log)) { - throw new Exception('Renew isn\'t required.'); - } - - // Prepare folder name for certbot. Using this helps prevent miss-match in LetsEncrypt configuration when renewing certificate - $folder = ID::unique(); - - // Generate certificate files using Let's Encrypt - $letsEncryptData = $this->issueCertificate($folder, $domain->get(), $email); + // Prepare unique cert name. Using this helps prevent miss-match in configuration when renewing certificates. + $certName = ID::unique(); + $renewDate = $certificates->issueCertificate($certName, $domain->get()); // Command succeeded, store all data into document - $logs = 'Certificate successfully generated.'; - $certificate->setAttribute('logs', \mb_strcut($logs, 0, 1000000));// Limit to 1MB - - - // Give certificates to Traefik - $this->applyCertificateFiles($folder, $domain->get(), $letsEncryptData); + $certificate->setAttribute('logs', 'Certificate successfully generated.'); // Update certificate info stored in database - $certificate->setAttribute('renewDate', $this->getRenewDate($domain->get())); + $certificate->setAttribute('renewDate', $renewDate); $certificate->setAttribute('attempts', 0); $certificate->setAttribute('issueDate', DateTime::now()); $success = true; @@ -181,7 +173,7 @@ class Certificates extends Action $attempts = $certificate->getAttribute('attempts', 0) + 1; $certificate->setAttribute('attempts', $attempts); - // Store cuttent time as renew date to ensure another attempt in next maintenance cycle + // Store current time as renew date to ensure another attempt in next maintenance cycle. $certificate->setAttribute('renewDate', DateTime::now()); // Send email to security email @@ -193,7 +185,7 @@ class Certificates extends Action $certificate->setAttribute('updated', DateTime::now()); // Save all changes we made to certificate document into database - $this->saveCertificateDocument($domain->get(), $certificate, $success, $dbForConsole, $queueForEvents, $queueForFunctions); + $this->saveCertificateDocument($domain->get(), $certificate, $success, $dbForPlatform, $queueForEvents, $queueForFunctions); } } @@ -203,7 +195,7 @@ class Certificates extends Action * @param string $domain Domain name that certificate is for * @param Document $certificate Certificate document that we need to save * @param bool $success - * @param Database $dbForConsole Database connection for console + * @param Database $dbForPlatform Database connection for console * @param Event $queueForEvents * @param Func $queueForFunctions * @return void @@ -212,21 +204,21 @@ class Certificates extends Action * @throws Conflict * @throws Structure */ - private function saveCertificateDocument(string $domain, Document $certificate, bool $success, Database $dbForConsole, Event $queueForEvents, Func $queueForFunctions): void + private function saveCertificateDocument(string $domain, Document $certificate, bool $success, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions): void { // Check if update or insert required - $certificateDocument = $dbForConsole->findOne('certificates', [Query::equal('domain', [$domain])]); - if (!empty($certificateDocument) && !$certificateDocument->isEmpty()) { + $certificateDocument = $dbForPlatform->findOne('certificates', [Query::equal('domain', [$domain])]); + 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); + $certificate = $dbForPlatform->updateDocument('certificates', $certificate->getId(), $certificate); } else { $certificate->removeAttribute('$internalId'); - $certificate = $dbForConsole->createDocument('certificates', $certificate); + $certificate = $dbForPlatform->createDocument('certificates', $certificate); } $certificateId = $certificate->getId(); - $this->updateDomainDocuments($certificateId, $domain, $success, $dbForConsole, $queueForEvents, $queueForFunctions); + $this->updateDomainDocuments($certificateId, $domain, $success, $dbForPlatform, $queueForEvents, $queueForFunctions); } /** @@ -245,8 +237,8 @@ class Certificates extends Action } /** - * Internal domain validation functionality to prevent unnecessary attempts failed from Let's Encrypt side. We check: - * - Domain needs to be public and valid (prevents NFT domains that are not supported by Let's Encrypt) + * Internal domain validation functionality to prevent unnecessary attempts. We check: + * - Domain needs to be public and valid (prevents NFT domains that are not supported) * - Domain must have proper DNS record * * @param Domain $domain Domain which we validate @@ -292,136 +284,6 @@ class Certificates extends Action } } - /** - * Reads expiry date of certificate from file and decides if renewal is required or not. - * - * @param string $domain Domain for which we check certificate file - * @return bool True, if certificate needs to be renewed - * @throws Exception - */ - private function isRenewRequired(string $domain, Log $log): bool - { - $certPath = APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem'; - if (\file_exists($certPath)) { - $validTo = null; - - $certData = openssl_x509_parse(file_get_contents($certPath)); - $validTo = $certData['validTo_time_t'] ?? 0; - - if (empty($validTo)) { - $log->addTag('certificateDomain', $domain); - throw new Exception('Unable to read certificate file (cert.pem).'); - } - - // LetsEncrypt allows renewal 30 days before expiry - $expiryInAdvance = (60 * 60 * 24 * 30); - if ($validTo - $expiryInAdvance > \time()) { - $log->addTag('certificateDomain', $domain); - $log->addExtra('certificateData', \is_array($certData) ? \json_encode($certData) : \strval($certData)); - return false; - } - } - - return true; - } - - /** - * LetsEncrypt communication to issue certificate (using certbot CLI) - * - * @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 - * @throws Exception - */ - private function issueCertificate(string $folder, string $domain, string $email): array - { - $stdout = ''; - $stderr = ''; - - $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}", '', $stdout, $stderr); - - // Unexpected error, usually 5XX, API limits, ... - if ($exit !== 0) { - throw new Exception('Failed to issue a certificate with message: ' . $stderr); - } - - return [ - 'stdout' => $stdout, - 'stderr' => $stderr - ]; - } - - /** - * Read new renew date from certificate file generated by Let's Encrypt - * - * @param string $domain Domain which certificate was generated for - * @return string - * @throws \Utopia\Database\Exception - */ - private function getRenewDate(string $domain): string - { - $certPath = APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem'; - $certData = openssl_x509_parse(file_get_contents($certPath)); - $validTo = $certData['validTo_time_t'] ?? null; - $dt = (new \DateTime())->setTimestamp($validTo); - return DateTime::addSeconds($dt, -60 * 60 * 24 * 30); // -30 days - } - - /** - * Method to take files from Let's Encrypt, and put it into Traefik. - * - * @param string $domain Domain which certificate was generated for - * @param string $folder Folder in which certificates were generated - * @param array $letsEncryptData Let's Encrypt logs to use for additional info when throwing error - * @return void - * @throws Exception - */ - private function applyCertificateFiles(string $folder, string $domain, array $letsEncryptData): void - { - - // Prepare folder in storage for domain - $path = APP_STORAGE_CERTIFICATES . '/' . $domain; - if (!\is_readable($path)) { - if (!\mkdir($path, 0755, true)) { - throw new Exception('Failed to create path for certificate.'); - } - } - - // 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']); - } - - 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']); - } - - 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']); - } - - 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']); - } - - $config = \implode(PHP_EOL, [ - "tls:", - " certificates:", - " - certFile: /storage/certificates/{$domain}/fullchain.pem", - " keyFile: /storage/certificates/{$domain}/privkey.pem" - ]); - - // Save configuration into Traefik using our new cert files - if (!\file_put_contents(APP_STORAGE_CONFIG . '/' . $domain . '.yml', $config)) { - throw new Exception('Failed to save Traefik configuration.'); - } - } - /** * Method to make sure information about error is delivered to admnistrator. * @@ -475,17 +337,21 @@ class Certificates extends Action * * @return void */ - private function updateDomainDocuments(string $certificateId, string $domain, bool $success, Database $dbForConsole, Event $queueForEvents, Func $queueForFunctions): void + private function updateDomainDocuments(string $certificateId, string $domain, bool $success, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions): void { + // TODO: @christyjacob remove once we migrate the rules in 1.7.x + if (System::getEnv('_APP_RULES_FORMAT') === 'md5') { + $rule = $dbForPlatform->getDocument('rules', md5($domain)); + } else { + $rule = $dbForPlatform->findOne('rules', [ + Query::equal('domain', [$domain]), + ]); + } - $rule = $dbForConsole->findOne('rules', [ - 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); + $dbForPlatform->updateDocument('rules', $rule->getId(), $rule); $projectId = $rule->getAttribute('projectId'); @@ -494,7 +360,11 @@ class Certificates extends Action return; } - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); + + if ($project->isEmpty()) { + return; + } /** Trigger Webhook */ $ruleModel = new Rule(); diff --git a/src/Appwrite/Platform/Workers/Databases.php b/src/Appwrite/Platform/Workers/Databases.php index f697e7be13..9c11dd4090 100644 --- a/src/Appwrite/Platform/Workers/Databases.php +++ b/src/Appwrite/Platform/Workers/Databases.php @@ -11,6 +11,7 @@ use Utopia\Database\Document; use Utopia\Database\Exception as DatabaseException; use Utopia\Database\Exception\Authorization; use Utopia\Database\Exception\Conflict; +use Utopia\Database\Exception\NotFound; use Utopia\Database\Exception\Restricted; use Utopia\Database\Exception\Structure; use Utopia\Database\Query; @@ -33,21 +34,21 @@ class Databases extends Action $this ->desc('Databases worker') ->inject('message') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('dbForProject') ->inject('log') - ->callback(fn (Message $message, Database $dbForConsole, Database $dbForProject, Log $log) => $this->action($message, $dbForConsole, $dbForProject, $log)); + ->callback(fn (Message $message, Database $dbForPlatform, Database $dbForProject, Log $log) => $this->action($message, $dbForPlatform, $dbForProject, $log)); } /** * @param Message $message - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param Database $dbForProject * @param Log $log * @return void * @throws \Exception */ - public function action(Message $message, Database $dbForConsole, Database $dbForProject, Log $log): void + public function action(Message $message, Database $dbForPlatform, Database $dbForProject, Log $log): void { $payload = $message->getPayload() ?? []; @@ -73,10 +74,10 @@ class Databases extends Action match (\strval($type)) { DATABASE_TYPE_DELETE_DATABASE => $this->deleteDatabase($database, $project, $dbForProject), DATABASE_TYPE_DELETE_COLLECTION => $this->deleteCollection($database, $collection, $project, $dbForProject), - DATABASE_TYPE_CREATE_ATTRIBUTE => $this->createAttribute($database, $collection, $document, $project, $dbForConsole, $dbForProject), - DATABASE_TYPE_DELETE_ATTRIBUTE => $this->deleteAttribute($database, $collection, $document, $project, $dbForConsole, $dbForProject), - DATABASE_TYPE_CREATE_INDEX => $this->createIndex($database, $collection, $document, $project, $dbForConsole, $dbForProject), - DATABASE_TYPE_DELETE_INDEX => $this->deleteIndex($database, $collection, $document, $project, $dbForConsole, $dbForProject), + DATABASE_TYPE_CREATE_ATTRIBUTE => $this->createAttribute($database, $collection, $document, $project, $dbForPlatform, $dbForProject), + DATABASE_TYPE_DELETE_ATTRIBUTE => $this->deleteAttribute($database, $collection, $document, $project, $dbForPlatform, $dbForProject), + DATABASE_TYPE_CREATE_INDEX => $this->createIndex($database, $collection, $document, $project, $dbForPlatform, $dbForProject), + DATABASE_TYPE_DELETE_INDEX => $this->deleteIndex($database, $collection, $document, $project, $dbForPlatform, $dbForProject), default => throw new \Exception('No database operation for type: ' . \strval($type)), }; } @@ -86,14 +87,15 @@ class Databases extends Action * @param Document $collection * @param Document $attribute * @param Document $project - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param Database $dbForProject * @return void * @throws Authorization * @throws Conflict * @throws \Exception + * @throws \Throwable */ - private function createAttribute(Document $database, Document $collection, Document $attribute, Document $project, Database $dbForConsole, Database $dbForProject): void + private function createAttribute(Document $database, Document $collection, Document $attribute, Document $project, Database $dbForPlatform, Database $dbForProject): void { if ($collection->isEmpty()) { throw new Exception('Missing collection'); @@ -132,8 +134,7 @@ class Databases extends Action $formatOptions = $attribute->getAttribute('formatOptions', []); $filters = $attribute->getAttribute('filters', []); $options = $attribute->getAttribute('options', []); - $project = $dbForConsole->getDocument('projects', $projectId); - + $project = $dbForPlatform->getDocument('projects', $projectId); try { switch ($type) { @@ -170,7 +171,6 @@ class Databases extends Action $dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'available')); } catch (\Throwable $e) { - // TODO: Send non DatabaseExceptions to Sentry Console::error($e->getMessage()); if ($e instanceof DatabaseException) { @@ -193,15 +193,17 @@ class Databases extends Action $relatedAttribute->setAttribute('status', 'failed') ); } + + throw $e; } finally { $this->trigger($database, $collection, $attribute, $project, $projectId, $events); - } - if ($type === Database::VAR_RELATIONSHIP && $options['twoWay']) { - $dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId()); - } + if ($type === Database::VAR_RELATIONSHIP && $options['twoWay']) { + $dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId()); + } - $dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collectionId); + $dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collectionId); + } } /** @@ -209,14 +211,15 @@ class Databases extends Action * @param Document $collection * @param Document $attribute * @param Document $project - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param Database $dbForProject * @return void * @throws Authorization * @throws Conflict * @throws \Exception + * @throws \Throwable **/ - private function deleteAttribute(Document $database, Document $collection, Document $attribute, Document $project, Database $dbForConsole, Database $dbForProject): void + private function deleteAttribute(Document $database, Document $collection, Document $attribute, Document $project, Database $dbForPlatform, Database $dbForProject): void { if ($collection->isEmpty()) { throw new Exception('Missing collection'); @@ -236,7 +239,7 @@ class Databases extends Action $key = $attribute->getAttribute('key', ''); $status = $attribute->getAttribute('status', ''); $type = $attribute->getAttribute('type', ''); - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); $options = $attribute->getAttribute('options', []); $relatedAttribute = new Document(); $relatedCollection = new Document(); @@ -248,7 +251,7 @@ class Databases extends Action // - stuck: attribute was available but cannot be removed try { - if ($status !== 'failed') { + try { if ($type === Database::VAR_RELATIONSHIP) { if ($options['twoWay']) { $relatedCollection = $dbForProject->getDocument('database_' . $database->getInternalId(), $options['relatedCollection']); @@ -265,96 +268,107 @@ class Databases extends Action } elseif (!$dbForProject->deleteAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) { throw new DatabaseException('Failed to delete Attribute'); } - } - $dbForProject->deleteDocument('attributes', $attribute->getId()); + $dbForProject->deleteDocument('attributes', $attribute->getId()); - if (!$relatedAttribute->isEmpty()) { - $dbForProject->deleteDocument('attributes', $relatedAttribute->getId()); - } - } catch (\Throwable $e) { - // TODO: Send non DatabaseExceptions to Sentry - Console::error($e->getMessage()); - - if ($e instanceof DatabaseException) { - $attribute->setAttribute('error', $e->getMessage()); if (!$relatedAttribute->isEmpty()) { - $relatedAttribute->setAttribute('error', $e->getMessage()); + $dbForProject->deleteDocument('attributes', $relatedAttribute->getId()); + } + + } catch (NotFound $e) { + Console::error($e->getMessage()); + + $dbForProject->deleteDocument('attributes', $attribute->getId()); + + if (!$relatedAttribute->isEmpty()) { + $dbForProject->deleteDocument('attributes', $relatedAttribute->getId()); + } + + throw $e; + + } catch (\Throwable $e) { + Console::error($e->getMessage()); + + if ($e instanceof DatabaseException) { + $attribute->setAttribute('error', $e->getMessage()); + if (!$relatedAttribute->isEmpty()) { + $relatedAttribute->setAttribute('error', $e->getMessage()); + } } - } - $dbForProject->updateDocument( - 'attributes', - $attribute->getId(), - $attribute->setAttribute('status', 'stuck') - ); - if (!$relatedAttribute->isEmpty()) { $dbForProject->updateDocument( 'attributes', - $relatedAttribute->getId(), - $relatedAttribute->setAttribute('status', 'stuck') + $attribute->getId(), + $attribute->setAttribute('status', 'stuck') ); + if (!$relatedAttribute->isEmpty()) { + $dbForProject->updateDocument( + 'attributes', + $relatedAttribute->getId(), + $relatedAttribute->setAttribute('status', 'stuck') + ); + } + + throw $e; + } finally { + $this->trigger($database, $collection, $attribute, $project, $projectId, $events); } - } finally { - $this->trigger($database, $collection, $attribute, $project, $projectId, $events); - } - // The underlying database removes/rebuilds indexes when attribute is removed - // Update indexes table with changes - /** @var Document[] $indexes */ - $indexes = $collection->getAttribute('indexes', []); + // The underlying database removes/rebuilds indexes when attribute is removed + // Update indexes table with changes + /** @var Document[] $indexes */ + $indexes = $collection->getAttribute('indexes', []); - foreach ($indexes as $index) { - /** @var string[] $attributes */ - $attributes = $index->getAttribute('attributes'); - $lengths = $index->getAttribute('lengths'); - $orders = $index->getAttribute('orders'); + foreach ($indexes as $index) { + /** @var string[] $attributes */ + $attributes = $index->getAttribute('attributes'); + $lengths = $index->getAttribute('lengths'); + $orders = $index->getAttribute('orders'); - $found = \array_search($key, $attributes); + $found = \array_search($key, $attributes); - if ($found !== false) { - // If found, remove entry from attributes, lengths, and orders - // array_values wraps array_diff to reindex array keys - // when found attribute is removed from array - $attributes = \array_values(\array_diff($attributes, [$attributes[$found]])); - $lengths = \array_values(\array_diff($lengths, isset($lengths[$found]) ? [$lengths[$found]] : [])); - $orders = \array_values(\array_diff($orders, isset($orders[$found]) ? [$orders[$found]] : [])); + if ($found !== false) { + // If found, remove entry from attributes, lengths, and orders + // array_values wraps array_diff to reindex array keys + // when found attribute is removed from array + $attributes = \array_values(\array_diff($attributes, [$attributes[$found]])); + $lengths = \array_values(\array_diff($lengths, isset($lengths[$found]) ? [$lengths[$found]] : [])); + $orders = \array_values(\array_diff($orders, isset($orders[$found]) ? [$orders[$found]] : [])); - if (empty($attributes)) { - $dbForProject->deleteDocument('indexes', $index->getId()); - } else { - $index - ->setAttribute('attributes', $attributes, Document::SET_TYPE_ASSIGN) - ->setAttribute('lengths', $lengths, Document::SET_TYPE_ASSIGN) - ->setAttribute('orders', $orders, Document::SET_TYPE_ASSIGN); - - // Check if an index exists with the same attributes and orders - $exists = false; - foreach ($indexes as $existing) { - if ( - $existing->getAttribute('key') !== $index->getAttribute('key') // Ignore itself - && $existing->getAttribute('attributes') === $index->getAttribute('attributes') - && $existing->getAttribute('orders') === $index->getAttribute('orders') - ) { - $exists = true; - break; - } - } - - if ($exists) { // Delete the duplicate if created, else update in db - $this->deleteIndex($database, $collection, $index, $project, $dbForConsole, $dbForProject); + if (empty($attributes)) { + $dbForProject->deleteDocument('indexes', $index->getId()); } else { - $dbForProject->updateDocument('indexes', $index->getId(), $index); + $index + ->setAttribute('attributes', $attributes, Document::SET_TYPE_ASSIGN) + ->setAttribute('lengths', $lengths, Document::SET_TYPE_ASSIGN) + ->setAttribute('orders', $orders, Document::SET_TYPE_ASSIGN); + + // Check if an index exists with the same attributes and orders + $exists = false; + foreach ($indexes as $existing) { + if ( + $existing->getAttribute('key') !== $index->getAttribute('key') // Ignore itself + && $existing->getAttribute('attributes') === $index->getAttribute('attributes') + && $existing->getAttribute('orders') === $index->getAttribute('orders') + ) { + $exists = true; + break; + } + } + + if ($exists) { // Delete the duplicate if created, else update in db + $this->deleteIndex($database, $collection, $index, $project, $dbForPlatform, $dbForProject); + } else { + $dbForProject->updateDocument('indexes', $index->getId(), $index); + } } } } - } + } finally { + $dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collectionId); - $dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collectionId); - $dbForProject->purgeCachedCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId()); - - if (!$relatedCollection->isEmpty() && !$relatedAttribute->isEmpty()) { - $dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId()); - $dbForProject->purgeCachedCollection('database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId()); + if (!$relatedCollection->isEmpty() && !$relatedAttribute->isEmpty()) { + $dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId()); + } } } @@ -363,15 +377,16 @@ class Databases extends Action * @param Document $collection * @param Document $index * @param Document $project - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param Database $dbForProject * @return void * @throws Authorization * @throws Conflict * @throws Structure * @throws DatabaseException + * @throws \Throwable */ - private function createIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForConsole, Database $dbForProject): void + private function createIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject): void { if ($collection->isEmpty()) { throw new Exception('Missing collection'); @@ -393,7 +408,7 @@ class Databases extends Action $attributes = $index->getAttribute('attributes', []); $lengths = $index->getAttribute('lengths', []); $orders = $index->getAttribute('orders', []); - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); try { if (!$dbForProject->createIndex('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key, $type, $attributes, $lengths, $orders)) { @@ -401,9 +416,7 @@ class Databases extends Action } $dbForProject->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'available')); } catch (\Throwable $e) { - // TODO: Send non DatabaseExceptions to Sentry Console::error($e->getMessage()); - if ($e instanceof DatabaseException) { $index->setAttribute('error', $e->getMessage()); } @@ -412,11 +425,12 @@ class Databases extends Action $index->getId(), $index->setAttribute('status', 'failed') ); + + throw $e; } finally { $this->trigger($database, $collection, $index, $project, $projectId, $events); + $dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collectionId); } - - $dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collectionId); } /** @@ -424,15 +438,16 @@ class Databases extends Action * @param Document $collection * @param Document $index * @param Document $project - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param Database $dbForProject * @return void * @throws Authorization * @throws Conflict * @throws Structure * @throws DatabaseException + * @throws \Throwable */ - private function deleteIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForConsole, Database $dbForProject): void + private function deleteIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject): void { if ($collection->isEmpty()) { throw new Exception('Missing collection'); @@ -450,7 +465,7 @@ class Databases extends Action ]); $key = $index->getAttribute('key'); $status = $index->getAttribute('status', ''); - $project = $dbForConsole->getDocument('projects', $projectId); + $project = $dbForPlatform->getDocument('projects', $projectId); try { if ($status !== 'failed' && !$dbForProject->deleteIndex('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) { @@ -459,7 +474,6 @@ class Databases extends Action $dbForProject->deleteDocument('indexes', $index->getId()); $index->setAttribute('status', 'deleted'); } catch (\Throwable $e) { - // TODO: Send non DatabaseExceptions to Sentry Console::error($e->getMessage()); if ($e instanceof DatabaseException) { @@ -470,11 +484,13 @@ class Databases extends Action $index->getId(), $index->setAttribute('status', 'stuck') ); + + throw $e; + } finally { $this->trigger($database, $collection, $index, $project, $projectId, $events); + $dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collection->getId()); } - - $dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collection->getId()); } /** diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 48e4014f1e..f286ed4c4c 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -3,6 +3,7 @@ namespace Appwrite\Platform\Workers; use Appwrite\Auth\Auth; +use Appwrite\Certificates\Adapter as CertificatesAdapter; use Appwrite\Extend\Exception; use Executor\Executor; use Throwable; @@ -44,24 +45,28 @@ class Deletes extends Action $this ->desc('Deletes worker') ->inject('message') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('getProjectDB') ->inject('deviceForFiles') ->inject('deviceForFunctions') ->inject('deviceForBuilds') ->inject('deviceForCache') + ->inject('certificates') ->inject('abuseRetention') ->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)); + ->callback( + fn ($message, $dbForPlatform, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, string $abuseRetention, string $executionRetention, string $auditRetention, Log $log) => + $this->action($message, $dbForPlatform, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $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): void + public function action(Message $message, Database $dbForPlatform, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, string $abuseRetention, string $executionRetention, string $auditRetention, Log $log): void { $payload = $message->getPayload() ?? []; @@ -84,10 +89,10 @@ class Deletes extends Action case DELETE_TYPE_DOCUMENT: switch ($document->getCollection()) { case DELETE_TYPE_PROJECTS: - $this->deleteProject($dbForConsole, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $document); + $this->deleteProject($dbForPlatform, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $document); break; case DELETE_TYPE_FUNCTIONS: - $this->deleteFunction($dbForConsole, $getProjectDB, $deviceForFunctions, $deviceForBuilds, $document, $project); + $this->deleteFunction($dbForPlatform, $getProjectDB, $deviceForFunctions, $deviceForBuilds, $certificates, $document, $project); break; case DELETE_TYPE_DEPLOYMENTS: $this->deleteDeployment($getProjectDB, $deviceForFunctions, $deviceForBuilds, $document, $project); @@ -99,10 +104,10 @@ class Deletes extends Action $this->deleteBucket($getProjectDB, $deviceForFiles, $document, $project); break; case DELETE_TYPE_INSTALLATIONS: - $this->deleteInstallation($dbForConsole, $getProjectDB, $document, $project); + $this->deleteInstallation($dbForPlatform, $getProjectDB, $document, $project); break; case DELETE_TYPE_RULES: - $this->deleteRule($dbForConsole, $document); + $this->deleteRule($dbForPlatform, $document, $certificates); break; default: Console::error('No lazy delete operation available for document of type: ' . $document->getCollection()); @@ -110,7 +115,7 @@ class Deletes extends Action } break; case DELETE_TYPE_TEAM_PROJECTS: - $this->deleteProjectsByTeam($dbForConsole, $getProjectDB, $document); + $this->deleteProjectsByTeam($dbForPlatform, $getProjectDB, $certificates, $document); break; case DELETE_TYPE_EXECUTIONS: $this->deleteExecutionLogs($project, $getProjectDB, $executionRetention); @@ -124,7 +129,7 @@ class Deletes extends Action $this->deleteAbuseLogs($project, $getProjectDB, $abuseRetention); break; case DELETE_TYPE_REALTIME: - $this->deleteRealtimeUsage($dbForConsole, $datetime); + $this->deleteRealtimeUsage($dbForPlatform, $datetime); break; case DELETE_TYPE_SESSIONS: $this->deleteExpiredSessions($project, $getProjectDB); @@ -139,7 +144,7 @@ class Deletes extends Action $this->deleteCacheByDate($project, $getProjectDB, $datetime); break; case DELETE_TYPE_SCHEDULES: - $this->deleteSchedules($dbForConsole, $getProjectDB, $datetime); + $this->deleteSchedules($dbForPlatform, $getProjectDB, $datetime); break; case DELETE_TYPE_TOPIC: $this->deleteTopic($project, $getProjectDB, $document); @@ -159,7 +164,7 @@ class Deletes extends Action } /** - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param callable $getProjectDB * @param string $datetime * @param Document|null $document @@ -170,7 +175,7 @@ class Deletes extends Action * @throws Structure * @throws DatabaseException */ - private function deleteSchedules(Database $dbForConsole, callable $getProjectDB, string $datetime): void + private function deleteSchedules(Database $dbForPlatform, callable $getProjectDB, string $datetime): void { $this->listByGroup( 'schedules', @@ -179,12 +184,12 @@ class Deletes extends Action Query::lessThanEqual('resourceUpdatedAt', $datetime), Query::equal('active', [false]), ], - $dbForConsole, - function (Document $document) use ($dbForConsole, $getProjectDB) { - $project = $dbForConsole->getDocument('projects', $document->getAttribute('projectId')); + $dbForPlatform, + function (Document $document) use ($dbForPlatform, $getProjectDB) { + $project = $dbForPlatform->getDocument('projects', $document->getAttribute('projectId')); if ($project->isEmpty()) { - $dbForConsole->deleteDocument('schedules', $document->getId()); + $dbForPlatform->deleteDocument('schedules', $document->getId()); Console::success('Deleted schedule for deleted project ' . $document->getAttribute('projectId')); return; } @@ -208,7 +213,7 @@ class Deletes extends Action } if ($delete) { - $dbForConsole->deleteDocument('schedules', $document->getId()); + $dbForPlatform->deleteDocument('schedules', $document->getId()); Console::success('Deleting schedule for ' . $document->getAttribute('resourceType') . ' ' . $document->getAttribute('resourceId')); } } @@ -264,7 +269,7 @@ class Deletes extends Action MESSAGE_TYPE_EMAIL => 'emailTotal', MESSAGE_TYPE_SMS => 'smsTotal', MESSAGE_TYPE_PUSH => 'pushTotal', - default => throw new Exception('Invalid target provider type'), + default => throw new Exception('Invalid target CertificatesAdapter type'), }; $dbForProject->decreaseDocumentAttribute( 'topics', @@ -389,7 +394,7 @@ class Deletes extends Action } /** - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param callable $getProjectDB * @param string $hourlyUsageRetentionDatetime * @return void @@ -432,7 +437,7 @@ class Deletes extends Action } /** - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param Document $document * @return void * @throws Authorization @@ -442,10 +447,10 @@ class Deletes extends Action * @throws Structure * @throws Exception */ - private function deleteProjectsByTeam(Database $dbForConsole, callable $getProjectDB, Document $document): void + private function deleteProjectsByTeam(Database $dbForPlatform, callable $getProjectDB, CertificatesAdapter $certificates, Document $document): void { - $projects = $dbForConsole->find('projects', [ + $projects = $dbForPlatform->find('projects', [ Query::equal('teamInternalId', [$document->getInternalId()]) ]); @@ -455,13 +460,13 @@ class Deletes extends Action $deviceForBuilds = getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId()); $deviceForCache = getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId()); - $this->deleteProject($dbForConsole, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $project); - $dbForConsole->deleteDocument('projects', $project->getId()); + $this->deleteProject($dbForPlatform, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $project); + $dbForPlatform->deleteDocument('projects', $project->getId()); } } /** - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param callable $getProjectDB * @param Device $deviceForFiles * @param Device $deviceForFunctions @@ -473,7 +478,7 @@ class Deletes extends Action * @throws Authorization * @throws DatabaseException */ - private function deleteProject(Database $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, Document $document): void + private function deleteProject(Database $dbForPlatform, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, Document $document): void { $projectInternalId = $document->getInternalId(); $projectId = $document->getId(); @@ -495,29 +500,30 @@ class Deletes extends Action $limit = \count($projectCollectionIds) + 25; + $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); + $sharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES_V1', '')); + + $projectTables = !\in_array($dsn->getHost(), $sharedTables); + $sharedTablesV1 = \in_array($dsn->getHost(), $sharedTablesV1); + $sharedTablesV2 = !$projectTables && !$sharedTablesV1; + $sharedTables = $sharedTablesV1 || $sharedTablesV2; + while (true) { $collections = $dbForProject->listCollections($limit); foreach ($collections as $collection) { - if ($dsn->getHost() !== System::getEnv('_APP_DATABASE_SHARED_TABLES', '') || !\in_array($collection->getId(), $projectCollectionIds)) { - try { + try { + if ($projectTables || !\in_array($collection->getId(), $projectCollectionIds)) { $dbForProject->deleteCollection($collection->getId()); - } catch (Throwable $e) { - Console::error('Error deleting '.$collection->getId().' '.$e->getMessage()); - - /** - * Ignore junction tables; - */ - if (!preg_match('/^_\d+_\d+$/', $collection->getId())) { - throw $e; - } + } else { + $this->deleteByGroup($collection->getId(), [], database: $dbForProject); } - } else { - $this->deleteByGroup($collection->getId(), [], database: $dbForProject); + } catch (Throwable $e) { + Console::error('Error deleting '.$collection->getId().' '.$e->getMessage()); } } - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + if ($sharedTables) { $collectionsIds = \array_map(fn ($collection) => $collection->getId(), $collections); if (empty(\array_diff($collectionsIds, $projectCollectionIds))) { @@ -531,50 +537,57 @@ class Deletes extends Action // Delete Platforms $this->deleteByGroup('platforms', [ Query::equal('projectInternalId', [$projectInternalId]) - ], $dbForConsole); + ], $dbForPlatform); // Delete project and function rules $this->deleteByGroup('rules', [ Query::equal('projectInternalId', [$projectInternalId]) - ], $dbForConsole, function (Document $document) use ($dbForConsole) { - $this->deleteRule($dbForConsole, $document); + ], $dbForPlatform, function (Document $document) use ($dbForPlatform, $certificates) { + $this->deleteRule($dbForPlatform, $document, $certificates); }); // Delete Keys $this->deleteByGroup('keys', [ Query::equal('projectInternalId', [$projectInternalId]) - ], $dbForConsole); + ], $dbForPlatform); // Delete Webhooks $this->deleteByGroup('webhooks', [ Query::equal('projectInternalId', [$projectInternalId]) - ], $dbForConsole); + ], $dbForPlatform); // Delete VCS Installations $this->deleteByGroup('installations', [ Query::equal('projectInternalId', [$projectInternalId]) - ], $dbForConsole); + ], $dbForPlatform); // Delete VCS Repositories $this->deleteByGroup('repositories', [ Query::equal('projectInternalId', [$projectInternalId]), - ], $dbForConsole); + ], $dbForPlatform); // Delete VCS comments $this->deleteByGroup('vcsComments', [ Query::equal('projectInternalId', [$projectInternalId]), - ], $dbForConsole); + ], $dbForPlatform); // Delete Schedules (No projectInternalId in this collection) $this->deleteByGroup('schedules', [ Query::equal('projectId', [$projectId]), - ], $dbForConsole); + ], $dbForPlatform); // Delete metadata table - if ($dsn->getHost() !== System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { - $dbForProject->deleteCollection('_metadata'); - } else { - $this->deleteByGroup('_metadata', [], $dbForProject); + if ($projectTables) { + $dbForProject->deleteCollection(Database::METADATA); + } elseif ($sharedTablesV1) { + $this->deleteByGroup(Database::METADATA, [], $dbForProject); + } elseif ($sharedTablesV2) { + $queries = \array_map( + fn ($id) => Query::notEqual('$id', $id), + $projectCollectionIds + ); + + $this->deleteByGroup(Database::METADATA, $queries, $dbForProject); } // Delete all storage directories @@ -641,7 +654,7 @@ class Deletes extends Action } /** - * @param database $dbForConsole + * @param database $dbForPlatform * @param callable $getProjectDB * @param string $datetime * @return void @@ -657,7 +670,7 @@ class Deletes extends Action } /** - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param callable $getProjectDB * @return void * @throws Exception|Throwable @@ -675,21 +688,21 @@ class Deletes extends Action } /** - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param string $datetime * @return void * @throws Exception */ - private function deleteRealtimeUsage(Database $dbForConsole, string $datetime): void + private function deleteRealtimeUsage(Database $dbForPlatform, string $datetime): void { // Delete Dead Realtime Logs $this->deleteByGroup('realtime', [ Query::lessThan('timestamp', $datetime) - ], $dbForConsole); + ], $dbForPlatform); } /** - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param callable $getProjectDB * @param string $datetime * @return void @@ -710,7 +723,7 @@ class Deletes extends Action } /** - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param callable $getProjectDB * @param string $datetime * @return void @@ -738,7 +751,7 @@ class Deletes extends Action * @return void * @throws Exception */ - private function deleteFunction(Database $dbForConsole, callable $getProjectDB, Device $deviceForFunctions, Device $deviceForBuilds, Document $document, Document $project): void + private function deleteFunction(Database $dbForPlatform, callable $getProjectDB, Device $deviceForFunctions, Device $deviceForBuilds, CertificatesAdapter $certificates, Document $document, Document $project): void { $projectId = $project->getId(); $dbForProject = $getProjectDB($project); @@ -753,8 +766,8 @@ class Deletes extends Action Query::equal('resourceType', ['function']), Query::equal('resourceInternalId', [$functionInternalId]), Query::equal('projectInternalId', [$project->getInternalId()]) - ], $dbForConsole, function (Document $document) use ($project, $dbForConsole) { - $this->deleteRule($dbForConsole, $document); + ], $dbForPlatform, function (Document $document) use ($project, $dbForPlatform, $certificates) { + $this->deleteRule($dbForPlatform, $document, $certificates); }); /** @@ -808,13 +821,13 @@ class Deletes extends Action Query::equal('projectInternalId', [$project->getInternalId()]), Query::equal('resourceInternalId', [$functionInternalId]), Query::equal('resourceType', ['function']), - ], $dbForConsole, function (Document $document) use ($dbForConsole) { + ], $dbForPlatform, function (Document $document) use ($dbForPlatform) { $providerRepositoryId = $document->getAttribute('providerRepositoryId', ''); $projectInternalId = $document->getAttribute('projectInternalId', ''); $this->deleteByGroup('vcsComments', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::equal('projectInternalId', [$projectInternalId]), - ], $dbForConsole); + ], $dbForPlatform); }); /** @@ -1036,29 +1049,18 @@ class Deletes extends Action } /** - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param Document $document rule document * @return void */ - private function deleteRule(Database $dbForConsole, Document $document): void + private function deleteRule(Database $dbForPlatform, Document $document, CertificatesAdapter $certificates): void { - $domain = $document->getAttribute('domain'); - $directory = APP_STORAGE_CERTIFICATES . '/' . $domain; - $checkTraversal = realpath($directory) === $directory; - - if ($checkTraversal && is_dir($directory)) { - // Delete files, so Traefik is aware of change - array_map('unlink', glob($directory . '/*.*')); - rmdir($directory); - Console::info("Deleted certificate files for {$domain}"); - } else { - Console::info("No certificate files found for {$domain}"); - } + $certificates->deleteCertificate($domain); // Delete certificate document, so Appwrite is aware of change if (isset($document['certificateId'])) { - $dbForConsole->deleteDocument('certificates', $document['certificateId']); + $dbForPlatform->deleteDocument('certificates', $document['certificateId']); } } @@ -1079,21 +1081,21 @@ class Deletes extends Action } /** - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param callable $getProjectDB * @param Document $document * @param Document $project * @return void * @throws Exception */ - private function deleteInstallation(Database $dbForConsole, callable $getProjectDB, Document $document, Document $project): void + private function deleteInstallation(Database $dbForPlatform, callable $getProjectDB, Document $document, Document $project): void { $dbForProject = $getProjectDB($project); $this->listByGroup('functions', [ Query::equal('installationInternalId', [$document->getInternalId()]) - ], $dbForProject, function ($function) use ($dbForProject, $dbForConsole) { - $dbForConsole->deleteDocument('repositories', $function->getAttribute('repositoryId')); + ], $dbForProject, function ($function) use ($dbForProject, $dbForPlatform) { + $dbForPlatform->deleteDocument('repositories', $function->getAttribute('repositoryId')); $function = $function ->setAttribute('installationId', '') diff --git a/src/Appwrite/Platform/Workers/Functions.php b/src/Appwrite/Platform/Workers/Functions.php index 7e548f57be..72a3334f2f 100644 --- a/src/Appwrite/Platform/Workers/Functions.php +++ b/src/Appwrite/Platform/Workers/Functions.php @@ -41,29 +41,18 @@ 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 Message $message - * @param Database $dbForProject - * @param Func $queueForFunctions - * @param Event $queueForEvents - * @param Usage $queueForUsage - * @param Log $log - * @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,17 +62,16 @@ 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'] ?? []); @@ -121,7 +109,6 @@ class Functions extends Action $limit = 30; $sum = 30; $offset = 0; - /** @var Document[] $functions */ while ($sum >= $limit) { $functions = $dbForProject->find('functions', [ Query::limit($limit), @@ -138,6 +125,12 @@ class Functions extends Action if (!array_intersect($events, $function->getAttribute('events', []))) { continue; } + + if ($isResourceBlocked($project, RESOURCE_TYPE_FUNCTIONS, $function->getId())) { + Console::log('Function ' . $function->getId() . ' is blocked, skipping execution.'); + continue; + } + Console::success('Iterating function: ' . $function->getAttribute('name')); $this->execute( @@ -168,6 +161,11 @@ class Functions extends Action return; } + if ($isResourceBlocked($project, RESOURCE_TYPE_FUNCTIONS, $function->getId())) { + Console::log('Function ' . $function->getId() . ' is blocked, skipping execution.'); + return; + } + /** * Handle Schedule and HTTP execution. */ diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 510fec0431..58f6265ff4 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.'] diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index beff0b064b..f6af0eb5f2 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -16,7 +16,6 @@ use Utopia\Database\Exception\Conflict; use Utopia\Database\Exception\Restricted; use Utopia\Database\Exception\Structure; use Utopia\Database\Helpers\ID; -use Utopia\Logger\Log; use Utopia\Migration\Destination; use Utopia\Migration\Destinations\Appwrite as DestinationAppwrite; use Utopia\Migration\Exception as MigrationException; @@ -33,10 +32,12 @@ class Migrations extends Action { protected Database $dbForProject; - protected Database $dbForConsole; + protected Database $dbForPlatform; protected Document $project; + protected $logError; + public static function getName(): string { return 'migrations'; @@ -51,15 +52,15 @@ class Migrations extends Action ->desc('Migrations worker') ->inject('message') ->inject('dbForProject') - ->inject('dbForConsole') - ->inject('log') - ->callback(fn (Message $message, Database $dbForProject, Database $dbForConsole, Log $log) => $this->action($message, $dbForProject, $dbForConsole, $log)); + ->inject('dbForPlatform') + ->inject('logError') + ->callback(fn (Message $message, Database $dbForProject, Database $dbForPlatform, callable $logError) => $this->action($message, $dbForProject, $dbForPlatform, $logError)); } /** * @throws Exception */ - public function action(Message $message, Database $dbForProject, Database $dbForConsole, Log $log): void + public function action(Message $message, Database $dbForProject, Database $dbForPlatform, callable $logError): void { $payload = $message->getPayload() ?? []; @@ -76,8 +77,9 @@ class Migrations extends Action } $this->dbForProject = $dbForProject; - $this->dbForConsole = $dbForConsole; + $this->dbForPlatform = $dbForPlatform; $this->project = $project; + $this->logError = $logError; /** * Handle Event execution. @@ -86,10 +88,7 @@ class Migrations extends Action return; } - $log->addTag('migrationId', $migration->getId()); - $log->addTag('projectId', $project->getId()); - - $this->processMigration($migration, $log); + $this->processMigration($migration); } /** @@ -124,7 +123,7 @@ class Migrations extends Action ), SourceAppwrite::getName() => new SourceAppwrite( $credentials['projectId'], - $credentials['endpoint'], + $credentials['endpoint'] === 'http://localhost/v1' ? 'http://appwrite/v1' : $credentials['endpoint'], $credentials['apiKey'], ), default => throw new \Exception('Invalid source type'), @@ -134,16 +133,15 @@ class Migrations extends Action /** * @throws Exception */ - protected function processDestination(Document $migration): Destination + protected function processDestination(Document $migration, string $apiKey): Destination { $destination = $migration->getAttribute('destination'); - $credentials = $migration->getAttribute('credentials'); return match ($destination) { DestinationAppwrite::getName() => new DestinationAppwrite( - $credentials['projectId'], - $credentials['endpoint'], - $credentials['apiKey'], + $this->project->getId(), + 'http://appwrite/v1', + $apiKey, $this->dbForProject, Config::getParam('collections', [])['databases']['collections'], ), @@ -199,7 +197,7 @@ class Migrations extends Action */ protected function removeAPIKey(Document $apiKey): void { - $this->dbForConsole->deleteDocument('keys', $apiKey->getId()); + $this->dbForPlatform->deleteDocument('keys', $apiKey->getId()); } /** @@ -246,8 +244,8 @@ class Migrations extends Action 'secret' => $generatedSecret, ]); - $this->dbForConsole->createDocument('keys', $key); - $this->dbForConsole->purgeCachedDocument('projects', $project->getId()); + $this->dbForPlatform->createDocument('keys', $key); + $this->dbForPlatform->purgeCachedDocument('projects', $project->getId()); return $key; } @@ -260,20 +258,18 @@ class Migrations extends Action * @throws \Utopia\Database\Exception * @throws Exception */ - protected function processMigration(Document $migration, Log $log): void + protected function processMigration(Document $migration): void { $project = $this->project; - $projectDocument = $this->dbForConsole->getDocument('projects', $project->getId()); + $projectDocument = $this->dbForPlatform->getDocument('projects', $project->getId()); $tempAPIKey = $this->generateAPIKey($projectDocument); $transfer = $source = $destination = null; try { - $migration = $this->dbForProject->getDocument('migrations', $migration->getId()); - if ( - $migration->getAttribute('source') === SourceAppwrite::getName() || - $migration->getAttribute('destination') === DestinationAppwrite::getName() + $migration->getAttribute('source') === SourceAppwrite::getName() && + empty($migration->getAttribute('credentials', [])) ) { $credentials = $migration->getAttribute('credentials', []); @@ -288,10 +284,8 @@ class Migrations extends Action $migration->setAttribute('status', 'processing'); $this->updateMigrationDocument($migration, $projectDocument); - $log->addTag('type', $migration->getAttribute('source')); - $source = $this->processSource($migration); - $destination = $this->processDestination($migration); + $destination = $this->processDestination($migration, $tempAPIKey->getAttribute('secret')); $source->report(); @@ -327,7 +321,6 @@ class Migrations extends Action $errorMessages = []; foreach ($sourceErrors as $error) { - /** @var $sourceErrors $error */ $message = "Error occurred while fetching '{$error->getResourceName()}:{$error->getResourceId()}' from source with message: '{$error->getMessage()}'"; if ($error->getPrevious()) { $message .= " Message: ".$error->getPrevious()->getMessage() . " File: ".$error->getPrevious()->getFile() . " Line: ".$error->getPrevious()->getLine(); @@ -347,7 +340,6 @@ class Migrations extends Action } $migration->setAttribute('errors', $errorMessages); - $log->addExtra('migrationErrors', json_encode($errorMessages)); $this->updateMigrationDocument($migration, $projectDocument); return; @@ -362,7 +354,12 @@ class Migrations extends Action if (! $migration->isEmpty()) { $migration->setAttribute('status', 'failed'); $migration->setAttribute('stage', 'finished'); - $migration->setAttribute('errors', [$th->getMessage()]); + + call_user_func($this->logError, $th, 'appwrite-worker', 'appwrite-queue-'.self::getName(), [ + 'migrationId' => $migration->getId(), + 'source' => $migration->getAttribute('source') ?? '', + 'destination' => $migration->getAttribute('destination') ?? '', + ]); return; } @@ -382,7 +379,6 @@ class Migrations extends Action } $migration->setAttribute('errors', $errorMessages); - $log->addTag('migrationErrors', json_encode($errorMessages)); } } finally { if (! $tempAPIKey->isEmpty()) { @@ -394,15 +390,40 @@ class Migrations extends Action if ($migration->getAttribute('status', '') === 'failed') { Console::error('Migration('.$migration->getInternalId().':'.$migration->getId().') failed, Project('.$this->project->getInternalId().':'.$this->project->getId().')'); - $destination->error(); - $source->error(); + if ($destination) { + $destination->error(); - throw new Exception('Migration failed'); + foreach ($destination->getErrors() as $error) { + /** @var MigrationException $error */ + call_user_func($this->logError, $error, 'appwrite-worker', 'appwrite-queue-' . self::getName(), [ + 'migrationId' => $migration->getId(), + 'source' => $migration->getAttribute('source') ?? '', + 'destination' => $migration->getAttribute('destination') ?? '', + 'resourceName' => $error->getResourceName(), + 'resourceGroup' => $error->getResourceGroup() + ]); + } + } + + if ($source) { + $source->error(); + + foreach ($source->getErrors() as $error) { + /** @var MigrationException $error */ + call_user_func($this->logError, $error, 'appwrite-worker', 'appwrite-queue-' . self::getName(), [ + 'migrationId' => $migration->getId(), + 'source' => $migration->getAttribute('source') ?? '', + 'destination' => $migration->getAttribute('destination') ?? '', + 'resourceName' => $error->getResourceName(), + 'resourceGroup' => $error->getResourceGroup() + ]); + } + } } if ($migration->getAttribute('status', '') === 'completed') { - $destination->success(); - $source->success(); + $destination?->success(); + $source?->success(); } } } diff --git a/src/Appwrite/Platform/Workers/Usage.php b/src/Appwrite/Platform/Workers/Usage.php index 034e558d5d..8199fe73a7 100644 --- a/src/Appwrite/Platform/Workers/Usage.php +++ b/src/Appwrite/Platform/Workers/Usage.php @@ -15,9 +15,10 @@ class Usage extends Action { private array $stats = []; private int $lastTriggeredTime = 0; + private int $aggregationInterval = 20; private int $keys = 0; private const INFINITY_PERIOD = '_inf_'; - private const KEYS_THRESHOLD = 10000; + private const KEYS_THRESHOLD = 20000; public static function getName(): string { @@ -39,6 +40,7 @@ class Usage extends Action $this->action($message, $getProjectDB, $queueForUsageDump); }); + $this->aggregationInterval = (int) System::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', '20'); $this->lastTriggeredTime = time(); } @@ -56,10 +58,15 @@ class Usage extends Action if (empty($payload)) { throw new Exception('Missing payload'); } - //Todo Figure out way to preserve keys when the container is being recreated @shimonewman - $aggregationInterval = (int) System::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', '20'); - $project = new Document($payload['project'] ?? []); + $document = $payload['project'] ?? []; + $project = new Document($document); + + if (empty($project->getAttribute('database'))) { + var_dump($payload); + return; + } + $projectId = $project->getInternalId(); foreach ($payload['reduce'] ?? [] as $document) { if (empty($document)) { @@ -74,7 +81,12 @@ class Usage extends Action ); } - $this->stats[$projectId]['project'] = $project; + + $this->stats[$projectId]['project'] = [ + '$id' => $project->getId(), + '$internalId' => $project->getInternalId(), + 'database' => $project->getAttribute('database'), + ]; $this->stats[$projectId]['receivedAt'] = DateTime::now(); foreach ($payload['metrics'] ?? [] as $metric) { $this->keys++; @@ -89,7 +101,7 @@ class Usage extends Action // If keys crossed threshold or X time passed since the last send and there are some keys in the array ($this->stats) if ( $this->keys >= self::KEYS_THRESHOLD || - (time() - $this->lastTriggeredTime > $aggregationInterval && $this->keys > 0) + (time() - $this->lastTriggeredTime > $this->aggregationInterval && $this->keys > 0) ) { Console::warning('[' . DateTime::now() . '] Aggregated ' . $this->keys . ' keys'); diff --git a/src/Appwrite/Platform/Workers/UsageDump.php b/src/Appwrite/Platform/Workers/UsageDump.php index b5480e1dec..3e50ba0363 100644 --- a/src/Appwrite/Platform/Workers/UsageDump.php +++ b/src/Appwrite/Platform/Workers/UsageDump.php @@ -57,16 +57,35 @@ class UsageDump extends Action throw new Exception('Missing payload'); } - // TODO: rename both usage workers @shimonewman + foreach ($payload['stats'] ?? [] as $stats) { - $project = new Document($stats['project'] ?? []); + //$project = new Document($stats['project'] ?? []); + + /** + * Start temp bug fallback + */ + $document = $stats['project'] ?? []; + if (!empty($document['$uid'])) { + $document['$id'] = $document['$uid']; + } + + $project = new Document($document); + + if (empty($project->getAttribute('database'))) { + continue; + } + + /** + * End temp bug fallback + */ + $numberOfKeys = !empty($stats['keys']) ? count($stats['keys']) : 0; $receivedAt = $stats['receivedAt'] ?? 'NONE'; if ($numberOfKeys === 0) { continue; } - console::log('[' . DateTime::now() . '] ProjectId [' . $project->getInternalId() . '] ReceivedAt [' . $receivedAt . '] ' . $numberOfKeys . ' keys'); + console::log('['.DateTime::now().'] Id: '.$project->getId(). ' InternalId: '.$project->getInternalId(). ' Db: '.$project->getAttribute('database').' ReceivedAt: '.$receivedAt. ' Keys: '.$numberOfKeys); try { $dbForProject = $getProjectDB($project); diff --git a/src/Appwrite/Platform/Workers/Webhooks.php b/src/Appwrite/Platform/Workers/Webhooks.php index 88ca7871f2..ba0bdaa6a3 100644 --- a/src/Appwrite/Platform/Workers/Webhooks.php +++ b/src/Appwrite/Platform/Workers/Webhooks.php @@ -31,21 +31,21 @@ class Webhooks extends Action $this ->desc('Webhooks worker') ->inject('message') - ->inject('dbForConsole') + ->inject('dbForPlatform') ->inject('queueForMails') ->inject('log') - ->callback(fn (Message $message, Database $dbForConsole, Mail $queueForMails, Log $log) => $this->action($message, $dbForConsole, $queueForMails, $log)); + ->callback(fn (Message $message, Database $dbForPlatform, Mail $queueForMails, Log $log) => $this->action($message, $dbForPlatform, $queueForMails, $log)); } /** * @param Message $message - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param Mail $queueForMails * @param Log $log * @return void * @throws Exception */ - public function action(Message $message, Database $dbForConsole, Mail $queueForMails, Log $log): void + public function action(Message $message, Database $dbForPlatform, Mail $queueForMails, Log $log): void { $this->errors = []; $payload = $message->getPayload() ?? []; @@ -63,7 +63,7 @@ class Webhooks extends Action foreach ($project->getAttribute('webhooks', []) as $webhook) { if (array_intersect($webhook->getAttribute('events', []), $events)) { - $this->execute($events, $webhookPayload, $webhook, $user, $project, $dbForConsole, $queueForMails); + $this->execute($events, $webhookPayload, $webhook, $user, $project, $dbForPlatform, $queueForMails); } } @@ -78,11 +78,11 @@ class Webhooks extends Action * @param Document $webhook * @param Document $user * @param Document $project - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param Mail $queueForMails * @return void */ - private function execute(array $events, string $payload, Document $webhook, Document $user, Document $project, Database $dbForConsole, Mail $queueForMails): void + private function execute(array $events, string $payload, Document $webhook, Document $user, Document $project, Database $dbForPlatform, Mail $queueForMails): void { if ($webhook->getAttribute('enabled') !== true) { return; @@ -138,8 +138,8 @@ class Webhooks extends Action \curl_close($ch); if (!empty($curlError) || $statusCode >= 400) { - $dbForConsole->increaseDocumentAttribute('webhooks', $webhook->getId(), 'attempts', 1); - $webhook = $dbForConsole->getDocument('webhooks', $webhook->getId()); + $dbForPlatform->increaseDocumentAttribute('webhooks', $webhook->getId(), 'attempts', 1); + $webhook = $dbForPlatform->getDocument('webhooks', $webhook->getId()); $attempts = $webhook->getAttribute('attempts'); $logs = ''; @@ -158,17 +158,17 @@ class Webhooks extends Action if ($attempts >= \intval(System::getEnv('_APP_WEBHOOK_MAX_FAILED_ATTEMPTS', '10'))) { $webhook->setAttribute('enabled', false); - $this->sendEmailAlert($attempts, $statusCode, $webhook, $project, $dbForConsole, $queueForMails); + $this->sendEmailAlert($attempts, $statusCode, $webhook, $project, $dbForPlatform, $queueForMails); } - $dbForConsole->updateDocument('webhooks', $webhook->getId(), $webhook); - $dbForConsole->purgeCachedDocument('projects', $project->getId()); + $dbForPlatform->updateDocument('webhooks', $webhook->getId(), $webhook); + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); $this->errors[] = $logs; } else { $webhook->setAttribute('attempts', 0); // Reset attempts on success - $dbForConsole->updateDocument('webhooks', $webhook->getId(), $webhook); - $dbForConsole->purgeCachedDocument('projects', $project->getId()); + $dbForPlatform->updateDocument('webhooks', $webhook->getId(), $webhook); + $dbForPlatform->purgeCachedDocument('projects', $project->getId()); } } @@ -177,20 +177,20 @@ class Webhooks extends Action * @param mixed $statusCode * @param Document $webhook * @param Document $project - * @param Database $dbForConsole + * @param Database $dbForPlatform * @param Mail $queueForMails * @return void */ - public function sendEmailAlert(int $attempts, mixed $statusCode, Document $webhook, Document $project, Database $dbForConsole, Mail $queueForMails): void + public function sendEmailAlert(int $attempts, mixed $statusCode, Document $webhook, Document $project, Database $dbForPlatform, Mail $queueForMails): void { - $memberships = $dbForConsole->find('memberships', [ + $memberships = $dbForPlatform->find('memberships', [ Query::equal('teamInternalId', [$project->getAttribute('teamInternalId')]), Query::limit(APP_LIMIT_SUBQUERY) ]); $userIds = array_column(\array_map(fn ($membership) => $membership->getArrayCopy(), $memberships), 'userId'); - $users = $dbForConsole->find('users', [ + $users = $dbForPlatform->find('users', [ Query::equal('$id', $userIds), ]); diff --git a/src/Appwrite/PubSub/Adapter.php b/src/Appwrite/PubSub/Adapter.php new file mode 100644 index 0000000000..e5ddbe5e62 --- /dev/null +++ b/src/Appwrite/PubSub/Adapter.php @@ -0,0 +1,13 @@ +client = $client; + + } + + public function ping($message = null): bool + { + return $this->client->ping($message); + } + + public function subscribe($channels, $callback) + { + return $this->client->subscribe($channels, $callback); + } + + public function publish($channel, $message) + { + return $this->client->publish($channel, $message); + } +} diff --git a/src/Appwrite/Specification/Format/OpenAPI3.php b/src/Appwrite/Specification/Format/OpenAPI3.php index f7430ec70e..65df0cb694 100644 --- a/src/Appwrite/Specification/Format/OpenAPI3.php +++ b/src/Appwrite/Specification/Format/OpenAPI3.php @@ -323,6 +323,7 @@ class OpenAPI3 extends Format case 'Utopia\Validator\JSON': case 'Utopia\Validator\Mock': case 'Utopia\Validator\Assoc': + case 'Appwrite\Functions\Validator\Payload': $param['default'] = (empty($param['default'])) ? new \stdClass() : $param['default']; $node['schema']['type'] = 'object'; $node['schema']['x-example'] = '{}'; diff --git a/src/Appwrite/Specification/Format/Swagger2.php b/src/Appwrite/Specification/Format/Swagger2.php index f2e324c71f..66a540a4d4 100644 --- a/src/Appwrite/Specification/Format/Swagger2.php +++ b/src/Appwrite/Specification/Format/Swagger2.php @@ -349,6 +349,7 @@ class Swagger2 extends Format case 'Utopia\Validator\JSON': case 'Utopia\Validator\Mock': case 'Utopia\Validator\Assoc': + case 'Appwrite\Functions\Validator\Payload': $node['type'] = 'object'; $node['default'] = (empty($param['default'])) ? new \stdClass() : $param['default']; $node['x-example'] = '{}'; diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php index a3555c08dd..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), + new Filter($attributes, APP_DATABASE_QUERY_MAX_VALUES), new Order($attributes), ]; 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', diff --git a/src/Appwrite/Utopia/Response/Model/Func.php b/src/Appwrite/Utopia/Response/Model/Func.php index f4ff214d0b..ab2d7ba051 100644 --- a/src/Appwrite/Utopia/Response/Model/Func.php +++ b/src/Appwrite/Utopia/Response/Model/Func.php @@ -94,7 +94,7 @@ class Func extends Model ]) ->addRule('schedule', [ 'type' => self::TYPE_STRING, - 'description' => 'Function execution schedult in CRON format.', + 'description' => 'Function execution schedule in CRON format.', 'default' => '', 'example' => '5 4 * * *', ]) diff --git a/src/Appwrite/Utopia/Response/Model/Membership.php b/src/Appwrite/Utopia/Response/Model/Membership.php index 64283bd4a8..46153842bc 100644 --- a/src/Appwrite/Utopia/Response/Model/Membership.php +++ b/src/Appwrite/Utopia/Response/Model/Membership.php @@ -36,13 +36,13 @@ class Membership extends Model ]) ->addRule('userName', [ 'type' => self::TYPE_STRING, - 'description' => 'User name.', + 'description' => 'User name. Hide this attribute by toggling membership privacy in the Console.', 'default' => '', 'example' => 'John Doe', ]) ->addRule('userEmail', [ 'type' => self::TYPE_STRING, - 'description' => 'User email address.', + 'description' => 'User email address. Hide this attribute by toggling membership privacy in the Console.', 'default' => '', 'example' => 'john@appwrite.io', ]) @@ -78,7 +78,7 @@ class Membership extends Model ]) ->addRule('mfa', [ 'type' => self::TYPE_BOOLEAN, - 'description' => 'Multi factor authentication status, true if the user has MFA enabled or false otherwise.', + 'description' => 'Multi factor authentication status, true if the user has MFA enabled or false otherwise. Hide this attribute by toggling membership privacy in the Console.', 'default' => false, 'example' => false, ]) 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 diff --git a/src/Appwrite/Utopia/Response/Model/Project.php b/src/Appwrite/Utopia/Response/Model/Project.php index e1d0105587..6e01baee84 100644 --- a/src/Appwrite/Utopia/Response/Model/Project.php +++ b/src/Appwrite/Utopia/Response/Model/Project.php @@ -151,6 +151,24 @@ class Project extends Model 'default' => false, 'example' => true, ]) + ->addRule('authMembershipsUserName', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Whether or not to show user names in the teams membership response.', + 'default' => false, + 'example' => true, + ]) + ->addRule('authMembershipsUserEmail', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Whether or not to show user emails in the teams membership response.', + 'default' => false, + 'example' => true, + ]) + ->addRule('authMembershipsMfa', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Whether or not to show user MFA status in the teams membership response.', + 'default' => false, + 'example' => true, + ]) ->addRule('oAuthProviders', [ 'type' => Response::MODEL_AUTH_PROVIDER, 'description' => 'List of Auth Providers.', @@ -348,6 +366,9 @@ class Project extends Model $document->setAttribute('authPersonalDataCheck', $authValues['personalDataCheck'] ?? false); $document->setAttribute('authMockNumbers', $authValues['mockNumbers'] ?? []); $document->setAttribute('authSessionAlerts', $authValues['sessionAlerts'] ?? false); + $document->setAttribute('authMembershipsUserName', $authValues['membershipsUserName'] ?? true); + $document->setAttribute('authMembershipsUserEmail', $authValues['membershipsUserEmail'] ?? true); + $document->setAttribute('authMembershipsMfa', $authValues['membershipsMfa'] ?? true); foreach ($auth as $index => $method) { $key = $method['key']; 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/Client.php b/tests/e2e/Client.php index 0774f1c6fd..dc80808b14 100644 --- a/tests/e2e/Client.php +++ b/tests/e2e/Client.php @@ -179,9 +179,14 @@ class Client default => http_build_query($params), }; - foreach ($headers as $i => $header) { - $headers[] = $i . ':' . $header; - unset($headers[$i]); + $formattedHeaders = []; + foreach ($headers as $key => $value) { + if (strtolower($key) === 'accept-encoding') { + curl_setopt($ch, CURLOPT_ENCODING, $value); + continue; + } else { + $formattedHeaders[] = $key . ': ' . $value; + } } curl_setopt($ch, CURLOPT_PATH_AS_IS, 1); @@ -189,7 +194,7 @@ class Client curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HTTPHEADER, $formattedHeaders); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0); curl_setopt($ch, CURLOPT_TIMEOUT, 15); curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders, &$cookies) { @@ -220,7 +225,6 @@ class Client curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); } - $responseBody = curl_exec($ch); $responseType = $responseHeaders['content-type'] ?? ''; $responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE); diff --git a/tests/e2e/General/CompressionTest.php b/tests/e2e/General/CompressionTest.php new file mode 100644 index 0000000000..9affacfe0a --- /dev/null +++ b/tests/e2e/General/CompressionTest.php @@ -0,0 +1,137 @@ +client->call(Client::METHOD_GET, '/ping', [ + 'accept-encoding' => 'gzip', + 'x-appwrite-project' => $this->getProject()['$id'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('Pong!', $response['body']); + $this->assertLessThan(1024, strlen($response['body'])); + $this->assertArrayNotHasKey('content-encoding', $response['headers']); + + // without header + $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']); + $this->assertLessThan(1024, strlen($response['body'])); + $this->assertArrayNotHasKey('content-encoding', $response['headers']); + } + + public function testLargeResponse() + { + // create an anonymous user + $response = $this->client->call(Client::METHOD_POST, '/users', array_merge([ + 'x-appwrite-project' => $this->getProject()['$id'], + 'content-type' => 'application/json', + ], $this->getHeaders()), [ + 'userId' => ID::unique(), + 'email' => 'test@localhost.test', + 'password' => 'password', + 'name' => 'User Name', + ]); + $this->assertEquals(201, $response['headers']['status-code']); + $userId = $response['body']['$id']; + + // set prefs with 2000 bytes of data + $prefs = ["longValue" => str_repeat('a', 2000)]; + + $response = $this->client->call(Client::METHOD_PATCH, '/users/' . $userId . '/prefs', array_merge([ + 'x-appwrite-project' => $this->getProject()['$id'], + 'content-type' => 'application/json', + ], $this->getHeaders()), [ + 'prefs' => $prefs, + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // get prefs with compression + $response = $this->client->call(Client::METHOD_GET, '/users/' . $userId . '/prefs', array_merge([ + 'x-appwrite-project' => $this->getProject()['$id'], + 'accept-encoding' => 'gzip', + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertArrayHasKey('content-encoding', $response['headers'], 'Content encoding should be gzip, headers received: ' . json_encode($response['headers'], JSON_PRETTY_PRINT)); + $this->assertLessThan(2000, intval($response['headers']['content-length'])); + + // get prefs without compression + $response = $this->client->call(Client::METHOD_GET, '/users/' . $userId . '/prefs', array_merge([ + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertGreaterThanOrEqual(2000, intval($response['headers']['content-length'])); + $this->assertArrayNotHasKey('content-encoding', $response['headers']); + } + + public function testImageResponse() + { + // create bucket + $bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'bucketId' => ID::unique(), + 'name' => 'Test Bucket', + 'fileSecurity' => true, + ]); + $bucketId = $bucket['body']['$id']; + $this->assertEquals(201, $bucket['headers']['status-code']); + + // upload image + $file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', array_merge([ + 'content-type' => 'multipart/form-data', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'fileId' => ID::unique(), + 'file' => new CURLFile(realpath(__DIR__ . '/../../resources/logo.png'), 'image/png', 'logo.png'), + 'permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + $fileId = $file['body']['$id']; + + // get image with header + $response = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId, array_merge([ + 'content-type' => 'application/json', + 'accept-encoding' => 'gzip', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertArrayNotHasKey('content-encoding', $response['headers']); + // get image without + $response = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertArrayNotHasKey('content-encoding', $response['headers']); + } +} diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index ad2c93790c..7f84ace6f2 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -94,6 +94,8 @@ trait ProjectCustom 'topics.read', 'subscribers.write', 'subscribers.read', + 'migrations.write', + 'migrations.read' ], ]); diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 244f84b161..cca27cc3be 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -2695,4 +2695,45 @@ class AccountCustomClientTest extends Scope return $data; } + + public function testCreatePushTarget(): void + { + $response = $this->client->call(Client::METHOD_POST, '/account/targets/push', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'] + ], $this->getHeaders()), [ + '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/targets/push', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'targetId' => ID::unique(), + 'identifier' => 'test-identifier-2', + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals('test-identifier-2', $response['body']['identifier']); + + $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'], + ], $this->getHeaders()), [ + '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/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index 04f2dbd8c8..d079cb313c 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -1744,6 +1744,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 []; } @@ -2096,7 +2111,7 @@ trait DatabasesBase */ $conditions = []; - for ($i = 0; $i < 101; $i++) { + for ($i = 0; $i < APP_DATABASE_QUERY_MAX_VALUES + 1; $i++) { $conditions[] = $i; } @@ -2109,7 +2124,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_MAX_VALUES.' values: releaseYear', $documents['body']['message']); $value = ''; diff --git a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php index 7cb8adb815..b501e2119e 100644 --- a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php +++ b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php @@ -1362,7 +1362,7 @@ class DatabasesCustomServerTest extends Scope ]); $this->assertEquals(400, $tooWide['headers']['status-code']); - $this->assertEquals('Attribute limit exceeded', $tooWide['body']['message']); + $this->assertEquals('attribute_limit_exceeded', $tooWide['body']['type']); } public function testIndexLimitException() diff --git a/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php b/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php index b1315103b1..4f4b0c960d 100644 --- a/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php +++ b/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php @@ -1,12 +1,13 @@ 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->assertContains($response['body']['issuerOrganisation'], ['Let\'s Encrypt', 'Google Trust Services']); $this->assertIsInt($response['body']['validFrom']); $this->assertIsInt($response['body']['validTo']); @@ -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']); diff --git a/tests/e2e/Services/Migrations/MigrationsBase.php b/tests/e2e/Services/Migrations/MigrationsBase.php new file mode 100644 index 0000000000..e4c7ba7712 --- /dev/null +++ b/tests/e2e/Services/Migrations/MigrationsBase.php @@ -0,0 +1,895 @@ +getProject(true); + self::$project = $projectBackup; + + return self::$destinationProject; + } + + public function performMigrationSync( + array $body, + ): array { + $migration = $this->client->call(Client::METHOD_POST, '/migrations/appwrite', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ], $body); + + $this->assertEquals(202, $migration['headers']['status-code']); + $this->assertNotEmpty($migration['body']); + $this->assertNotEmpty($migration['body']['$id']); + + $attempts = 0; + while ($attempts < 5) { + $response = $this->client->call(Client::METHOD_GET, '/migrations/' . $migration['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + + if ($response['body']['status'] === 'failed') { + $this->fail('Migration failed', json_encode($response['body'], JSON_PRETTY_PRINT)); + } + + $this->assertNotEquals('failed', $response['body']['status']); + + if ($response['body']['status'] === 'completed') { + return $response['body']; + } + + if ($attempts === 4) { + $this->assertEquals('completed', $response['body']['status']); + } + + $attempts++; + sleep(5); + } + } + + /** + * Appwrite E2E Migration Tests + */ + public function testCreateAppwriteMigration() + { + $response = $this->performMigrationSync([ + 'resources' => Appwrite::getSupportedResources(), + 'endpoint' => 'http://localhost/v1', + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals(Appwrite::getSupportedResources(), $response['resources']); + $this->assertEquals('Appwrite', $response['source']); + $this->assertEquals('Appwrite', $response['destination']); + $this->assertEmpty($response['statusCounters']); + } + + /** + * Auth + */ + public function testAppwriteMigrationAuthUserPassword() + { + $response = $this->client->call(Client::METHOD_POST, '/users', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'userId' => ID::unique(), + 'email' => 'test@test.com', + 'password' => 'password', + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals('test@test.com', $response['body']['email']); + + $user = $response['body']; + + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_USER, + ], + 'endpoint' => 'http://localhost/v1', + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([Resource::TYPE_USER], $result['resources']); + $this->assertArrayHasKey(Resource::TYPE_USER, $result['statusCounters']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_USER]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['warning']); + + $response = $this->client->call(Client::METHOD_GET, '/users/' . $user['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals($user['email'], $response['body']['email']); + $this->assertEquals($user['password'], $response['body']['password']); + + // Cleanup + $this->client->call(Client::METHOD_DELETE, '/users/' . $user['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/users/' . $user['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + } + + public function testAppwriteMigrationAuthUserPhone() + { + $response = $this->client->call(Client::METHOD_POST, '/users', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'userId' => ID::unique(), + 'phone' => '+12065550100', + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals('+12065550100', $response['body']['phone']); + + $user = $response['body']; + + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_USER, + ], + 'endpoint' => 'http://localhost/v1', + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([Resource::TYPE_USER], $result['resources']); + $this->assertArrayHasKey(Resource::TYPE_USER, $result['statusCounters']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_USER]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['warning']); + + $response = $this->client->call(Client::METHOD_GET, '/users/' . $user['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals($user['phone'], $response['body']['phone']); + + // Cleanup + $this->client->call(Client::METHOD_DELETE, '/users/' . $user['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/users/' . $user['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + } + + public function testAppwriteMigrationAuthTeam() + { + $user = $this->client->call(Client::METHOD_POST, '/users', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'userId' => ID::unique(), + 'email' => 'test@test.com', + 'password' => 'password', + ]); + + $this->assertEquals(201, $user['headers']['status-code']); + $this->assertNotEmpty($user['body']); + $this->assertNotEmpty($user['body']['$id']); + $this->assertEquals('test@test.com', $user['body']['email']); + + $team = $this->client->call(Client::METHOD_POST, '/teams', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'teamId' => ID::unique(), + 'name' => 'Test Team', + ]); + + $this->assertEquals(201, $team['headers']['status-code']); + $this->assertNotEmpty($team['body']); + $this->assertNotEmpty($team['body']['$id']); + + $membership = $this->client->call(Client::METHOD_POST, '/teams/' . $team['body']['$id'] . '/memberships', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'teamId' => $team['body']['$id'], + 'userId' => $user['body']['$id'], + 'roles' => ['owner'], + ]); + + $this->assertEquals(201, $membership['headers']['status-code']); + $this->assertNotEmpty($membership['body']); + $this->assertNotEmpty($membership['body']['$id']); + + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_USER, + Resource::TYPE_TEAM, + Resource::TYPE_MEMBERSHIP, + ], + 'endpoint' => 'http://localhost/v1', + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([Resource::TYPE_USER, Resource::TYPE_TEAM, Resource::TYPE_MEMBERSHIP], $result['resources']); + $this->assertArrayHasKey(Resource::TYPE_USER, $result['statusCounters']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_USER]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['warning']); + + $this->assertArrayHasKey(Resource::TYPE_TEAM, $result['statusCounters']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_TEAM]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_TEAM]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_TEAM]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_TEAM]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_TEAM]['warning']); + + $this->assertArrayHasKey(Resource::TYPE_MEMBERSHIP, $result['statusCounters']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_MEMBERSHIP]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_MEMBERSHIP]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_MEMBERSHIP]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_MEMBERSHIP]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_MEMBERSHIP]['warning']); + + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $team['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals($team['body']['name'], $response['body']['name']); + + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $team['body']['$id'] . '/memberships', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + + $membership = $response['body']['memberships'][0]; + + $this->assertEquals($user['body']['$id'], $membership['userId']); + $this->assertEquals($team['body']['$id'], $membership['teamId']); + $this->assertEquals(['owner'], $membership['roles']); + + // Cleanup + $this->client->call(Client::METHOD_DELETE, '/teams/' . $team['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/teams/' . $team['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/users/' . $user['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/users/' . $user['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/teams/' . $team['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/teams/' . $team['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + } + + /** + * Databases + */ + public function testAppwriteMigrationDatabase() + { + $response = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'databaseId' => ID::unique(), + 'name' => 'Test Database' + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + + $databaseId = $response['body']['$id']; + + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_DATABASE, + ], + 'endpoint' => 'http://localhost/v1', + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([Resource::TYPE_DATABASE], $result['resources']); + $this->assertArrayHasKey(Resource::TYPE_DATABASE, $result['statusCounters']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_DATABASE]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE]['warning']); + + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + + $this->assertEquals($databaseId, $response['body']['$id']); + $this->assertEquals('Test Database', $response['body']['name']); + + // Cleanup on destination + $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + return [ + 'databaseId' => $databaseId, + ]; + } + + /** + * @depends testAppwriteMigrationDatabase + */ + public function testAppwriteMigrationDatabasesCollection(array $data) + { + $databaseId = $data['databaseId']; + + $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'collectionId' => ID::unique(), + 'name' => 'Test Collection', + ]); + + $this->assertEquals(201, $collection['headers']['status-code']); + + $collectionId = $collection['body']['$id']; + + // Create Attribute + $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'key' => 'name', + 'size' => 100, + 'encrypt' => false, + 'required' => true + ]); + + $this->assertEquals(202, $response['headers']['status-code']); + + // Wait for attribute to be ready + $this->assertEventually(function () use ($databaseId, $collectionId) { + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/name', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('available', $response['body']['status']); + }, 5000, 500); + + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_DATABASE, + Resource::TYPE_COLLECTION, + Resource::TYPE_ATTRIBUTE, + ], + 'endpoint' => 'http://localhost/v1', + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_ATTRIBUTE], $result['resources']); + + foreach ([Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_ATTRIBUTE] as $resource) { + $this->assertArrayHasKey($resource, $result['statusCounters']); + $this->assertEquals(0, $result['statusCounters'][$resource]['error']); + $this->assertEquals(0, $result['statusCounters'][$resource]['pending']); + $this->assertEquals(1, $result['statusCounters'][$resource]['success']); + $this->assertEquals(0, $result['statusCounters'][$resource]['processing']); + $this->assertEquals(0, $result['statusCounters'][$resource]['warning']); + } + + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + + $this->assertEquals($collectionId, $response['body']['$id']); + $this->assertEquals('Test Collection', $response['body']['name']); + + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/name', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + + $this->assertEquals('name', $response['body']['key']); + $this->assertEquals(100, $response['body']['size']); + $this->assertEquals(true, $response['body']['required']); + + // Cleanup + $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + return [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + ]; + } + + /** + * @depends testAppwriteMigrationDatabasesCollection + */ + public function testAppwriteMigrationDatabasesDocument(array $data) + { + $databaseId = $data['databaseId']; + $collectionId = $data['collectionId']; + + $document = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'documentId' => ID::unique(), + 'data' => [ + 'name' => 'Test Document', + ] + ]); + + $this->assertEquals(201, $document['headers']['status-code']); + $this->assertNotEmpty($document['body']); + $this->assertNotEmpty($document['body']['$id']); + + $documentId = $document['body']['$id']; + + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_DATABASE, + Resource::TYPE_COLLECTION, + Resource::TYPE_ATTRIBUTE, + Resource::TYPE_DOCUMENT, + ], + 'endpoint' => 'http://localhost/v1', + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_ATTRIBUTE, Resource::TYPE_DOCUMENT], $result['resources']); + + //TODO: Add TYPE_DOCUMENT to the migration status counters once pending issue is resolved + foreach ([Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_ATTRIBUTE] as $resource) { + $this->assertArrayHasKey($resource, $result['statusCounters']); + $this->assertEquals(0, $result['statusCounters'][$resource]['error']); + $this->assertEquals(0, $result['statusCounters'][$resource]['pending']); + $this->assertEquals(1, $result['statusCounters'][$resource]['success']); + $this->assertEquals(0, $result['statusCounters'][$resource]['processing']); + $this->assertEquals(0, $result['statusCounters'][$resource]['warning']); + } + + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $documentId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + + $this->assertEquals($documentId, $response['body']['$id']); + $this->assertEquals('Test Document', $response['body']['name']); + + // Cleanup + $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + } + + /** + * Storage + */ + public function testAppwriteMigrationStorageBucket() + { + $bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'bucketId' => ID::unique(), + 'name' => 'Test Bucket', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'maximumFileSize' => 1000000, + 'allowedFileExtensions' => ['pdf'], + 'compression' => 'gzip', + 'encryption' => false, + 'antivirus' => false + ]); + + $this->assertEquals(201, $bucket['headers']['status-code']); + $this->assertNotEmpty($bucket['body']); + $this->assertNotEmpty($bucket['body']['$id']); + $this->assertEquals('Test Bucket', $bucket['body']['name']); + + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_BUCKET + ], + 'endpoint' => 'http://localhost/v1', + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([Resource::TYPE_BUCKET], $result['resources']); + $this->assertArrayHasKey(Resource::TYPE_BUCKET, $result['statusCounters']); + + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_BUCKET]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_BUCKET]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_BUCKET]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_BUCKET]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_BUCKET]['warning']); + + $response = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucket['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + + $this->assertEquals($bucket['body']['$id'], $response['body']['$id']); + $this->assertEquals($bucket['body']['name'], $response['body']['name']); + $this->assertEquals($bucket['body']['$permissions'], $response['body']['$permissions']); + $this->assertEquals($bucket['body']['maximumFileSize'], $response['body']['maximumFileSize']); + $this->assertEquals($bucket['body']['allowedFileExtensions'], $response['body']['allowedFileExtensions']); + $this->assertEquals($bucket['body']['compression'], $response['body']['compression']); + $this->assertEquals($bucket['body']['encryption'], $response['body']['encryption']); + $this->assertEquals($bucket['body']['antivirus'], $response['body']['antivirus']); + + // Cleanup + $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucket['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucket['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + } + + public function testAppwriteMigrationStorageFiles() + { + $bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'bucketId' => ID::unique(), + 'name' => 'Test Bucket', + 'fileSecurity' => true, + 'maximumFileSize' => 2000000, //2MB + 'allowedFileExtensions' => ['jpg', 'png', 'jfif'], + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + $this->assertEquals(201, $bucket['headers']['status-code']); + $this->assertNotEmpty($bucket['body']['$id']); + + $bucketId = $bucket['body']['$id']; + + $file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', [ + 'content-type' => 'multipart/form-data', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'fileId' => ID::unique(), + 'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'logo.png'), + 'permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $this->assertEquals(201, $file['headers']['status-code']); + $this->assertNotEmpty($file['body']['$id']); + + $fileId = $file['body']['$id']; + + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_BUCKET, + Resource::TYPE_FILE + ], + 'endpoint' => 'http://localhost/v1', + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([Resource::TYPE_BUCKET, Resource::TYPE_FILE], $result['resources']); + $this->assertArrayHasKey(Resource::TYPE_BUCKET, $result['statusCounters']); + + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_BUCKET]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_BUCKET]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_BUCKET]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_BUCKET]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_BUCKET]['warning']); + + $this->assertArrayHasKey(Resource::TYPE_FILE, $result['statusCounters']); + + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_FILE]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_FILE]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_FILE]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_FILE]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_FILE]['warning']); + + $response = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + + $this->assertEquals($fileId, $response['body']['$id']); + + // Cleanup + $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + } + + /** + * Functions + */ + public function testAppwriteMigrationFunction() + { + $functionId = $this->setupFunction([ + 'functionId' => ID::unique(), + 'name' => 'Test', + 'runtime' => 'php-8.0', + 'entrypoint' => 'index.php' + ]); + + $deploymentId = $this->setupDeployment($functionId, [ + 'entrypoint' => 'index.php', + 'code' => $this->packageFunction('php'), + 'activate' => true + ]); + + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_FUNCTION, + Resource::TYPE_DEPLOYMENT + ], + 'endpoint' => 'http://localhost/v1', + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([Resource::TYPE_FUNCTION, Resource::TYPE_DEPLOYMENT], $result['resources']); + $this->assertArrayHasKey(Resource::TYPE_FUNCTION, $result['statusCounters']); + + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_FUNCTION]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_FUNCTION]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_FUNCTION]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_FUNCTION]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_FUNCTION]['warning']); + + $this->assertArrayHasKey(Resource::TYPE_DEPLOYMENT, $result['statusCounters']); + + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DEPLOYMENT]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DEPLOYMENT]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_DEPLOYMENT]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DEPLOYMENT]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DEPLOYMENT]['warning']); + + $response = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + + $this->assertEquals($functionId, $response['body']['$id']); + $this->assertEquals('Test', $response['body']['name']); + $this->assertEquals('php-8.0', $response['body']['runtime']); + $this->assertEquals('index.php', $response['body']['entrypoint']); + + + $this->assertEventually(function () use ($functionId) { + $deployments = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ])); + + $this->assertEquals(200, $deployments['headers']['status-code']); + $this->assertNotEmpty($deployments['body']); + $this->assertEquals(1, $deployments['body']['total']); + + $this->assertEquals('ready', $deployments['body']['deployments'][0]['status'], 'Deployment status is not ready, deployment: ' . json_encode($deployments['body']['deployments'][0], JSON_PRETTY_PRINT)); + }, 50000, 500); + + // Attempt execution + $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ], [ + 'body' => 'test' + ]); + + $this->assertEquals(201, $execution['headers']['status-code']); + $this->assertStringContainsString('body-is-test', $execution['body']['logs']); + + // Cleanup + $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + } +} diff --git a/tests/e2e/Services/Migrations/MigrationsConsoleClientTest.php b/tests/e2e/Services/Migrations/MigrationsConsoleClientTest.php new file mode 100644 index 0000000000..2167ef9338 --- /dev/null +++ b/tests/e2e/Services/Migrations/MigrationsConsoleClientTest.php @@ -0,0 +1,12 @@ + $this->getProject()['$id'], ], $this->getHeaders()), [ 'teamId' => ID::unique(), - 'name' => 'Amating Team', + 'name' => 'Amazing Team', ]); $this->assertEquals(201, $team['headers']['status-code']); - $this->assertEquals('Amating Team', $team['body']['name']); + $this->assertEquals('Amazing Team', $team['body']['name']); $this->assertNotEmpty($team['body']['$id']); $teamId = $team['body']['$id']; @@ -3793,4 +3793,381 @@ class ProjectsConsoleClientTest extends Scope return $data; } + + public function testDeleteSharedProject(): void + { + $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'teamId' => ID::unique(), + 'name' => 'Amazing Team', + ]); + + $teamId = $team['body']['$id']; + + // Ensure deleting one project does not affect another project + $project1 = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'projectId' => ID::unique(), + 'name' => 'Amazing Project 1', + 'teamId' => $teamId, + 'region' => 'default' + ]); + + $project2 = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'projectId' => ID::unique(), + 'name' => 'Amazing Project 2', + 'teamId' => $teamId, + 'region' => 'default' + ]); + + $project1Id = $project1['body']['$id']; + $project2Id = $project2['body']['$id']; + + // Create user in each project + $key1 = $this->client->call(Client::METHOD_POST, '/projects/' . $project1Id . '/keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Key Test', + 'scopes' => ['users.read', 'users.write'], + ]); + + $user1 = $this->client->call(Client::METHOD_POST, '/users', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project1Id, + 'x-appwrite-key' => $key1['body']['secret'], + ], [ + 'userId' => ID::unique(), + 'email' => 'test1@appwrite.io', + 'password' => 'password', + ]); + + $this->assertEquals(201, $user1['headers']['status-code']); + + $key2 = $this->client->call(Client::METHOD_POST, '/projects/' . $project2Id . '/keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Key Test', + 'scopes' => ['users.read', 'users.write'], + ]); + + $user2 = $this->client->call(Client::METHOD_POST, '/users', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project2Id, + 'x-appwrite-key' => $key2['body']['secret'], + ], [ + 'userId' => ID::unique(), + 'email' => 'test2@appwrite.io', + 'password' => 'password', + ]); + + $this->assertEquals(201, $user2['headers']['status-code']); + + // Delete project 1 + $project1 = $this->client->call(Client::METHOD_DELETE, '/projects/' . $project1Id, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(204, $project1['headers']['status-code']); + + \sleep(3); + + // Ensure project 2 user is still there + $user2 = $this->client->call(Client::METHOD_GET, '/users/' . $user2['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project2Id, + 'x-appwrite-key' => $key2['body']['secret'], + ]); + + $this->assertEquals(200, $user2['headers']['status-code']); + + // Create another user in project 2 in case read hits stale cache + $user3 = $this->client->call(Client::METHOD_POST, '/users', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project2Id, + 'x-appwrite-key' => $key2['body']['secret'], + ], [ + 'userId' => ID::unique(), + 'email' => 'test3@appwrite.io' + ]); + + $this->assertEquals(201, $user3['headers']['status-code']); + } + + /** + * @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->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']); + + 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_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->assertCount(1, $response['body']['variables']); + $this->assertEquals("APP_TEST_UPDATE", $response['body']['variables'][0]['key']); + + /** + * 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']); + + $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; + } + + /** + * @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->assertCount(0, $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; + } } diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php index 616f309fd2..dda524fc7c 100644 --- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php @@ -111,6 +111,30 @@ class RealtimeCustomClientTest extends Scope $client->close(); } + public function testPingPong() + { + $client = $this->getWebsocket(['files'], [ + 'origin' => 'http://localhost' + ]); + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('connected', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(1, $response['data']['channels']); + $this->assertContains('files', $response['data']['channels']); + + $client->send(\json_encode([ + 'type' => 'ping' + ])); + + $response = json_decode($client->receive(), true); + $this->assertEquals('pong', $response['type']); + + $client->close(); + } + public function testManualAuthentication() { $user = $this->getUser(); diff --git a/tests/e2e/Services/Teams/TeamsBaseClient.php b/tests/e2e/Services/Teams/TeamsBaseClient.php index 8a1fed028e..ca6082f6d0 100644 --- a/tests/e2e/Services/Teams/TeamsBaseClient.php +++ b/tests/e2e/Services/Teams/TeamsBaseClient.php @@ -30,8 +30,8 @@ trait TeamsBaseClient $this->assertIsInt($response['body']['total']); $this->assertNotEmpty($response['body']['memberships'][0]['$id']); $this->assertFalse($response['body']['memberships'][0]['mfa']); - $this->assertEquals($this->getUser()['name'], $response['body']['memberships'][0]['userName']); - $this->assertEquals($this->getUser()['email'], $response['body']['memberships'][0]['userEmail']); + $this->assertArrayHasKey('userName', $response['body']['memberships'][0]); + $this->assertArrayHasKey('userEmail', $response['body']['memberships'][0]); $this->assertEquals($teamName, $response['body']['memberships'][0]['teamName']); $this->assertContains('owner', $response['body']['memberships'][0]['roles']); $this->assertContains('player', $response['body']['memberships'][0]['roles']); @@ -96,8 +96,8 @@ trait TeamsBaseClient $this->assertEquals(200, $response['headers']['status-code']); $this->assertIsInt($response['body']['total']); $this->assertNotEmpty($response['body']['memberships'][0]); - $this->assertEquals($this->getUser()['name'], $response['body']['memberships'][0]['userName']); - $this->assertEquals($this->getUser()['email'], $response['body']['memberships'][0]['userEmail']); + $this->assertArrayHasKey('userName', $response['body']['memberships'][0]); + $this->assertArrayHasKey('userEmail', $response['body']['memberships'][0]); $this->assertEquals($teamName, $response['body']['memberships'][0]['teamName']); $this->assertContains('owner', $response['body']['memberships'][0]['roles']); $this->assertContains('player', $response['body']['memberships'][0]['roles']); @@ -112,8 +112,8 @@ trait TeamsBaseClient $this->assertEquals(200, $response['headers']['status-code']); $this->assertIsInt($response['body']['total']); $this->assertNotEmpty($response['body']['memberships'][0]); - $this->assertEquals($this->getUser()['name'], $response['body']['memberships'][0]['userName']); - $this->assertEquals($this->getUser()['email'], $response['body']['memberships'][0]['userEmail']); + $this->assertArrayHasKey('userName', $response['body']['memberships'][0]); + $this->assertArrayHasKey('userEmail', $response['body']['memberships'][0]); $this->assertEquals($teamName, $response['body']['memberships'][0]['teamName']); $this->assertContains('owner', $response['body']['memberships'][0]['roles']); $this->assertContains('player', $response['body']['memberships'][0]['roles']); @@ -157,8 +157,8 @@ trait TeamsBaseClient $this->assertNotEmpty($response['body']['$id']); $this->assertFalse($response['body']['mfa']); $this->assertNotEmpty($response['body']['userId']); - $this->assertNotEmpty($response['body']['userName']); - $this->assertNotEmpty($response['body']['userEmail']); + $this->assertArrayHasKey('userName', $response['body']); + $this->assertArrayHasKey('userEmail', $response['body']); $this->assertNotEmpty($response['body']['teamId']); $this->assertNotEmpty($response['body']['teamName']); $this->assertCount(1, $response['body']['roles']); @@ -559,6 +559,76 @@ trait TeamsBaseClient return $data; } + + /** + * @depends testCreateTeam + */ + public function testUpdateMembershipWithSession(array $data): void + { + $teamUid = $data['teamUid'] ?? ''; + + // create user + $response = $this->client->call(Client::METHOD_POST, '/account', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'userId' => 'unique()', + 'email' => uniqid() . 'foe@localhost.test', + 'password' => 'password', + 'name' => 'test' + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $user = $response['body']; + + // create session + $response = $this->client->call(Client::METHOD_POST, '/account/sessions', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'email' => $user['email'], + 'password' => 'password' + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + $session = $response['cookies']['a_session_' . $this->getProject()['$id']]; + + $response = $this->client->call(Client::METHOD_POST, '/teams/' . $teamUid . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'email' => $user['email'], + 'roles' => ['developer'], + 'url' => 'http://localhost:5000/join-us#title' + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + + $lastEmail = $this->getLastEmail(); + + $secret = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256); + $membershipUid = substr($lastEmail['text'], strpos($lastEmail['text'], '?membershipId=', 0) + 14, 20); + $userUid = substr($lastEmail['text'], strpos($lastEmail['text'], '&userId=', 0) + 8, 20); + + $response = $this->client->call(Client::METHOD_PATCH, '/teams/' . $teamUid . '/memberships/' . $membershipUid . '/status', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, + ], [ + 'secret' => $secret, + 'userId' => $userUid, + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertNotEmpty($response['body']['userId']); + $this->assertNotEmpty($response['body']['teamId']); + $this->assertCount(1, $response['body']['roles']); + $this->assertEmpty($response['cookies']); + } + /** * @depends testUpdateTeamMembership */ @@ -648,7 +718,7 @@ trait TeamsBaseClient ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(3, $response['body']['total']); + $this->assertEquals(4, $response['body']['total']); $ownerMembershipUid = $response['body']['memberships'][0]['$id']; @@ -703,7 +773,7 @@ trait TeamsBaseClient ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(2, $response['body']['total']); + $this->assertEquals(3, $response['body']['total']); /** * Test for when the owner tries to delete their membership diff --git a/tests/e2e/Services/Teams/TeamsBaseServer.php b/tests/e2e/Services/Teams/TeamsBaseServer.php index 4e9d0839ab..6a1d05e9d4 100644 --- a/tests/e2e/Services/Teams/TeamsBaseServer.php +++ b/tests/e2e/Services/Teams/TeamsBaseServer.php @@ -29,7 +29,7 @@ trait TeamsBaseServer * Test for FAILURE */ - return []; + return $data; } /** @@ -60,6 +60,67 @@ trait TeamsBaseServer $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['joined'])); // is null in DB $this->assertEquals(true, $response['body']['confirm']); + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/auth/memberships-privacy', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => 'console', + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], + ]), [ + 'userName' => false, + 'userEmail' => false, + 'mfa' => false, + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + /** + * Test that sensitive fields are not hidden, as we are on console + */ + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertIsInt($response['body']['total']); + $this->assertNotEmpty($response['body']['memberships'][0]['$id']); + + // Assert that sensitive fields are present + $this->assertNotEmpty($response['body']['memberships'][0]['userName']); + $this->assertNotEmpty($response['body']['memberships'][0]['userEmail']); + $this->assertArrayHasKey('mfa', $response['body']['memberships'][0]); + + /** + * Update project settings to show sensitive fields + */ + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/auth/memberships-privacy', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => 'console', + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], + ]), [ + 'userName' => true, + 'userEmail' => true, + 'mfa' => true, + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + /** + * Test that sensitive fields are shown + */ + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertIsInt($response['body']['total']); + $this->assertNotEmpty($response['body']['memberships'][0]['$id']); + + // Assert that sensitive fields are present + $this->assertNotEmpty($response['body']['memberships'][0]['userName']); + $this->assertNotEmpty($response['body']['memberships'][0]['userEmail']); + $this->assertArrayHasKey('mfa', $response['body']['memberships'][0]); + /** * Test for FAILURE */ diff --git a/tests/e2e/Services/Teams/TeamsCustomClientTest.php b/tests/e2e/Services/Teams/TeamsCustomClientTest.php index 1de22f743f..7286bb0827 100644 --- a/tests/e2e/Services/Teams/TeamsCustomClientTest.php +++ b/tests/e2e/Services/Teams/TeamsCustomClientTest.php @@ -14,6 +14,109 @@ class TeamsCustomClientTest extends Scope use ProjectCustom; use SideClient; + /** + * @depends testGetTeamMemberships + */ + public function testGetMembershipPrivacy($data) + { + $teamUid = $data['teamUid'] ?? ''; + + $projectId = $this->getProject()['$id']; + + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $projectId . '/auth/memberships-privacy', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => 'console', + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], + ]), [ + 'userName' => false, + 'userEmail' => false, + 'mfa' => false, + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + /** + * Test that sensitive fields are hidden + */ + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertIsInt($response['body']['total']); + $this->assertNotEmpty($response['body']['memberships'][0]['$id']); + + // Assert that sensitive fields are not present + $this->assertEmpty($response['body']['memberships'][0]['userName']); + $this->assertEmpty($response['body']['memberships'][0]['userEmail']); + $this->assertFalse($response['body']['memberships'][0]['mfa']); + + /** + * Update project settings to show sensitive fields + */ + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $projectId . '/auth/memberships-privacy', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => 'console', + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], + ]), [ + 'userName' => true, + 'userEmail' => true, + 'mfa' => true, + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + /** + * Test that sensitive fields are shown + */ + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertIsInt($response['body']['total']); + $this->assertNotEmpty($response['body']['memberships'][0]['$id']); + + // Assert that sensitive fields are present + $this->assertNotEmpty($response['body']['memberships'][0]['userName']); + $this->assertNotEmpty($response['body']['memberships'][0]['userEmail']); + $this->assertArrayHasKey('mfa', $response['body']['memberships'][0]); + + /** + * Update project settings to show only MFA + */ + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/auth/memberships-privacy', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => 'console', + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], + ]), [ + 'userName' => false, + 'userEmail' => false, + 'mfa' => true, + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + /** + * Test that sensitive fields are not shown + */ + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertIsInt($response['body']['total']); + $this->assertNotEmpty($response['body']['memberships'][0]['$id']); + + // Assert that sensitive fields are present + $this->assertEmpty($response['body']['memberships'][0]['userName']); + $this->assertEmpty($response['body']['memberships'][0]['userEmail']); + $this->assertArrayHasKey('mfa', $response['body']['memberships'][0]); + } + /** * @depends testUpdateTeamMembership */ diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index d9105e0790..bbf9a5e2df 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -310,6 +310,14 @@ trait UsersBase $this->assertNotEmpty($session['secret']); $this->assertNotEmpty($session['expire']); $this->assertEquals('server', $session['provider']); + + $response = $this->client->call(Client::METHOD_GET, '/account', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-session' => $session['secret'] + ]); + + $this->assertEquals(200, $response['headers']['status-code']); } @@ -1498,6 +1506,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']; } @@ -1510,6 +1519,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'])); } diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index 6be3e16c1f..2ef41003ee 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -901,6 +901,17 @@ trait WebhooksBase $teamId = $data['teamId'] ?? ''; $email = uniqid() . 'friend@localhost.test'; + // Create user to ensure team event is triggered after user event + $user = $this->client->call(Client::METHOD_POST, '/account', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'userId' => ID::unique(), + 'email' => $email, + 'password' => 'password', + 'name' => 'Friend User', + ]); + /** * Test for SUCCESS */ @@ -909,7 +920,6 @@ trait WebhooksBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'email' => $email, - 'name' => 'Friend User', 'roles' => ['admin', 'editor'], 'url' => 'http://localhost:5000/join-us#title' ]); diff --git a/tests/resources/docker/docker-compose.yml b/tests/resources/docker/docker-compose.yml index a34b4fcf88..94d506056c 100644 --- a/tests/resources/docker/docker-compose.yml +++ b/tests/resources/docker/docker-compose.yml @@ -62,6 +62,7 @@ services: - redis # - clamav environment: + - _APP_COMPRESSION_MIN_SIZE_BYTES - _APP_ENV - _APP_OPTIONS_ABUSE - _APP_OPTIONS_ROUTER_PROTECTION diff --git a/tests/unit/Event/EventTest.php b/tests/unit/Event/EventTest.php index dd9833378f..079bb47b65 100644 --- a/tests/unit/Event/EventTest.php +++ b/tests/unit/Event/EventTest.php @@ -3,13 +3,9 @@ namespace Tests\Unit\Event; use Appwrite\Event\Event; -use Appwrite\URL\URL; use InvalidArgumentException; use PHPUnit\Framework\TestCase; -use Utopia\DSN\DSN; -use Utopia\Queue; use Utopia\Queue\Client; -use Utopia\System\System; require_once __DIR__ . '/../../../app/init.php'; @@ -20,19 +16,8 @@ class EventTest extends TestCase public function setUp(): void { - $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', ''), - ]); - - $dsn = System::getEnv('_APP_CONNECTIONS_QUEUE', $fallbackForRedis); - $dsn = explode('=', $dsn); - $dsn = $dsn[1] ?? ''; - $dsn = new DSN($dsn); - $connection = new Queue\Connection\Redis($dsn->getHost(), $dsn->getPort()); + global $register; + $connection = $register->get('pools')->get('queue')->pop()->getResource(); $this->queue = 'v1-tests' . uniqid(); $this->object = new Event($connection); $this->object->setClass('TestsV1'); diff --git a/tests/unit/Usage/StatsTest.php b/tests/unit/Usage/StatsTest.php index 67e39d8974..79fa1f58ec 100644 --- a/tests/unit/Usage/StatsTest.php +++ b/tests/unit/Usage/StatsTest.php @@ -2,13 +2,9 @@ namespace Tests\Unit\Usage; -use Appwrite\URL\URL as AppwriteURL; use PHPUnit\Framework\TestCase; -use Utopia\DSN\DSN; -use Utopia\Queue; use Utopia\Queue\Client; use Utopia\Queue\Connection; -use Utopia\System\System; class StatsTest extends TestCase { @@ -19,18 +15,9 @@ class StatsTest extends TestCase public function setUp(): void { - $env = System::getEnv('_APP_CONNECTIONS_QUEUE', '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', ''), - ])); - - $dsn = explode('=', $env); - $dsn = count($dsn) > 1 ? $dsn[1] : $dsn[0]; - $dsn = new DSN($dsn); - $this->connection = new Queue\Connection\Redis($dsn->getHost(), $dsn->getPort()); + global $register; + $connection = $register->get('pools')->get('queue')->pop()->getResource(); + $this->connection = $connection; $this->client = new Client(self::QUEUE_NAME, $this->connection); }