diff --git a/.env b/.env
index 1893e023ba..c10c12613b 100644
--- a/.env
+++ b/.env
@@ -86,6 +86,7 @@ _APP_MAINTENANCE_RETENTION_CACHE=2592000
_APP_MAINTENANCE_RETENTION_EXECUTION=1209600
_APP_MAINTENANCE_RETENTION_ABUSE=86400
_APP_MAINTENANCE_RETENTION_AUDIT=1209600
+_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE=15778800
_APP_USAGE_AGGREGATION_INTERVAL=30
_APP_STATS_RESOURCES_INTERVAL=3600
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index f914e662d3..0ed82dd853 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -26,7 +26,7 @@ jobs:
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
- username: ${{ secrets.DOCKERHUB_USERNAME }}
+ username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 4475a49809..712d30dac0 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -28,7 +28,7 @@ jobs:
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
- username: ${{ secrets.DOCKERHUB_USERNAME }}
+ username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
diff --git a/.gitpod.yml b/.gitpod.yml
index 478b62fc8d..78375bb1f0 100644
--- a/.gitpod.yml
+++ b/.gitpod.yml
@@ -7,6 +7,7 @@ tasks:
docker pull composer
command: |
docker run --rm --interactive --tty \
+ --user "$(id -u):$(id -g)" \
--volume $PWD:/app \
composer install \
--ignore-platform-reqs \
@@ -23,11 +24,3 @@ vscode:
extensions:
- ms-azuretools.vscode-docker
- zobo.php-intellisense
-
-github:
- # https://www.gitpod.io/docs/prebuilds#github-specific-configuration
- prebuilds:
- # enable for pull requests coming from forks (defaults to false)
- pullRequestsFromForks: true
- # add a check to pull requests (defaults to true)
- addCheck: false
diff --git a/Dockerfile b/Dockerfile
index 2bb9f80d9e..3f85078152 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.5 AS final
+FROM appwrite/base:0.10.1 AS final
LABEL maintainer="team@appwrite.io"
@@ -44,12 +44,14 @@ COPY ./dev /usr/src/code/dev
# Set Volumes
RUN mkdir -p /storage/uploads && \
+ mkdir -p /storage/imports && \
mkdir -p /storage/cache && \
mkdir -p /storage/config && \
mkdir -p /storage/certificates && \
mkdir -p /storage/functions && \
mkdir -p /storage/debug && \
chown -Rf www-data.www-data /storage/uploads && chmod -Rf 0755 /storage/uploads && \
+ chown -Rf www-data.www-data /storage/imports && chmod -Rf 0755 /storage/imports && \
chown -Rf www-data.www-data /storage/cache && chmod -Rf 0755 /storage/cache && \
chown -Rf www-data.www-data /storage/config && chmod -Rf 0755 /storage/config && \
chown -Rf www-data.www-data /storage/certificates && chmod -Rf 0755 /storage/certificates && \
@@ -88,9 +90,7 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/worker-stats-usage && \
chmod +x /usr/local/bin/worker-stats-usage-dump && \
chmod +x /usr/local/bin/stats-resources && \
- chmod +x /usr/local/bin/worker-stats-resources && \
- chmod +x /usr/local/bin/worker-usage && \
- chmod +x /usr/local/bin/worker-usage-dump
+ chmod +x /usr/local/bin/worker-stats-resources
# Letsencrypt Permissions
RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
diff --git a/README-CN.md b/README-CN.md
index c024491907..a81d99c3c3 100644
--- a/README-CN.md
+++ b/README-CN.md
@@ -72,7 +72,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
- appwrite/appwrite:1.6.1
+ appwrite/appwrite:1.6.2
```
### Windows
@@ -84,7 +84,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
- appwrite/appwrite:1.6.1
+ appwrite/appwrite:1.6.2
```
#### PowerShell
@@ -94,7 +94,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
- appwrite/appwrite:1.6.1
+ appwrite/appwrite:1.6.2
```
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
diff --git a/README.md b/README.md
index 5bccf4739f..c3585dbb68 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-> Appwrite Init has concluded! You can check out all the latest announcements [on our Init website](https://appwrite.io/init) :rocket:
+> [Get started with Appwrite](https://apwr.dev/appcloud)
@@ -24,11 +24,9 @@
English | [简体中文](README-CN.md)
-[**Announcing Appwrite Cloud Public Beta! Sign up today!**](https://cloud.appwrite.io)
-
Appwrite is an end-to-end backend server for Web, Mobile, Native, or Backend apps packaged as a set of Docker microservices. Appwrite abstracts the complexity and repetitiveness required to build a modern backend API from scratch and allows you to build secure apps faster.
-Using Appwrite, you can easily integrate your app with user authentication and multiple sign-in methods, a database for storing and querying users and team data, storage and file management, image manipulation, Cloud Functions, and [more services](https://appwrite.io/docs).
+Using Appwrite, you can easily integrate your app with user authentication and multiple sign-in methods, a database for storing and querying users and team data, storage and file management, image manipulation, Cloud Functions, messaging, and [more services](https://appwrite.io/docs).
@@ -52,7 +50,7 @@ Table of Contents:
- [Upgrade from an Older Version](#upgrade-from-an-older-version)
- [One-Click Setups](#one-click-setups)
- [Getting Started](#getting-started)
- - [Services](#services)
+ - [Products](#products)
- [SDKs](#sdks)
- [Client](#client)
- [Server](#server)
@@ -79,7 +77,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.1
+ appwrite/appwrite:1.6.2
```
### Windows
@@ -91,7 +89,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.1
+ appwrite/appwrite:1.6.2
```
#### PowerShell
@@ -101,7 +99,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.1
+ appwrite/appwrite:1.6.2
```
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/app/cli.php b/app/cli.php
index 0b2cb884e6..82f229673e 100644
--- a/app/cli.php
+++ b/app/cli.php
@@ -9,6 +9,7 @@ use Appwrite\Event\StatsResources;
use Appwrite\Event\StatsUsage;
use Appwrite\Platform\Appwrite;
use Appwrite\Runtimes\Runtimes;
+use Executor\Executor;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
use Utopia\CLI\CLI;
@@ -25,7 +26,7 @@ use Utopia\Queue\Publisher;
use Utopia\Registry\Registry;
use Utopia\System\System;
-// overwriting runtimes to be architectur agnostic for CLI
+// Overwriting runtimes to be architecture agnostic for CLI
Config::setParam('runtimes', (new Runtimes('v4'))->getAll(supported: false));
// require controllers after overwriting runtimes
@@ -43,8 +44,7 @@ CLI::setResource('cache', function ($pools) {
$adapters[] = $pools
->get($value)
->pop()
- ->getResource()
- ;
+ ->getResource();
}
return new Cache(new Sharding($adapters));
@@ -99,6 +99,10 @@ CLI::setResource('dbForPlatform', function ($pools, $cache) {
return $dbForPlatform;
}, ['pools', 'cache']);
+CLI::setResource('console', function () {
+ return new Document(Config::getParam('console'));
+}, []);
+
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
@@ -183,7 +187,7 @@ CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database
->setSharedTables(true)
->setNamespace('logsV1')
- ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS)
+ ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_TASK)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
// set tenant
@@ -252,6 +256,8 @@ CLI::setResource('logError', function (Registry $register) {
};
}, ['register']);
+CLI::setResource('executor', fn () => new Executor(fn (string $projectId, string $deploymentId) => System::getEnv('_APP_EXECUTOR_HOST')));
+
$platform = new Appwrite();
$platform->init(Service::TYPE_TASK);
diff --git a/app/config/collections/common.php b/app/config/collections/common.php
index f68400e226..f006a01d1e 100644
--- a/app/config/collections/common.php
+++ b/app/config/collections/common.php
@@ -1038,7 +1038,7 @@ return [
'$id' => ID::custom('providerUid'),
'type' => Database::VAR_STRING,
'format' => '',
- 'size' => 2048,
+ 'size' => 2048, // Decrease to 128 as in index length?
'signed' => true,
'required' => false,
'default' => null,
@@ -1107,14 +1107,14 @@ return [
'$id' => ID::custom('_key_userInternalId_provider_providerUid'),
'type' => Database::INDEX_UNIQUE,
'attributes' => ['userInternalId', 'provider', 'providerUid'],
- 'lengths' => [11, 128, 128],
+ 'lengths' => [11, 128, 128], // providerUid is length 2000!
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_provider_providerUid'),
'type' => Database::INDEX_UNIQUE,
'attributes' => ['provider', 'providerUid'],
- 'lengths' => [128, 128],
+ 'lengths' => [128, 128], // providerUid is length 2000!
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC],
],
[
@@ -1417,6 +1417,13 @@ return [
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
+ [
+ '$id' => ID::custom('_key_roles'),
+ 'type' => Database::INDEX_KEY,
+ 'attributes' => ['roles'],
+ 'lengths' => [128],
+ 'orders' => [],
+ ],
],
],
diff --git a/app/config/collections/platform.php b/app/config/collections/platform.php
index 8a46bfd3ec..8725f4b4ce 100644
--- a/app/config/collections/platform.php
+++ b/app/config/collections/platform.php
@@ -1066,7 +1066,29 @@ return [
'default' => null,
'array' => false,
'filters' => [],
- ]
+ ],
+ [
+ '$id' => ID::custom('owner'),
+ 'type' => Database::VAR_STRING,
+ 'format' => '',
+ 'size' => 16,
+ 'signed' => true,
+ 'required' => false,
+ 'default' => '', // "Appwrite" or empty string
+ 'array' => false,
+ 'filters' => [],
+ ],
+ [
+ '$id' => ID::custom('region'),
+ 'type' => Database::VAR_STRING,
+ 'format' => '',
+ 'size' => 16,
+ 'signed' => true,
+ 'required' => true,
+ 'default' => null,
+ 'array' => false,
+ 'filters' => [],
+ ],
],
'indexes' => [
[
@@ -1111,6 +1133,20 @@ return [
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
+ [
+ '$id' => ID::custom('_key_owner'),
+ 'type' => Database::INDEX_KEY,
+ 'attributes' => ['owner'],
+ 'lengths' => [16],
+ 'orders' => [Database::ORDER_ASC],
+ ],
+ [
+ '$id' => ID::custom('_key_region'),
+ 'type' => Database::INDEX_KEY,
+ 'attributes' => ['region'],
+ 'lengths' => [16],
+ 'orders' => [Database::ORDER_ASC],
+ ],
],
],
diff --git a/app/config/collections/projects.php b/app/config/collections/projects.php
index b760c337cc..03c85336d5 100644
--- a/app/config/collections/projects.php
+++ b/app/config/collections/projects.php
@@ -1794,6 +1794,17 @@ return [
'array' => false,
'filters' => ['json', 'encrypt'],
],
+ [
+ '$id' => ID::custom('options'),
+ 'type' => Database::VAR_STRING,
+ 'format' => '',
+ 'size' => 65536,
+ 'signed' => true,
+ 'required' => false,
+ 'default' => [],
+ 'array' => false,
+ 'filters' => ['json'],
+ ],
[
'$id' => ID::custom('resources'),
'type' => Database::VAR_STRING,
@@ -1848,7 +1859,29 @@ return [
'default' => null,
'array' => false,
'filters' => [],
- ]
+ ],
+ [
+ '$id' => ID::custom('resourceId'),
+ 'type' => Database::VAR_STRING,
+ 'format' => '',
+ 'size' => Database::LENGTH_KEY,
+ 'signed' => true,
+ 'required' => false,
+ 'default' => null,
+ 'array' => false,
+ 'filters' => [],
+ ],
+ [
+ '$id' => ID::custom('resourceType'),
+ 'type' => Database::VAR_STRING,
+ 'format' => '',
+ 'size' => Database::LENGTH_KEY,
+ 'signed' => true,
+ 'required' => false,
+ 'default' => null,
+ 'array' => false,
+ 'filters' => [],
+ ],
],
'indexes' => [
[
@@ -1872,6 +1905,13 @@ return [
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
+ [
+ '$id' => '_key_resource_id',
+ 'type' => Database::INDEX_KEY,
+ 'attributes' => ['resourceId'],
+ 'lengths' => [Database::LENGTH_KEY],
+ 'orders' => [Database::ORDER_DESC],
+ ],
[
'$id' => ID::custom('_fulltext_search'),
'type' => Database::INDEX_FULLTEXT,
diff --git a/app/config/console.php b/app/config/console.php
new file mode 100644
index 0000000000..e37c9b7836
--- /dev/null
+++ b/app/config/console.php
@@ -0,0 +1,53 @@
+ ID::custom('console'),
+ '$internalId' => ID::custom('console'),
+ 'name' => 'Appwrite',
+ '$collection' => ID::custom('projects'),
+ 'description' => 'Appwrite core engine',
+ 'logo' => '',
+ 'teamId' => null,
+ 'webhooks' => [],
+ 'keys' => [],
+ 'platforms' => [
+ [
+ '$collection' => ID::custom('platforms'),
+ 'name' => 'Localhost',
+ 'type' => Origin::CLIENT_TYPE_WEB,
+ 'hostname' => 'localhost',
+ ], // Current host is added on app init
+ ],
+ 'region' => 'fra',
+ 'legalName' => '',
+ 'legalCountry' => '',
+ 'legalState' => '',
+ 'legalCity' => '',
+ 'legalAddress' => '',
+ 'legalTaxId' => '',
+ 'auths' => [
+ 'mockNumbers' => [],
+ 'invites' => System::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled',
+ 'limit' => (System::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user
+ 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds
+ 'sessionAlerts' => System::getEnv('_APP_CONSOLE_SESSION_ALERTS', 'disabled') === 'enabled'
+ ],
+ 'authWhitelistEmails' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [],
+ 'authWhitelistIPs' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [],
+ 'oAuthProviders' => [
+ 'githubEnabled' => true,
+ 'githubSecret' => System::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''),
+ 'githubAppid' => System::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '')
+ ],
+];
+
+return $console;
diff --git a/app/config/locale/continents.php b/app/config/locale/continents.php
index 2f1ffc0a53..611c725ef1 100644
--- a/app/config/locale/continents.php
+++ b/app/config/locale/continents.php
@@ -1,11 +1,46 @@
[
+ 'name' => 'Africa',
+ 'latitude' => 8.7832,
+ 'longitude' => 34.5085
+ ],
+ 'AN' => [
+ 'name' => 'Antarctica',
+ 'latitude' => -82.8628,
+ 'longitude' => 135.0000
+ ],
+ 'AS' => [
+ 'name' => 'Asia',
+ 'latitude' => 34.0479,
+ 'longitude' => 100.6197
+ ],
+ 'EU' => [
+ 'name' => 'Europe',
+ 'latitude' => 54.5260,
+ 'longitude' => 15.2551
+ ],
+ 'NA' => [
+ 'name' => 'North America',
+ 'latitude' => 54.5260,
+ 'longitude' => -105.2551
+ ],
+ 'OC' => [
+ 'name' => 'Oceania',
+ 'latitude' => -22.7359,
+ 'longitude' => 140.0188
+ ],
+ 'SA' => [
+ 'name' => 'South America',
+ 'latitude' => -8.7832,
+ 'longitude' => -55.4915
+ ],
];
diff --git a/app/config/locale/countries.php b/app/config/locale/countries.php
index bd2cbbbaaa..adfc5d76a6 100644
--- a/app/config/locale/countries.php
+++ b/app/config/locale/countries.php
@@ -1,209 +1,210 @@
['name' => 'Afghanistan', 'latitude' => 33.0, 'longitude' => 66.0],
+ 'AO' => ['name' => 'Angola', 'latitude' => -12.5, 'longitude' => 18.5],
+ 'AL' => ['name' => 'Albania', 'latitude' => 41.0, 'longitude' => 20.0],
+ 'AD' => ['name' => 'Andorra', 'latitude' => 42.5, 'longitude' => 1.6],
+ 'AE' => ['name' => 'United Arab Emirates', 'latitude' => 24.0, 'longitude' => 54.0],
+ 'AR' => ['name' => 'Argentina', 'latitude' => -34.0, 'longitude' => -64.0],
+ 'AM' => ['name' => 'Armenia', 'latitude' => 40.0, 'longitude' => 45.0],
+ 'AG' => ['name' => 'Antigua and Barbuda', 'latitude' => 17.05, 'longitude' => -61.8],
+ 'AU' => ['name' => 'Australia', 'latitude' => -25.0, 'longitude' => 135.0],
+ 'AT' => ['name' => 'Austria', 'latitude' => 47.3, 'longitude' => 13.3],
+ 'AZ' => ['name' => 'Azerbaijan', 'latitude' => 40.5, 'longitude' => 47.5],
+ 'BI' => ['name' => 'Burundi', 'latitude' => -3.5, 'longitude' => 30.0],
+ 'BE' => ['name' => 'Belgium', 'latitude' => 50.8, 'longitude' => 4.0],
+ 'BJ' => ['name' => 'Benin', 'latitude' => 9.5, 'longitude' => 2.25],
+ 'BF' => ['name' => 'Burkina Faso', 'latitude' => 13.0, 'longitude' => -2.0],
+ 'BD' => ['name' => 'Bangladesh', 'latitude' => 24.0, 'longitude' => 90.0],
+ 'BG' => ['name' => 'Bulgaria', 'latitude' => 43.0, 'longitude' => 25.0],
+ 'BH' => ['name' => 'Bahrain', 'latitude' => 26.0, 'longitude' => 50.5],
+ 'BS' => ['name' => 'Bahamas', 'latitude' => 24.25, 'longitude' => -76.0],
+ 'BA' => ['name' => 'Bosnia and Herzegovina', 'latitude' => 44.0, 'longitude' => 18.0],
+ 'BY' => ['name' => 'Belarus', 'latitude' => 53.0, 'longitude' => 28.0],
+ 'BZ' => ['name' => 'Belize', 'latitude' => 17.25, 'longitude' => -88.75],
+ 'BO' => ['name' => 'Bolivia', 'latitude' => -17.0, 'longitude' => -65.0],
+ 'BR' => ['name' => 'Brazil', 'latitude' => -10.0, 'longitude' => -55.0],
+ 'BB' => ['name' => 'Barbados', 'latitude' => 13.17, 'longitude' => -59.53],
+ 'BN' => ['name' => 'Brunei', 'latitude' => 4.5, 'longitude' => 114.67],
+ 'BT' => ['name' => 'Bhutan', 'latitude' => 27.5, 'longitude' => 90.5],
+ 'BW' => ['name' => 'Botswana', 'latitude' => -22.0, 'longitude' => 24.0],
+ 'CF' => ['name' => 'Central African Republic', 'latitude' => 7.0, 'longitude' => 21.0],
+ 'CA' => ['name' => 'Canada', 'latitude' => 60.0, 'longitude' => -95.0],
+ 'CH' => ['name' => 'Switzerland', 'latitude' => 47.0, 'longitude' => 8.0],
+ 'CL' => ['name' => 'Chile', 'latitude' => -30.0, 'longitude' => -71.0],
+ 'CN' => ['name' => 'China', 'latitude' => 35.0, 'longitude' => 105.0],
+ 'CI' => ['name' => 'Côte d\'Ivoire', 'latitude' => 8.0, 'longitude' => -5.0],
+ 'CM' => ['name' => 'Cameroon', 'latitude' => 6.0, 'longitude' => 12.0],
+ 'CD' => ['name' => 'Democratic Republic of the Congo', 'latitude' => -2.5, 'longitude' => 23.5],
+ 'CG' => ['name' => 'Republic of the Congo', 'latitude' => -1.0, 'longitude' => 15.0],
+ 'CO' => ['name' => 'Colombia', 'latitude' => 4.0, 'longitude' => -72.0],
+ 'KM' => ['name' => 'Comoros', 'latitude' => -12.17, 'longitude' => 44.25],
+ 'CV' => ['name' => 'Cape Verde', 'latitude' => 16.0, 'longitude' => -24.0],
+ 'CR' => ['name' => 'Costa Rica', 'latitude' => 10.0, 'longitude' => -84.0],
+ 'CU' => ['name' => 'Cuba', 'latitude' => 21.5, 'longitude' => -80.0],
+ 'CY' => ['name' => 'Cyprus', 'latitude' => 35.0, 'longitude' => 33.0],
+ 'CZ' => ['name' => 'Czech Republic', 'latitude' => 49.75, 'longitude' => 15.5],
+ 'DE' => ['name' => 'Germany', 'latitude' => 51.0, 'longitude' => 9.0],
+ 'DJ' => ['name' => 'Djibouti', 'latitude' => 11.5, 'longitude' => 43.0],
+ 'DM' => ['name' => 'Dominica', 'latitude' => 15.42, 'longitude' => -61.33],
+ 'DK' => ['name' => 'Denmark', 'latitude' => 56.0, 'longitude' => 10.0],
+ 'DO' => ['name' => 'Dominican Republic', 'latitude' => 19.0, 'longitude' => -70.67],
+ 'DZ' => ['name' => 'Algeria', 'latitude' => 28.0, 'longitude' => 3.0],
+ 'EC' => ['name' => 'Ecuador', 'latitude' => -2.0, 'longitude' => -77.5],
+ 'EG' => ['name' => 'Egypt', 'latitude' => 27.0, 'longitude' => 30.0],
+ 'ER' => ['name' => 'Eritrea', 'latitude' => 15.0, 'longitude' => 39.0],
+ 'ES' => ['name' => 'Spain', 'latitude' => 40.0, 'longitude' => -4.0],
+ 'EE' => ['name' => 'Estonia', 'latitude' => 59.0, 'longitude' => 26.0],
+ 'ET' => ['name' => 'Ethiopia', 'latitude' => 8.0, 'longitude' => 38.0],
+ 'FI' => ['name' => 'Finland', 'latitude' => 64.0, 'longitude' => 26.0],
+ 'FJ' => ['name' => 'Fiji', 'latitude' => -18.0, 'longitude' => 175.0],
+ 'FR' => ['name' => 'France', 'latitude' => 46.0, 'longitude' => 2.0],
+ 'FM' => ['name' => 'Micronesia', 'latitude' => 6.92, 'longitude' => 158.25],
+ 'GA' => ['name' => 'Gabon', 'latitude' => -1.0, 'longitude' => 11.75],
+ 'GB' => ['name' => 'United Kingdom', 'latitude' => 54.0, 'longitude' => -2.0],
+ 'GE' => ['name' => 'Georgia', 'latitude' => 42.0, 'longitude' => 43.5],
+ 'GH' => ['name' => 'Ghana', 'latitude' => 8.0, 'longitude' => -2.0],
+ 'GN' => ['name' => 'Guinea', 'latitude' => 11.0, 'longitude' => -10.0],
+ 'GM' => ['name' => 'Gambia', 'latitude' => 13.47, 'longitude' => -16.57],
+ 'GW' => ['name' => 'Guinea-Bissau', 'latitude' => 12.0, 'longitude' => -15.0],
+ 'GQ' => ['name' => 'Equatorial Guinea', 'latitude' => 2.0, 'longitude' => 10.0],
+ 'GR' => ['name' => 'Greece', 'latitude' => 39.0, 'longitude' => 22.0],
+ 'GD' => ['name' => 'Grenada', 'latitude' => 12.12, 'longitude' => -61.67],
+ 'GT' => ['name' => 'Guatemala', 'latitude' => 15.5, 'longitude' => -90.25],
+ 'GY' => ['name' => 'Guyana', 'latitude' => 5.0, 'longitude' => -59.0],
+ 'HK' => ['name' => 'Hong Kong', 'latitude' => 22.25, 'longitude' => 114.17],
+ 'HN' => ['name' => 'Honduras', 'latitude' => 15.0, 'longitude' => -86.5],
+ 'HR' => ['name' => 'Croatia', 'latitude' => 45.17, 'longitude' => 15.5],
+ 'HT' => ['name' => 'Haiti', 'latitude' => 19.0, 'longitude' => -72.42],
+ 'HU' => ['name' => 'Hungary', 'latitude' => 47.0, 'longitude' => 20.0],
+ 'ID' => ['name' => 'Indonesia', 'latitude' => -5.0, 'longitude' => 120.0],
+ 'IN' => ['name' => 'India', 'latitude' => 20.0, 'longitude' => 77.0],
+ 'IE' => ['name' => 'Ireland', 'latitude' => 53.0, 'longitude' => -8.0],
+ 'IR' => ['name' => 'Iran', 'latitude' => 32.0, 'longitude' => 53.0],
+ 'IQ' => ['name' => 'Iraq', 'latitude' => 33.0, 'longitude' => 44.0],
+ 'IS' => ['name' => 'Iceland', 'latitude' => 65.0, 'longitude' => -18.0],
+ 'IL' => ['name' => 'Israel', 'latitude' => 31.5, 'longitude' => 34.75],
+ 'IT' => ['name' => 'Italy', 'latitude' => 42.83, 'longitude' => 12.83],
+ 'JM' => ['name' => 'Jamaica', 'latitude' => 18.25, 'longitude' => -77.5],
+ 'JO' => ['name' => 'Jordan', 'latitude' => 31.0, 'longitude' => 36.0],
+ 'JP' => ['name' => 'Japan', 'latitude' => 36.0, 'longitude' => 138.0],
+ 'KZ' => ['name' => 'Kazakhstan', 'latitude' => 48.0, 'longitude' => 68.0],
+ 'KE' => ['name' => 'Kenya', 'latitude' => 1.0, 'longitude' => 38.0],
+ 'KG' => ['name' => 'Kyrgyzstan', 'latitude' => 41.0, 'longitude' => 75.0],
+ 'KH' => ['name' => 'Cambodia', 'latitude' => 13.0, 'longitude' => 105.0],
+ 'KI' => ['name' => 'Kiribati', 'latitude' => 1.42, 'longitude' => 173.0],
+ 'KN' => ['name' => 'Saint Kitts and Nevis', 'latitude' => 17.33, 'longitude' => -62.75],
+ 'KR' => ['name' => 'South Korea', 'latitude' => 37.0, 'longitude' => 127.5],
+ 'KW' => ['name' => 'Kuwait', 'latitude' => 29.34, 'longitude' => 47.66],
+ 'LA' => ['name' => 'Laos', 'latitude' => 18.0, 'longitude' => 105.0],
+ 'LB' => ['name' => 'Lebanon', 'latitude' => 33.83, 'longitude' => 35.83],
+ 'LR' => ['name' => 'Liberia', 'latitude' => 6.5, 'longitude' => -9.5],
+ 'LY' => ['name' => 'Libya', 'latitude' => 25.0, 'longitude' => 17.0],
+ 'LC' => ['name' => 'Saint Lucia', 'latitude' => 13.88, 'longitude' => -61.13],
+ 'LI' => ['name' => 'Liechtenstein', 'latitude' => 47.17, 'longitude' => 9.53],
+ 'LK' => ['name' => 'Sri Lanka', 'latitude' => 7.0, 'longitude' => 81.0],
+ 'LS' => ['name' => 'Lesotho', 'latitude' => -29.5, 'longitude' => 28.5],
+ 'LT' => ['name' => 'Lithuania', 'latitude' => 56.0, 'longitude' => 24.0],
+ 'LU' => ['name' => 'Luxembourg', 'latitude' => 49.75, 'longitude' => 6.17],
+ 'LV' => ['name' => 'latitudevia', 'latitude' => 57.0, 'longitude' => 25.0],
+ 'MA' => ['name' => 'Morocco', 'latitude' => 32.0, 'longitude' => -5.0],
+ 'MC' => ['name' => 'Monaco', 'latitude' => 43.73, 'longitude' => 7.4],
+ 'MD' => ['name' => 'Moldova', 'latitude' => 47.0, 'longitude' => 29.0],
+ 'MG' => ['name' => 'Madagascar', 'latitude' => -20.0, 'longitude' => 47.0],
+ 'MV' => ['name' => 'Maldives', 'latitude' => 3.25, 'longitude' => 73.0],
+ 'MX' => ['name' => 'Mexico', 'latitude' => 23.0, 'longitude' => -102.0],
+ 'MH' => ['name' => 'Marshall Islands', 'latitude' => 9.0, 'longitude' => 168.0],
+ 'MK' => ['name' => 'North Macedonia', 'latitude' => 41.83, 'longitude' => 22.0],
+ 'ML' => ['name' => 'Mali', 'latitude' => 17.0, 'longitude' => -4.0],
+ 'MT' => ['name' => 'Malta', 'latitude' => 35.83, 'longitude' => 14.58],
+ 'MM' => ['name' => 'Myanmar', 'latitude' => 22.0, 'longitude' => 98.0],
+ 'ME' => ['name' => 'Montenegro', 'latitude' => 42.5, 'longitude' => 19.3],
+ 'MN' => ['name' => 'Mongolia', 'latitude' => 46.0, 'longitude' => 105.0],
+ 'MZ' => ['name' => 'Mozambique', 'latitude' => -18.25, 'longitude' => 35.0],
+ 'MR' => ['name' => 'Mauritania', 'latitude' => 20.0, 'longitude' => -12.0],
+ 'MU' => ['name' => 'Mauritius', 'latitude' => -20.28, 'longitude' => 57.55],
+ 'MW' => ['name' => 'Malawi', 'latitude' => -13.5, 'longitude' => 34.0],
+ 'MY' => ['name' => 'Malaysia', 'latitude' => 2.5, 'longitude' => 112.5],
+ 'NA' => ['name' => 'Namibia', 'latitude' => -22.0, 'longitude' => 17.0],
+ 'NE' => ['name' => 'Niger', 'latitude' => 16.0, 'longitude' => 8.0],
+ 'NG' => ['name' => 'Nigeria', 'latitude' => 10.0, 'longitude' => 8.0],
+ 'NI' => ['name' => 'Nicaragua', 'latitude' => 13.0, 'longitude' => -85.0],
+ 'NL' => ['name' => 'Netherlands', 'latitude' => 52.5, 'longitude' => 5.75],
+ 'NO' => ['name' => 'Norway', 'latitude' => 62.0, 'longitude' => 10.0],
+ 'NP' => ['name' => 'Nepal', 'latitude' => 28.0, 'longitude' => 84.0],
+ 'NR' => ['name' => 'Nauru', 'latitude' => -0.53, 'longitude' => 166.92],
+ 'NZ' => ['name' => 'New Zealand', 'latitude' => -41.0, 'longitude' => 174.0],
+ 'OM' => ['name' => 'Oman', 'latitude' => 21.0, 'longitude' => 57.0],
+ 'PK' => ['name' => 'Pakistan', 'latitude' => 30.0, 'longitude' => 70.0],
+ 'PS' => ['name' => 'Palestine', 'latitude' => 31.9, 'longitude' => 35.2],
+ 'PA' => ['name' => 'Panama', 'latitude' => 9.0, 'longitude' => -80.0],
+ 'PE' => ['name' => 'Peru', 'latitude' => -10.0, 'longitude' => -76.0],
+ 'PH' => ['name' => 'Philippines', 'latitude' => 13.0, 'longitude' => 122.0],
+ 'PW' => ['name' => 'Palau', 'latitude' => 7.5, 'longitude' => 134.5],
+ 'PG' => ['name' => 'Papua New Guinea', 'latitude' => -6.0, 'longitude' => 147.0],
+ 'PL' => ['name' => 'Poland', 'latitude' => 52.0, 'longitude' => 20.0],
+ 'KP' => ['name' => 'North Korea', 'latitude' => 40.0, 'longitude' => 127.0],
+ 'PT' => ['name' => 'Portugal', 'latitude' => 39.5, 'longitude' => -8.0],
+ 'PY' => ['name' => 'Paraguay', 'latitude' => -23.0, 'longitude' => -58.0],
+ 'QA' => ['name' => 'Qatar', 'latitude' => 25.5, 'longitude' => 51.25],
+ 'RO' => ['name' => 'Romania', 'latitude' => 46.0, 'longitude' => 25.0],
+ 'RU' => ['name' => 'Russia', 'latitude' => 60.0, 'longitude' => 100.0],
+ 'RW' => ['name' => 'Rwanda', 'latitude' => -2.0, 'longitude' => 30.0],
+ 'SA' => ['name' => 'Saudi Arabia', 'latitude' => 25.0, 'longitude' => 45.0],
+ 'SD' => ['name' => 'Sudan', 'latitude' => 15.0, 'longitude' => 30.0],
+ 'SN' => ['name' => 'Senegal', 'latitude' => 14.0, 'longitude' => -14.0],
+ 'SG' => ['name' => 'Singapore', 'latitude' => 1.37, 'longitude' => 103.8],
+ 'SB' => ['name' => 'Solomon Islands', 'latitude' => -8.0, 'longitude' => 159.0],
+ 'SL' => ['name' => 'Sierra Leone', 'latitude' => 8.5, 'longitude' => -11.5],
+ 'SV' => ['name' => 'El Salvador', 'latitude' => 13.83, 'longitude' => -88.92],
+ 'SM' => ['name' => 'San Marino', 'latitude' => 43.77, 'longitude' => 12.42],
+ 'SO' => ['name' => 'Somalia', 'latitude' => 10.0, 'longitude' => 49.0],
+ 'RS' => ['name' => 'Serbia', 'latitude' => 44.0, 'longitude' => 21.0],
+ 'SS' => ['name' => 'South Sudan', 'latitude' => 8.0, 'longitude' => 30.0],
+ 'ST' => ['name' => 'São Tomé and Príncipe', 'latitude' => 1.0, 'longitude' => 7.0],
+ 'SR' => ['name' => 'Suriname', 'latitude' => 4.0, 'longitude' => -56.0],
+ 'SK' => ['name' => 'Slovakia', 'latitude' => 48.67, 'longitude' => 19.5],
+ 'SI' => ['name' => 'Slovenia', 'latitude' => 46.0, 'longitude' => 15.0],
+ 'SE' => ['name' => 'Sweden', 'latitude' => 62.0, 'longitude' => 15.0],
+ 'SZ' => ['name' => 'Eswatini', 'latitude' => -26.5, 'longitude' => 31.5],
+ 'SC' => ['name' => 'Seychelles', 'latitude' => -4.58, 'longitude' => 55.67],
+ 'SY' => ['name' => 'Syria', 'latitude' => 35.0, 'longitude' => 38.0],
+ 'TD' => ['name' => 'Chad', 'latitude' => 15.0, 'longitude' => 19.0],
+ 'TG' => ['name' => 'Togo', 'latitude' => 8.0, 'longitude' => 1.17],
+ 'TH' => ['name' => 'Thailand', 'latitude' => 15.0, 'longitude' => 100.0],
+ 'TJ' => ['name' => 'Tajikistan', 'latitude' => 39.0, 'longitude' => 71.0],
+ 'TM' => ['name' => 'Turkmenistan', 'latitude' => 40.0, 'longitude' => 60.0],
+ 'TL' => ['name' => 'Timor-Leste', 'latitude' => -8.83, 'longitude' => 125.92],
+ 'TO' => ['name' => 'Tonga', 'latitude' => -20.0, 'longitude' => -175.0],
+ 'TT' => ['name' => 'Trinidad and Tobago', 'latitude' => 11.0, 'longitude' => -61.0],
+ 'TN' => ['name' => 'Tunisia', 'latitude' => 34.0, 'longitude' => 9.0],
+ 'TR' => ['name' => 'Turkey', 'latitude' => 39.0, 'longitude' => 35.0],
+ 'TV' => ['name' => 'Tuvalu', 'latitude' => -8.0, 'longitude' => 178.0],
+ 'TZ' => ['name' => 'Tanzania', 'latitude' => -6.0, 'longitude' => 35.0],
+ 'TW' => ['name' => 'Taiwan', 'latitude' => 23.5, 'longitude' => 121.0],
+ 'UG' => ['name' => 'Uganda', 'latitude' => 1.0, 'longitude' => 32.0],
+ 'UA' => ['name' => 'Ukraine', 'latitude' => 49.0, 'longitude' => 32.0],
+ 'UY' => ['name' => 'Uruguay', 'latitude' => -33.0, 'longitude' => -56.0],
+ 'US' => ['name' => 'United States', 'latitude' => 38.0, 'longitude' => -97.0],
+ 'UZ' => ['name' => 'Uzbekistan', 'latitude' => 41.0, 'longitude' => 64.0],
+ 'VA' => ['name' => 'Vatican City', 'latitude' => 41.9, 'longitude' => 12.45],
+ 'VC' => ['name' => 'Saint Vincent and the Grenadines', 'latitude' => 13.25, 'longitude' => -61.2],
+ 'VE' => ['name' => 'Venezuela', 'latitude' => 8.0, 'longitude' => -66.0],
+ 'VN' => ['name' => 'Vietnam', 'latitude' => 16.0, 'longitude' => 106.0],
+ 'VU' => ['name' => 'Vanuatu', 'latitude' => -16.0, 'longitude' => 167.0],
+ 'WS' => ['name' => 'Samoa', 'latitude' => -13.58, 'longitude' => -172.33],
+ 'YE' => ['name' => 'Yemen', 'latitude' => 15.0, 'longitude' => 48.0],
+ 'ZA' => ['name' => 'South Africa', 'latitude' => -29.0, 'longitude' => 24.0],
+ 'ZM' => ['name' => 'Zambia', 'latitude' => -15.0, 'longitude' => 30.0],
+ 'ZW' => ['name' => 'Zimbabwe', 'latitude' => -20.0, 'longitude' => 30.0],
];
diff --git a/app/config/platforms.php b/app/config/platforms.php
index 92e325bd44..86f0a756c9 100644
--- a/app/config/platforms.php
+++ b/app/config/platforms.php
@@ -11,7 +11,7 @@ return [
[
'key' => 'web',
'name' => 'Web',
- 'version' => '16.1.0',
+ 'version' => '17.0.1',
'url' => 'https://github.com/appwrite/sdk-for-web',
'package' => 'https://www.npmjs.com/package/appwrite',
'enabled' => true,
@@ -59,7 +59,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
- 'version' => '13.1.1',
+ 'version' => '15.0.0',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@@ -77,7 +77,7 @@ return [
[
'key' => 'apple',
'name' => 'Apple',
- 'version' => '7.1.0',
+ 'version' => '9.0.0',
'url' => 'https://github.com/appwrite/sdk-for-apple',
'package' => 'https://github.com/appwrite/sdk-for-apple',
'enabled' => true,
@@ -134,7 +134,7 @@ return [
[
'key' => 'react-native',
'name' => 'React Native',
- 'version' => '0.6.0',
+ 'version' => '0.7.1',
'url' => 'https://github.com/appwrite/sdk-for-react-native',
'package' => 'https://npmjs.com/package/react-native-appwrite',
'enabled' => true,
@@ -217,7 +217,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
- 'version' => '6.2.0',
+ 'version' => '6.2.2',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
@@ -245,7 +245,7 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
- 'version' => '14.2.0',
+ 'version' => '15.0.1',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,
@@ -299,7 +299,7 @@ return [
[
'key' => 'python',
'name' => 'Python',
- 'version' => '6.2.0',
+ 'version' => '9.0.2',
'url' => 'https://github.com/appwrite/sdk-for-python',
'package' => 'https://pypi.org/project/appwrite/',
'enabled' => true,
@@ -371,7 +371,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
- 'version' => '12.2.0',
+ 'version' => '14.0.0',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
@@ -411,7 +411,7 @@ return [
[
'key' => 'swift',
'name' => 'Swift',
- 'version' => '6.2.0',
+ 'version' => '8.0.0',
'url' => 'https://github.com/appwrite/sdk-for-swift',
'package' => 'https://github.com/appwrite/sdk-for-swift',
'enabled' => true,
diff --git a/app/config/regions.php b/app/config/regions.php
index 61029a9996..05e04930fe 100644
--- a/app/config/regions.php
+++ b/app/config/regions.php
@@ -3,72 +3,8 @@
return [
'default' => [
'$id' => 'default',
- 'name' => 'Frankfurt',
+ 'name' => 'default',
'disabled' => false,
- 'flag' => 'de',
- 'default' => true,
- ],
- 'fra' => [
- '$id' => 'fra',
- 'name' => 'Frankfurt',
- 'disabled' => false,
- 'flag' => 'de',
- 'default' => true,
- ],
- 'nyc' => [
- '$id' => 'nyc',
- 'name' => 'New York',
- 'disabled' => true,
- 'flag' => 'us',
- 'default' => true,
- ],
- 'sfo' => [
- '$id' => 'sfo',
- 'name' => 'San Francisco',
- 'disabled' => true,
- 'flag' => 'us',
- 'default' => true,
- ],
- 'blr' => [
- '$id' => 'blr',
- 'name' => 'Bengaluru',
- 'disabled' => true,
- 'flag' => 'in',
- 'default' => true,
- ],
- 'lon' => [
- '$id' => 'lon',
- 'name' => 'London',
- 'disabled' => true,
- 'flag' => 'gb',
- 'default' => true,
- ],
- 'ams' => [
- '$id' => 'ams',
- 'name' => 'Amsterdam',
- 'disabled' => true,
- 'flag' => 'nl',
- 'default' => true,
- ],
- 'sgp' => [
- '$id' => 'sgp',
- 'name' => 'Singapore',
- 'disabled' => true,
- 'flag' => 'sg',
- 'default' => true,
- ],
- 'tor' => [
- '$id' => 'tor',
- 'name' => 'Toronto',
- 'disabled' => true,
- 'flag' => 'ca',
- 'default' => true,
- ],
- 'syd' => [
- '$id' => 'syd',
- 'name' => 'Sydney',
- 'disabled' => true,
- 'flag' => 'au',
'default' => true,
],
];
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 820b1f55e0..316fe13116 100644
--- a/app/config/specs/open-api3-1.6.x-client.json
+++ b/app/config/specs/open-api3-1.6.x-client.json
@@ -3383,7 +3383,7 @@
"parameters": [
{
"name": "code",
- "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro.",
+ "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro, rupay.",
"required": true,
"schema": {
"type": "string",
@@ -3404,7 +3404,8 @@
"union-china-pay",
"visa",
"mir",
- "maestro"
+ "maestro",
+ "rupay"
],
"x-enum-name": "CreditCard",
"x-enum-keys": [
@@ -3423,7 +3424,8 @@
"Union China Pay",
"Visa",
"MIR",
- "Maestro"
+ "Maestro",
+ "Rupay"
]
},
"in": "path"
@@ -4365,7 +4367,7 @@
"tags": [
"databases"
],
- "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
+ "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
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 7f57dfc437..54161c4262 100644
--- a/app/config/specs/open-api3-1.6.x-console.json
+++ b/app/config/specs/open-api3-1.6.x-console.json
@@ -3387,7 +3387,7 @@
"parameters": [
{
"name": "code",
- "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro.",
+ "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro, rupay.",
"required": true,
"schema": {
"type": "string",
@@ -3408,7 +3408,8 @@
"union-china-pay",
"visa",
"mir",
- "maestro"
+ "maestro",
+ "rupay"
],
"x-enum-name": "CreditCard",
"x-enum-keys": [
@@ -3427,7 +3428,8 @@
"Union China Pay",
"Visa",
"MIR",
- "Maestro"
+ "Maestro",
+ "Rupay"
]
},
"in": "path"
@@ -6407,8 +6409,6 @@
},
"required": [
"required",
- "min",
- "max",
"default"
]
}
@@ -6644,8 +6644,6 @@
},
"required": [
"required",
- "min",
- "max",
"default"
]
}
@@ -7823,7 +7821,7 @@
"tags": [
"databases"
],
- "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
+ "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
@@ -11585,7 +11583,7 @@
},
"x-appwrite": {
"method": "getCertificate",
- "weight": 134,
+ "weight": 133,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11692,7 +11690,7 @@
},
"x-appwrite": {
"method": "getPubSub",
- "weight": 130,
+ "weight": 129,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11718,54 +11716,6 @@
]
}
},
- "\/health\/queue": {
- "get": {
- "summary": "Get queue",
- "operationId": "healthGetQueue",
- "tags": [
- "health"
- ],
- "description": "Check the Appwrite queue messaging servers are up and connection is successful.",
- "responses": {
- "200": {
- "description": "Health Status",
- "content": {
- "application\/json": {
- "schema": {
- "$ref": "#\/components\/schemas\/healthStatus"
- }
- }
- }
- }
- },
- "x-appwrite": {
- "method": "getQueue",
- "weight": 129,
- "cookies": false,
- "type": "",
- "deprecated": false,
- "demo": "health\/get-queue.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue.md",
- "rate-limit": 0,
- "rate-time": 3600,
- "rate-key": "url:{url},ip:{ip}",
- "scope": "health.read",
- "platforms": [
- "server"
- ],
- "packaging": false,
- "auth": {
- "Project": []
- }
- },
- "security": [
- {
- "Project": [],
- "Key": []
- }
- ]
- }
- },
"\/health\/queue\/builds": {
"get": {
"summary": "Get builds queue",
@@ -11788,7 +11738,7 @@
},
"x-appwrite": {
"method": "getQueueBuilds",
- "weight": 136,
+ "weight": 135,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11849,7 +11799,7 @@
},
"x-appwrite": {
"method": "getQueueCertificates",
- "weight": 135,
+ "weight": 134,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11910,7 +11860,7 @@
},
"x-appwrite": {
"method": "getQueueDatabases",
- "weight": 137,
+ "weight": 136,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11982,7 +11932,7 @@
},
"x-appwrite": {
"method": "getQueueDeletes",
- "weight": 138,
+ "weight": 137,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12081,8 +12031,9 @@
"v1-audits",
"v1-mails",
"v1-functions",
- "v1-usage",
- "v1-usage-dump",
+ "v1-stats-resources",
+ "v1-stats-usage",
+ "v1-stats-usage-dump",
"v1-webhooks",
"v1-certificates",
"v1-builds",
@@ -12130,7 +12081,7 @@
},
"x-appwrite": {
"method": "getQueueFunctions",
- "weight": 142,
+ "weight": 141,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12191,7 +12142,7 @@
},
"x-appwrite": {
"method": "getQueueLogs",
- "weight": 133,
+ "weight": 132,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12252,7 +12203,7 @@
},
"x-appwrite": {
"method": "getQueueMails",
- "weight": 139,
+ "weight": 138,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12313,7 +12264,7 @@
},
"x-appwrite": {
"method": "getQueueMessaging",
- "weight": 140,
+ "weight": 139,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12374,7 +12325,7 @@
},
"x-appwrite": {
"method": "getQueueMigrations",
- "weight": 141,
+ "weight": 140,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12413,14 +12364,14 @@
]
}
},
- "\/health\/queue\/usage": {
+ "\/health\/queue\/stats-resources": {
"get": {
- "summary": "Get usage queue",
- "operationId": "healthGetQueueUsage",
+ "summary": "Get stats resources queue",
+ "operationId": "healthGetQueueStatsResources",
"tags": [
"health"
],
- "description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
+ "description": "Get the number of metrics that are waiting to be processed in the Appwrite stats resources queue.",
"responses": {
"200": {
"description": "Health Queue",
@@ -12434,13 +12385,13 @@
}
},
"x-appwrite": {
- "method": "getQueueUsage",
- "weight": 143,
+ "method": "getQueueStatsResources",
+ "weight": 142,
"cookies": false,
"type": "",
"deprecated": false,
- "demo": "health\/get-queue-usage.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage.md",
+ "demo": "health\/get-queue-stats-resources.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-resources.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -12474,10 +12425,71 @@
]
}
},
- "\/health\/queue\/usage-dump": {
+ "\/health\/queue\/stats-usage": {
+ "get": {
+ "summary": "Get stats usage queue",
+ "operationId": "healthGetQueueUsage",
+ "tags": [
+ "health"
+ ],
+ "description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
+ "responses": {
+ "200": {
+ "description": "Health Queue",
+ "content": {
+ "application\/json": {
+ "schema": {
+ "$ref": "#\/components\/schemas\/healthQueue"
+ }
+ }
+ }
+ }
+ },
+ "x-appwrite": {
+ "method": "getQueueUsage",
+ "weight": 143,
+ "cookies": false,
+ "type": "",
+ "deprecated": false,
+ "demo": "health\/get-queue-usage.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage.md",
+ "rate-limit": 0,
+ "rate-time": 3600,
+ "rate-key": "url:{url},ip:{ip}",
+ "scope": "health.read",
+ "platforms": [
+ "server"
+ ],
+ "packaging": false,
+ "auth": {
+ "Project": []
+ }
+ },
+ "security": [
+ {
+ "Project": [],
+ "Key": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "threshold",
+ "description": "Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "format": "int32",
+ "default": 5000
+ },
+ "in": "query"
+ }
+ ]
+ }
+ },
+ "\/health\/queue\/stats-usage-dump": {
"get": {
"summary": "Get usage dump queue",
- "operationId": "healthGetQueueUsageDump",
+ "operationId": "healthGetQueueStatsUsageDump",
"tags": [
"health"
],
@@ -12495,13 +12507,13 @@
}
},
"x-appwrite": {
- "method": "getQueueUsageDump",
+ "method": "getQueueStatsUsageDump",
"weight": 144,
"cookies": false,
"type": "",
"deprecated": false,
- "demo": "health\/get-queue-usage-dump.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage-dump.md",
+ "demo": "health\/get-queue-stats-usage-dump.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage-dump.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -12557,7 +12569,7 @@
},
"x-appwrite": {
"method": "getQueueWebhooks",
- "weight": 132,
+ "weight": 131,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12714,7 +12726,7 @@
},
"x-appwrite": {
"method": "getTime",
- "weight": 131,
+ "weight": 130,
"cookies": false,
"type": "",
"deprecated": false,
@@ -17609,17 +17621,17 @@
},
"endpoint": {
"type": "string",
- "description": "Source's Appwrite Endpoint",
+ "description": "Source Appwrite endpoint",
"x-example": "https:\/\/example.com"
},
"projectId": {
"type": "string",
- "description": "Source's Project ID",
+ "description": "Source Project ID",
"x-example": ""
},
"apiKey": {
"type": "string",
- "description": "Source's API Key",
+ "description": "Source API Key",
"x-example": ""
}
},
@@ -36052,6 +36064,20 @@
"$ref": "#\/components\/schemas\/metric"
},
"x-example": []
+ },
+ "imageTransformations": {
+ "type": "array",
+ "description": "Aggregated number of files transformations per period.",
+ "items": {
+ "$ref": "#\/components\/schemas\/metric"
+ },
+ "x-example": []
+ },
+ "imageTransformationsTotal": {
+ "type": "integer",
+ "description": "Total aggregated number of files transformations.",
+ "x-example": 0,
+ "format": "int32"
}
},
"required": [
@@ -36059,7 +36085,9 @@
"filesTotal",
"filesStorageTotal",
"files",
- "storage"
+ "storage",
+ "imageTransformations",
+ "imageTransformationsTotal"
]
},
"usageFunctions": {
@@ -36597,6 +36625,20 @@
"$ref": "#\/components\/schemas\/metric"
},
"x-example": []
+ },
+ "imageTransformations": {
+ "type": "array",
+ "description": "An array of aggregated number of image transformations.",
+ "items": {
+ "$ref": "#\/components\/schemas\/metric"
+ },
+ "x-example": []
+ },
+ "imageTransformationsTotal": {
+ "type": "integer",
+ "description": "Total aggregated number of image transformations.",
+ "x-example": 0,
+ "format": "int32"
}
},
"required": [
@@ -36628,7 +36670,9 @@
"authPhoneEstimate",
"authPhoneCountryBreakdown",
"databasesReads",
- "databasesWrites"
+ "databasesWrites",
+ "imageTransformations",
+ "imageTransformationsTotal"
]
},
"headers": {
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 68d408762a..3d32d3e978 100644
--- a/app/config/specs/open-api3-1.6.x-server.json
+++ b/app/config/specs/open-api3-1.6.x-server.json
@@ -3077,7 +3077,7 @@
"parameters": [
{
"name": "code",
- "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro.",
+ "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro, rupay.",
"required": true,
"schema": {
"type": "string",
@@ -3098,7 +3098,8 @@
"union-china-pay",
"visa",
"mir",
- "maestro"
+ "maestro",
+ "rupay"
],
"x-enum-name": "CreditCard",
"x-enum-keys": [
@@ -3117,7 +3118,8 @@
"Union China Pay",
"Visa",
"MIR",
- "Maestro"
+ "Maestro",
+ "Rupay"
]
},
"in": "path"
@@ -5951,8 +5953,6 @@
},
"required": [
"required",
- "min",
- "max",
"default"
]
}
@@ -6190,8 +6190,6 @@
},
"required": [
"required",
- "min",
- "max",
"default"
]
}
@@ -7381,7 +7379,7 @@
"tags": [
"databases"
],
- "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
+ "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
@@ -10461,7 +10459,7 @@
},
"x-appwrite": {
"method": "getCertificate",
- "weight": 134,
+ "weight": 133,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10570,7 +10568,7 @@
},
"x-appwrite": {
"method": "getPubSub",
- "weight": 130,
+ "weight": 129,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10597,55 +10595,6 @@
]
}
},
- "\/health\/queue": {
- "get": {
- "summary": "Get queue",
- "operationId": "healthGetQueue",
- "tags": [
- "health"
- ],
- "description": "Check the Appwrite queue messaging servers are up and connection is successful.",
- "responses": {
- "200": {
- "description": "Health Status",
- "content": {
- "application\/json": {
- "schema": {
- "$ref": "#\/components\/schemas\/healthStatus"
- }
- }
- }
- }
- },
- "x-appwrite": {
- "method": "getQueue",
- "weight": 129,
- "cookies": false,
- "type": "",
- "deprecated": false,
- "demo": "health\/get-queue.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue.md",
- "rate-limit": 0,
- "rate-time": 3600,
- "rate-key": "url:{url},ip:{ip}",
- "scope": "health.read",
- "platforms": [
- "server"
- ],
- "packaging": false,
- "auth": {
- "Project": [],
- "Key": []
- }
- },
- "security": [
- {
- "Project": [],
- "Key": []
- }
- ]
- }
- },
"\/health\/queue\/builds": {
"get": {
"summary": "Get builds queue",
@@ -10668,7 +10617,7 @@
},
"x-appwrite": {
"method": "getQueueBuilds",
- "weight": 136,
+ "weight": 135,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10730,7 +10679,7 @@
},
"x-appwrite": {
"method": "getQueueCertificates",
- "weight": 135,
+ "weight": 134,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10792,7 +10741,7 @@
},
"x-appwrite": {
"method": "getQueueDatabases",
- "weight": 137,
+ "weight": 136,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10865,7 +10814,7 @@
},
"x-appwrite": {
"method": "getQueueDeletes",
- "weight": 138,
+ "weight": 137,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10966,8 +10915,9 @@
"v1-audits",
"v1-mails",
"v1-functions",
- "v1-usage",
- "v1-usage-dump",
+ "v1-stats-resources",
+ "v1-stats-usage",
+ "v1-stats-usage-dump",
"v1-webhooks",
"v1-certificates",
"v1-builds",
@@ -11015,7 +10965,7 @@
},
"x-appwrite": {
"method": "getQueueFunctions",
- "weight": 142,
+ "weight": 141,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11077,7 +11027,7 @@
},
"x-appwrite": {
"method": "getQueueLogs",
- "weight": 133,
+ "weight": 132,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11139,7 +11089,7 @@
},
"x-appwrite": {
"method": "getQueueMails",
- "weight": 139,
+ "weight": 138,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11201,7 +11151,7 @@
},
"x-appwrite": {
"method": "getQueueMessaging",
- "weight": 140,
+ "weight": 139,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11263,7 +11213,7 @@
},
"x-appwrite": {
"method": "getQueueMigrations",
- "weight": 141,
+ "weight": 140,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11303,14 +11253,14 @@
]
}
},
- "\/health\/queue\/usage": {
+ "\/health\/queue\/stats-resources": {
"get": {
- "summary": "Get usage queue",
- "operationId": "healthGetQueueUsage",
+ "summary": "Get stats resources queue",
+ "operationId": "healthGetQueueStatsResources",
"tags": [
"health"
],
- "description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
+ "description": "Get the number of metrics that are waiting to be processed in the Appwrite stats resources queue.",
"responses": {
"200": {
"description": "Health Queue",
@@ -11324,13 +11274,13 @@
}
},
"x-appwrite": {
- "method": "getQueueUsage",
- "weight": 143,
+ "method": "getQueueStatsResources",
+ "weight": 142,
"cookies": false,
"type": "",
"deprecated": false,
- "demo": "health\/get-queue-usage.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage.md",
+ "demo": "health\/get-queue-stats-resources.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-resources.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -11365,10 +11315,72 @@
]
}
},
- "\/health\/queue\/usage-dump": {
+ "\/health\/queue\/stats-usage": {
+ "get": {
+ "summary": "Get stats usage queue",
+ "operationId": "healthGetQueueUsage",
+ "tags": [
+ "health"
+ ],
+ "description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
+ "responses": {
+ "200": {
+ "description": "Health Queue",
+ "content": {
+ "application\/json": {
+ "schema": {
+ "$ref": "#\/components\/schemas\/healthQueue"
+ }
+ }
+ }
+ }
+ },
+ "x-appwrite": {
+ "method": "getQueueUsage",
+ "weight": 143,
+ "cookies": false,
+ "type": "",
+ "deprecated": false,
+ "demo": "health\/get-queue-usage.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage.md",
+ "rate-limit": 0,
+ "rate-time": 3600,
+ "rate-key": "url:{url},ip:{ip}",
+ "scope": "health.read",
+ "platforms": [
+ "server"
+ ],
+ "packaging": false,
+ "auth": {
+ "Project": [],
+ "Key": []
+ }
+ },
+ "security": [
+ {
+ "Project": [],
+ "Key": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "threshold",
+ "description": "Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "format": "int32",
+ "default": 5000
+ },
+ "in": "query"
+ }
+ ]
+ }
+ },
+ "\/health\/queue\/stats-usage-dump": {
"get": {
"summary": "Get usage dump queue",
- "operationId": "healthGetQueueUsageDump",
+ "operationId": "healthGetQueueStatsUsageDump",
"tags": [
"health"
],
@@ -11386,13 +11398,13 @@
}
},
"x-appwrite": {
- "method": "getQueueUsageDump",
+ "method": "getQueueStatsUsageDump",
"weight": 144,
"cookies": false,
"type": "",
"deprecated": false,
- "demo": "health\/get-queue-usage-dump.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage-dump.md",
+ "demo": "health\/get-queue-stats-usage-dump.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage-dump.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -11449,7 +11461,7 @@
},
"x-appwrite": {
"method": "getQueueWebhooks",
- "weight": 132,
+ "weight": 131,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11609,7 +11621,7 @@
},
"x-appwrite": {
"method": "getTime",
- "weight": 131,
+ "weight": 130,
"cookies": false,
"type": "",
"deprecated": false,
diff --git a/app/config/specs/open-api3-latest-client.json b/app/config/specs/open-api3-latest-client.json
index 820b1f55e0..e0b4e18e99 100644
--- a/app/config/specs/open-api3-latest-client.json
+++ b/app/config/specs/open-api3-latest-client.json
@@ -1,7 +1,7 @@
{
"openapi": "3.0.0",
"info": {
- "version": "1.6.1",
+ "version": "1.6.2",
"title": "Appwrite",
"description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)",
"termsOfService": "https:\/\/appwrite.io\/policy\/terms",
@@ -3383,7 +3383,7 @@
"parameters": [
{
"name": "code",
- "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro.",
+ "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro, rupay.",
"required": true,
"schema": {
"type": "string",
@@ -3404,7 +3404,8 @@
"union-china-pay",
"visa",
"mir",
- "maestro"
+ "maestro",
+ "rupay"
],
"x-enum-name": "CreditCard",
"x-enum-keys": [
@@ -3423,7 +3424,8 @@
"Union China Pay",
"Visa",
"MIR",
- "Maestro"
+ "Maestro",
+ "Rupay"
]
},
"in": "path"
@@ -4365,7 +4367,7 @@
"tags": [
"databases"
],
- "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
+ "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
@@ -6857,7 +6859,7 @@
},
{
"name": "queries",
- "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm",
+ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
"required": false,
"schema": {
"type": "array",
diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json
index 7f57dfc437..53a8962172 100644
--- a/app/config/specs/open-api3-latest-console.json
+++ b/app/config/specs/open-api3-latest-console.json
@@ -1,7 +1,7 @@
{
"openapi": "3.0.0",
"info": {
- "version": "1.6.1",
+ "version": "1.6.2",
"title": "Appwrite",
"description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)",
"termsOfService": "https:\/\/appwrite.io\/policy\/terms",
@@ -3387,7 +3387,7 @@
"parameters": [
{
"name": "code",
- "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro.",
+ "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro, rupay.",
"required": true,
"schema": {
"type": "string",
@@ -3408,7 +3408,8 @@
"union-china-pay",
"visa",
"mir",
- "maestro"
+ "maestro",
+ "rupay"
],
"x-enum-name": "CreditCard",
"x-enum-keys": [
@@ -3427,7 +3428,8 @@
"Union China Pay",
"Visa",
"MIR",
- "Maestro"
+ "Maestro",
+ "Rupay"
]
},
"in": "path"
@@ -6407,8 +6409,6 @@
},
"required": [
"required",
- "min",
- "max",
"default"
]
}
@@ -6644,8 +6644,6 @@
},
"required": [
"required",
- "min",
- "max",
"default"
]
}
@@ -7823,7 +7821,7 @@
"tags": [
"databases"
],
- "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
+ "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
@@ -11585,7 +11583,7 @@
},
"x-appwrite": {
"method": "getCertificate",
- "weight": 134,
+ "weight": 133,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11692,7 +11690,7 @@
},
"x-appwrite": {
"method": "getPubSub",
- "weight": 130,
+ "weight": 129,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11718,54 +11716,6 @@
]
}
},
- "\/health\/queue": {
- "get": {
- "summary": "Get queue",
- "operationId": "healthGetQueue",
- "tags": [
- "health"
- ],
- "description": "Check the Appwrite queue messaging servers are up and connection is successful.",
- "responses": {
- "200": {
- "description": "Health Status",
- "content": {
- "application\/json": {
- "schema": {
- "$ref": "#\/components\/schemas\/healthStatus"
- }
- }
- }
- }
- },
- "x-appwrite": {
- "method": "getQueue",
- "weight": 129,
- "cookies": false,
- "type": "",
- "deprecated": false,
- "demo": "health\/get-queue.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue.md",
- "rate-limit": 0,
- "rate-time": 3600,
- "rate-key": "url:{url},ip:{ip}",
- "scope": "health.read",
- "platforms": [
- "server"
- ],
- "packaging": false,
- "auth": {
- "Project": []
- }
- },
- "security": [
- {
- "Project": [],
- "Key": []
- }
- ]
- }
- },
"\/health\/queue\/builds": {
"get": {
"summary": "Get builds queue",
@@ -11788,7 +11738,7 @@
},
"x-appwrite": {
"method": "getQueueBuilds",
- "weight": 136,
+ "weight": 135,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11849,7 +11799,7 @@
},
"x-appwrite": {
"method": "getQueueCertificates",
- "weight": 135,
+ "weight": 134,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11910,7 +11860,7 @@
},
"x-appwrite": {
"method": "getQueueDatabases",
- "weight": 137,
+ "weight": 136,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11982,7 +11932,7 @@
},
"x-appwrite": {
"method": "getQueueDeletes",
- "weight": 138,
+ "weight": 137,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12081,8 +12031,9 @@
"v1-audits",
"v1-mails",
"v1-functions",
- "v1-usage",
- "v1-usage-dump",
+ "v1-stats-resources",
+ "v1-stats-usage",
+ "v1-stats-usage-dump",
"v1-webhooks",
"v1-certificates",
"v1-builds",
@@ -12130,7 +12081,7 @@
},
"x-appwrite": {
"method": "getQueueFunctions",
- "weight": 142,
+ "weight": 141,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12191,7 +12142,7 @@
},
"x-appwrite": {
"method": "getQueueLogs",
- "weight": 133,
+ "weight": 132,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12252,7 +12203,7 @@
},
"x-appwrite": {
"method": "getQueueMails",
- "weight": 139,
+ "weight": 138,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12313,7 +12264,7 @@
},
"x-appwrite": {
"method": "getQueueMessaging",
- "weight": 140,
+ "weight": 139,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12374,7 +12325,7 @@
},
"x-appwrite": {
"method": "getQueueMigrations",
- "weight": 141,
+ "weight": 140,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12413,14 +12364,14 @@
]
}
},
- "\/health\/queue\/usage": {
+ "\/health\/queue\/stats-resources": {
"get": {
- "summary": "Get usage queue",
- "operationId": "healthGetQueueUsage",
+ "summary": "Get stats resources queue",
+ "operationId": "healthGetQueueStatsResources",
"tags": [
"health"
],
- "description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
+ "description": "Get the number of metrics that are waiting to be processed in the Appwrite stats resources queue.",
"responses": {
"200": {
"description": "Health Queue",
@@ -12434,13 +12385,13 @@
}
},
"x-appwrite": {
- "method": "getQueueUsage",
- "weight": 143,
+ "method": "getQueueStatsResources",
+ "weight": 142,
"cookies": false,
"type": "",
"deprecated": false,
- "demo": "health\/get-queue-usage.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage.md",
+ "demo": "health\/get-queue-stats-resources.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-resources.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -12474,10 +12425,71 @@
]
}
},
- "\/health\/queue\/usage-dump": {
+ "\/health\/queue\/stats-usage": {
+ "get": {
+ "summary": "Get stats usage queue",
+ "operationId": "healthGetQueueUsage",
+ "tags": [
+ "health"
+ ],
+ "description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
+ "responses": {
+ "200": {
+ "description": "Health Queue",
+ "content": {
+ "application\/json": {
+ "schema": {
+ "$ref": "#\/components\/schemas\/healthQueue"
+ }
+ }
+ }
+ }
+ },
+ "x-appwrite": {
+ "method": "getQueueUsage",
+ "weight": 143,
+ "cookies": false,
+ "type": "",
+ "deprecated": false,
+ "demo": "health\/get-queue-usage.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage.md",
+ "rate-limit": 0,
+ "rate-time": 3600,
+ "rate-key": "url:{url},ip:{ip}",
+ "scope": "health.read",
+ "platforms": [
+ "server"
+ ],
+ "packaging": false,
+ "auth": {
+ "Project": []
+ }
+ },
+ "security": [
+ {
+ "Project": [],
+ "Key": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "threshold",
+ "description": "Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "format": "int32",
+ "default": 5000
+ },
+ "in": "query"
+ }
+ ]
+ }
+ },
+ "\/health\/queue\/stats-usage-dump": {
"get": {
"summary": "Get usage dump queue",
- "operationId": "healthGetQueueUsageDump",
+ "operationId": "healthGetQueueStatsUsageDump",
"tags": [
"health"
],
@@ -12495,13 +12507,13 @@
}
},
"x-appwrite": {
- "method": "getQueueUsageDump",
+ "method": "getQueueStatsUsageDump",
"weight": 144,
"cookies": false,
"type": "",
"deprecated": false,
- "demo": "health\/get-queue-usage-dump.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage-dump.md",
+ "demo": "health\/get-queue-stats-usage-dump.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage-dump.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -12557,7 +12569,7 @@
},
"x-appwrite": {
"method": "getQueueWebhooks",
- "weight": 132,
+ "weight": 131,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12714,7 +12726,7 @@
},
"x-appwrite": {
"method": "getTime",
- "weight": 131,
+ "weight": 130,
"cookies": false,
"type": "",
"deprecated": false,
@@ -17609,17 +17621,17 @@
},
"endpoint": {
"type": "string",
- "description": "Source's Appwrite Endpoint",
+ "description": "Source Appwrite endpoint",
"x-example": "https:\/\/example.com"
},
"projectId": {
"type": "string",
- "description": "Source's Project ID",
+ "description": "Source Project ID",
"x-example": ""
},
"apiKey": {
"type": "string",
- "description": "Source's API Key",
+ "description": "Source API Key",
"x-example": ""
}
},
@@ -25877,7 +25889,7 @@
},
{
"name": "queries",
- "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm",
+ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
"required": false,
"schema": {
"type": "array",
@@ -27970,6 +27982,30 @@
"x-example": ""
},
"in": "path"
+ },
+ {
+ "name": "queries",
+ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
+ "required": false,
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "default": []
+ },
+ "in": "query"
+ },
+ {
+ "name": "search",
+ "description": "Search term to filter your list results. Max length: 256 chars.",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "x-example": "",
+ "default": ""
+ },
+ "in": "query"
}
]
}
@@ -36052,6 +36088,20 @@
"$ref": "#\/components\/schemas\/metric"
},
"x-example": []
+ },
+ "imageTransformations": {
+ "type": "array",
+ "description": "Aggregated number of files transformations per period.",
+ "items": {
+ "$ref": "#\/components\/schemas\/metric"
+ },
+ "x-example": []
+ },
+ "imageTransformationsTotal": {
+ "type": "integer",
+ "description": "Total aggregated number of files transformations.",
+ "x-example": 0,
+ "format": "int32"
}
},
"required": [
@@ -36059,7 +36109,9 @@
"filesTotal",
"filesStorageTotal",
"files",
- "storage"
+ "storage",
+ "imageTransformations",
+ "imageTransformationsTotal"
]
},
"usageFunctions": {
@@ -36597,6 +36649,20 @@
"$ref": "#\/components\/schemas\/metric"
},
"x-example": []
+ },
+ "imageTransformations": {
+ "type": "array",
+ "description": "An array of aggregated number of image transformations.",
+ "items": {
+ "$ref": "#\/components\/schemas\/metric"
+ },
+ "x-example": []
+ },
+ "imageTransformationsTotal": {
+ "type": "integer",
+ "description": "Total aggregated number of image transformations.",
+ "x-example": 0,
+ "format": "int32"
}
},
"required": [
@@ -36628,7 +36694,9 @@
"authPhoneEstimate",
"authPhoneCountryBreakdown",
"databasesReads",
- "databasesWrites"
+ "databasesWrites",
+ "imageTransformations",
+ "imageTransformationsTotal"
]
},
"headers": {
diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json
index 68d408762a..7fd174b6e0 100644
--- a/app/config/specs/open-api3-latest-server.json
+++ b/app/config/specs/open-api3-latest-server.json
@@ -1,7 +1,7 @@
{
"openapi": "3.0.0",
"info": {
- "version": "1.6.1",
+ "version": "1.6.2",
"title": "Appwrite",
"description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)",
"termsOfService": "https:\/\/appwrite.io\/policy\/terms",
@@ -3077,7 +3077,7 @@
"parameters": [
{
"name": "code",
- "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro.",
+ "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro, rupay.",
"required": true,
"schema": {
"type": "string",
@@ -3098,7 +3098,8 @@
"union-china-pay",
"visa",
"mir",
- "maestro"
+ "maestro",
+ "rupay"
],
"x-enum-name": "CreditCard",
"x-enum-keys": [
@@ -3117,7 +3118,8 @@
"Union China Pay",
"Visa",
"MIR",
- "Maestro"
+ "Maestro",
+ "Rupay"
]
},
"in": "path"
@@ -5951,8 +5953,6 @@
},
"required": [
"required",
- "min",
- "max",
"default"
]
}
@@ -6190,8 +6190,6 @@
},
"required": [
"required",
- "min",
- "max",
"default"
]
}
@@ -7381,7 +7379,7 @@
"tags": [
"databases"
],
- "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
+ "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
@@ -10461,7 +10459,7 @@
},
"x-appwrite": {
"method": "getCertificate",
- "weight": 134,
+ "weight": 133,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10570,7 +10568,7 @@
},
"x-appwrite": {
"method": "getPubSub",
- "weight": 130,
+ "weight": 129,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10597,55 +10595,6 @@
]
}
},
- "\/health\/queue": {
- "get": {
- "summary": "Get queue",
- "operationId": "healthGetQueue",
- "tags": [
- "health"
- ],
- "description": "Check the Appwrite queue messaging servers are up and connection is successful.",
- "responses": {
- "200": {
- "description": "Health Status",
- "content": {
- "application\/json": {
- "schema": {
- "$ref": "#\/components\/schemas\/healthStatus"
- }
- }
- }
- }
- },
- "x-appwrite": {
- "method": "getQueue",
- "weight": 129,
- "cookies": false,
- "type": "",
- "deprecated": false,
- "demo": "health\/get-queue.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue.md",
- "rate-limit": 0,
- "rate-time": 3600,
- "rate-key": "url:{url},ip:{ip}",
- "scope": "health.read",
- "platforms": [
- "server"
- ],
- "packaging": false,
- "auth": {
- "Project": [],
- "Key": []
- }
- },
- "security": [
- {
- "Project": [],
- "Key": []
- }
- ]
- }
- },
"\/health\/queue\/builds": {
"get": {
"summary": "Get builds queue",
@@ -10668,7 +10617,7 @@
},
"x-appwrite": {
"method": "getQueueBuilds",
- "weight": 136,
+ "weight": 135,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10730,7 +10679,7 @@
},
"x-appwrite": {
"method": "getQueueCertificates",
- "weight": 135,
+ "weight": 134,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10792,7 +10741,7 @@
},
"x-appwrite": {
"method": "getQueueDatabases",
- "weight": 137,
+ "weight": 136,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10865,7 +10814,7 @@
},
"x-appwrite": {
"method": "getQueueDeletes",
- "weight": 138,
+ "weight": 137,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10966,8 +10915,9 @@
"v1-audits",
"v1-mails",
"v1-functions",
- "v1-usage",
- "v1-usage-dump",
+ "v1-stats-resources",
+ "v1-stats-usage",
+ "v1-stats-usage-dump",
"v1-webhooks",
"v1-certificates",
"v1-builds",
@@ -11015,7 +10965,7 @@
},
"x-appwrite": {
"method": "getQueueFunctions",
- "weight": 142,
+ "weight": 141,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11077,7 +11027,7 @@
},
"x-appwrite": {
"method": "getQueueLogs",
- "weight": 133,
+ "weight": 132,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11139,7 +11089,7 @@
},
"x-appwrite": {
"method": "getQueueMails",
- "weight": 139,
+ "weight": 138,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11201,7 +11151,7 @@
},
"x-appwrite": {
"method": "getQueueMessaging",
- "weight": 140,
+ "weight": 139,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11263,7 +11213,7 @@
},
"x-appwrite": {
"method": "getQueueMigrations",
- "weight": 141,
+ "weight": 140,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11303,14 +11253,14 @@
]
}
},
- "\/health\/queue\/usage": {
+ "\/health\/queue\/stats-resources": {
"get": {
- "summary": "Get usage queue",
- "operationId": "healthGetQueueUsage",
+ "summary": "Get stats resources queue",
+ "operationId": "healthGetQueueStatsResources",
"tags": [
"health"
],
- "description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
+ "description": "Get the number of metrics that are waiting to be processed in the Appwrite stats resources queue.",
"responses": {
"200": {
"description": "Health Queue",
@@ -11324,13 +11274,13 @@
}
},
"x-appwrite": {
- "method": "getQueueUsage",
- "weight": 143,
+ "method": "getQueueStatsResources",
+ "weight": 142,
"cookies": false,
"type": "",
"deprecated": false,
- "demo": "health\/get-queue-usage.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage.md",
+ "demo": "health\/get-queue-stats-resources.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-resources.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -11365,10 +11315,72 @@
]
}
},
- "\/health\/queue\/usage-dump": {
+ "\/health\/queue\/stats-usage": {
+ "get": {
+ "summary": "Get stats usage queue",
+ "operationId": "healthGetQueueUsage",
+ "tags": [
+ "health"
+ ],
+ "description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
+ "responses": {
+ "200": {
+ "description": "Health Queue",
+ "content": {
+ "application\/json": {
+ "schema": {
+ "$ref": "#\/components\/schemas\/healthQueue"
+ }
+ }
+ }
+ }
+ },
+ "x-appwrite": {
+ "method": "getQueueUsage",
+ "weight": 143,
+ "cookies": false,
+ "type": "",
+ "deprecated": false,
+ "demo": "health\/get-queue-usage.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage.md",
+ "rate-limit": 0,
+ "rate-time": 3600,
+ "rate-key": "url:{url},ip:{ip}",
+ "scope": "health.read",
+ "platforms": [
+ "server"
+ ],
+ "packaging": false,
+ "auth": {
+ "Project": [],
+ "Key": []
+ }
+ },
+ "security": [
+ {
+ "Project": [],
+ "Key": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "threshold",
+ "description": "Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "format": "int32",
+ "default": 5000
+ },
+ "in": "query"
+ }
+ ]
+ }
+ },
+ "\/health\/queue\/stats-usage-dump": {
"get": {
"summary": "Get usage dump queue",
- "operationId": "healthGetQueueUsageDump",
+ "operationId": "healthGetQueueStatsUsageDump",
"tags": [
"health"
],
@@ -11386,13 +11398,13 @@
}
},
"x-appwrite": {
- "method": "getQueueUsageDump",
+ "method": "getQueueStatsUsageDump",
"weight": 144,
"cookies": false,
"type": "",
"deprecated": false,
- "demo": "health\/get-queue-usage-dump.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage-dump.md",
+ "demo": "health\/get-queue-stats-usage-dump.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage-dump.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -11449,7 +11461,7 @@
},
"x-appwrite": {
"method": "getQueueWebhooks",
- "weight": 132,
+ "weight": 131,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11609,7 +11621,7 @@
},
"x-appwrite": {
"method": "getTime",
- "weight": 131,
+ "weight": 130,
"cookies": false,
"type": "",
"deprecated": false,
@@ -18087,7 +18099,7 @@
},
{
"name": "queries",
- "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm",
+ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
"required": false,
"schema": {
"type": "array",
@@ -20141,6 +20153,30 @@
"x-example": ""
},
"in": "path"
+ },
+ {
+ "name": "queries",
+ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
+ "required": false,
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "default": []
+ },
+ "in": "query"
+ },
+ {
+ "name": "search",
+ "description": "Search term to filter your list results. Max length: 256 chars.",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "x-example": "",
+ "default": ""
+ },
+ "in": "query"
}
]
}
diff --git a/app/config/specs/swagger2-1.6.x-client.json b/app/config/specs/swagger2-1.6.x-client.json
index c0980c44ce..8960bfaa5c 100644
--- a/app/config/specs/swagger2-1.6.x-client.json
+++ b/app/config/specs/swagger2-1.6.x-client.json
@@ -3557,7 +3557,7 @@
"parameters": [
{
"name": "code",
- "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro.",
+ "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro, rupay.",
"required": true,
"type": "string",
"x-example": "amex",
@@ -3577,7 +3577,8 @@
"union-china-pay",
"visa",
"mir",
- "maestro"
+ "maestro",
+ "rupay"
],
"x-enum-name": "CreditCard",
"x-enum-keys": [
@@ -3596,7 +3597,8 @@
"Union China Pay",
"Visa",
"MIR",
- "Maestro"
+ "Maestro",
+ "Rupay"
],
"in": "path"
},
@@ -4547,7 +4549,7 @@
"tags": [
"databases"
],
- "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
+ "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
diff --git a/app/config/specs/swagger2-1.6.x-console.json b/app/config/specs/swagger2-1.6.x-console.json
index 94b0d55199..8fc7e7daf3 100644
--- a/app/config/specs/swagger2-1.6.x-console.json
+++ b/app/config/specs/swagger2-1.6.x-console.json
@@ -3577,7 +3577,7 @@
"parameters": [
{
"name": "code",
- "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro.",
+ "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro, rupay.",
"required": true,
"type": "string",
"x-example": "amex",
@@ -3597,7 +3597,8 @@
"union-china-pay",
"visa",
"mir",
- "maestro"
+ "maestro",
+ "rupay"
],
"x-enum-name": "CreditCard",
"x-enum-keys": [
@@ -3616,7 +3617,8 @@
"Union China Pay",
"Visa",
"MIR",
- "Maestro"
+ "Maestro",
+ "Rupay"
],
"in": "path"
},
@@ -6607,8 +6609,6 @@
},
"required": [
"required",
- "min",
- "max",
"default"
]
}
@@ -6845,8 +6845,6 @@
},
"required": [
"required",
- "min",
- "max",
"default"
]
}
@@ -8012,7 +8010,7 @@
"tags": [
"databases"
],
- "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
+ "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
@@ -11810,7 +11808,7 @@
},
"x-appwrite": {
"method": "getCertificate",
- "weight": 134,
+ "weight": 133,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11919,7 +11917,7 @@
},
"x-appwrite": {
"method": "getPubSub",
- "weight": 130,
+ "weight": 129,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11945,56 +11943,6 @@
]
}
},
- "\/health\/queue": {
- "get": {
- "summary": "Get queue",
- "operationId": "healthGetQueue",
- "consumes": [
- "application\/json"
- ],
- "produces": [
- "application\/json"
- ],
- "tags": [
- "health"
- ],
- "description": "Check the Appwrite queue messaging servers are up and connection is successful.",
- "responses": {
- "200": {
- "description": "Health Status",
- "schema": {
- "$ref": "#\/definitions\/healthStatus"
- }
- }
- },
- "x-appwrite": {
- "method": "getQueue",
- "weight": 129,
- "cookies": false,
- "type": "",
- "deprecated": false,
- "demo": "health\/get-queue.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue.md",
- "rate-limit": 0,
- "rate-time": 3600,
- "rate-key": "url:{url},ip:{ip}",
- "scope": "health.read",
- "platforms": [
- "server"
- ],
- "packaging": false,
- "auth": {
- "Project": []
- }
- },
- "security": [
- {
- "Project": [],
- "Key": []
- }
- ]
- }
- },
"\/health\/queue\/builds": {
"get": {
"summary": "Get builds queue",
@@ -12019,7 +11967,7 @@
},
"x-appwrite": {
"method": "getQueueBuilds",
- "weight": 136,
+ "weight": 135,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12080,7 +12028,7 @@
},
"x-appwrite": {
"method": "getQueueCertificates",
- "weight": 135,
+ "weight": 134,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12141,7 +12089,7 @@
},
"x-appwrite": {
"method": "getQueueDatabases",
- "weight": 137,
+ "weight": 136,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12211,7 +12159,7 @@
},
"x-appwrite": {
"method": "getQueueDeletes",
- "weight": 138,
+ "weight": 137,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12309,8 +12257,9 @@
"v1-audits",
"v1-mails",
"v1-functions",
- "v1-usage",
- "v1-usage-dump",
+ "v1-stats-resources",
+ "v1-stats-usage",
+ "v1-stats-usage-dump",
"v1-webhooks",
"v1-certificates",
"v1-builds",
@@ -12357,7 +12306,7 @@
},
"x-appwrite": {
"method": "getQueueFunctions",
- "weight": 142,
+ "weight": 141,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12418,7 +12367,7 @@
},
"x-appwrite": {
"method": "getQueueLogs",
- "weight": 133,
+ "weight": 132,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12479,7 +12428,7 @@
},
"x-appwrite": {
"method": "getQueueMails",
- "weight": 139,
+ "weight": 138,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12540,7 +12489,7 @@
},
"x-appwrite": {
"method": "getQueueMessaging",
- "weight": 140,
+ "weight": 139,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12601,7 +12550,7 @@
},
"x-appwrite": {
"method": "getQueueMigrations",
- "weight": 141,
+ "weight": 140,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12638,10 +12587,10 @@
]
}
},
- "\/health\/queue\/usage": {
+ "\/health\/queue\/stats-resources": {
"get": {
- "summary": "Get usage queue",
- "operationId": "healthGetQueueUsage",
+ "summary": "Get stats resources queue",
+ "operationId": "healthGetQueueStatsResources",
"consumes": [
"application\/json"
],
@@ -12651,7 +12600,7 @@
"tags": [
"health"
],
- "description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
+ "description": "Get the number of metrics that are waiting to be processed in the Appwrite stats resources queue.",
"responses": {
"200": {
"description": "Health Queue",
@@ -12661,13 +12610,13 @@
}
},
"x-appwrite": {
- "method": "getQueueUsage",
- "weight": 143,
+ "method": "getQueueStatsResources",
+ "weight": 142,
"cookies": false,
"type": "",
"deprecated": false,
- "demo": "health\/get-queue-usage.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage.md",
+ "demo": "health\/get-queue-stats-resources.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-resources.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -12699,10 +12648,71 @@
]
}
},
- "\/health\/queue\/usage-dump": {
+ "\/health\/queue\/stats-usage": {
+ "get": {
+ "summary": "Get stats usage queue",
+ "operationId": "healthGetQueueUsage",
+ "consumes": [
+ "application\/json"
+ ],
+ "produces": [
+ "application\/json"
+ ],
+ "tags": [
+ "health"
+ ],
+ "description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
+ "responses": {
+ "200": {
+ "description": "Health Queue",
+ "schema": {
+ "$ref": "#\/definitions\/healthQueue"
+ }
+ }
+ },
+ "x-appwrite": {
+ "method": "getQueueUsage",
+ "weight": 143,
+ "cookies": false,
+ "type": "",
+ "deprecated": false,
+ "demo": "health\/get-queue-usage.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage.md",
+ "rate-limit": 0,
+ "rate-time": 3600,
+ "rate-key": "url:{url},ip:{ip}",
+ "scope": "health.read",
+ "platforms": [
+ "server"
+ ],
+ "packaging": false,
+ "auth": {
+ "Project": []
+ }
+ },
+ "security": [
+ {
+ "Project": [],
+ "Key": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "threshold",
+ "description": "Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.",
+ "required": false,
+ "type": "integer",
+ "format": "int32",
+ "default": 5000,
+ "in": "query"
+ }
+ ]
+ }
+ },
+ "\/health\/queue\/stats-usage-dump": {
"get": {
"summary": "Get usage dump queue",
- "operationId": "healthGetQueueUsageDump",
+ "operationId": "healthGetQueueStatsUsageDump",
"consumes": [
"application\/json"
],
@@ -12722,13 +12732,13 @@
}
},
"x-appwrite": {
- "method": "getQueueUsageDump",
+ "method": "getQueueStatsUsageDump",
"weight": 144,
"cookies": false,
"type": "",
"deprecated": false,
- "demo": "health\/get-queue-usage-dump.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage-dump.md",
+ "demo": "health\/get-queue-stats-usage-dump.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage-dump.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -12784,7 +12794,7 @@
},
"x-appwrite": {
"method": "getQueueWebhooks",
- "weight": 132,
+ "weight": 131,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12945,7 +12955,7 @@
},
"x-appwrite": {
"method": "getTime",
- "weight": 131,
+ "weight": 130,
"cookies": false,
"type": "",
"deprecated": false,
@@ -18070,19 +18080,19 @@
},
"endpoint": {
"type": "string",
- "description": "Source's Appwrite Endpoint",
+ "description": "Source Appwrite endpoint",
"default": null,
"x-example": "https:\/\/example.com"
},
"projectId": {
"type": "string",
- "description": "Source's Project ID",
+ "description": "Source Project ID",
"default": null,
"x-example": ""
},
"apiKey": {
"type": "string",
- "description": "Source's API Key",
+ "description": "Source API Key",
"default": null,
"x-example": ""
}
@@ -36608,6 +36618,21 @@
"$ref": "#\/definitions\/metric"
},
"x-example": []
+ },
+ "imageTransformations": {
+ "type": "array",
+ "description": "Aggregated number of files transformations per period.",
+ "items": {
+ "type": "object",
+ "$ref": "#\/definitions\/metric"
+ },
+ "x-example": []
+ },
+ "imageTransformationsTotal": {
+ "type": "integer",
+ "description": "Total aggregated number of files transformations.",
+ "x-example": 0,
+ "format": "int32"
}
},
"required": [
@@ -36615,7 +36640,9 @@
"filesTotal",
"filesStorageTotal",
"files",
- "storage"
+ "storage",
+ "imageTransformations",
+ "imageTransformationsTotal"
]
},
"usageFunctions": {
@@ -37185,6 +37212,21 @@
"$ref": "#\/definitions\/metric"
},
"x-example": []
+ },
+ "imageTransformations": {
+ "type": "array",
+ "description": "An array of aggregated number of image transformations.",
+ "items": {
+ "type": "object",
+ "$ref": "#\/definitions\/metric"
+ },
+ "x-example": []
+ },
+ "imageTransformationsTotal": {
+ "type": "integer",
+ "description": "Total aggregated number of image transformations.",
+ "x-example": 0,
+ "format": "int32"
}
},
"required": [
@@ -37216,7 +37258,9 @@
"authPhoneEstimate",
"authPhoneCountryBreakdown",
"databasesReads",
- "databasesWrites"
+ "databasesWrites",
+ "imageTransformations",
+ "imageTransformationsTotal"
]
},
"headers": {
diff --git a/app/config/specs/swagger2-1.6.x-server.json b/app/config/specs/swagger2-1.6.x-server.json
index e38495629c..83757c94f4 100644
--- a/app/config/specs/swagger2-1.6.x-server.json
+++ b/app/config/specs/swagger2-1.6.x-server.json
@@ -3261,7 +3261,7 @@
"parameters": [
{
"name": "code",
- "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro.",
+ "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro, rupay.",
"required": true,
"type": "string",
"x-example": "amex",
@@ -3281,7 +3281,8 @@
"union-china-pay",
"visa",
"mir",
- "maestro"
+ "maestro",
+ "rupay"
],
"x-enum-name": "CreditCard",
"x-enum-keys": [
@@ -3300,7 +3301,8 @@
"Union China Pay",
"Visa",
"MIR",
- "Maestro"
+ "Maestro",
+ "Rupay"
],
"in": "path"
},
@@ -6133,8 +6135,6 @@
},
"required": [
"required",
- "min",
- "max",
"default"
]
}
@@ -6373,8 +6373,6 @@
},
"required": [
"required",
- "min",
- "max",
"default"
]
}
@@ -7552,7 +7550,7 @@
"tags": [
"databases"
],
- "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
+ "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
@@ -10689,7 +10687,7 @@
},
"x-appwrite": {
"method": "getCertificate",
- "weight": 134,
+ "weight": 133,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10800,7 +10798,7 @@
},
"x-appwrite": {
"method": "getPubSub",
- "weight": 130,
+ "weight": 129,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10827,57 +10825,6 @@
]
}
},
- "\/health\/queue": {
- "get": {
- "summary": "Get queue",
- "operationId": "healthGetQueue",
- "consumes": [
- "application\/json"
- ],
- "produces": [
- "application\/json"
- ],
- "tags": [
- "health"
- ],
- "description": "Check the Appwrite queue messaging servers are up and connection is successful.",
- "responses": {
- "200": {
- "description": "Health Status",
- "schema": {
- "$ref": "#\/definitions\/healthStatus"
- }
- }
- },
- "x-appwrite": {
- "method": "getQueue",
- "weight": 129,
- "cookies": false,
- "type": "",
- "deprecated": false,
- "demo": "health\/get-queue.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue.md",
- "rate-limit": 0,
- "rate-time": 3600,
- "rate-key": "url:{url},ip:{ip}",
- "scope": "health.read",
- "platforms": [
- "server"
- ],
- "packaging": false,
- "auth": {
- "Project": [],
- "Key": []
- }
- },
- "security": [
- {
- "Project": [],
- "Key": []
- }
- ]
- }
- },
"\/health\/queue\/builds": {
"get": {
"summary": "Get builds queue",
@@ -10902,7 +10849,7 @@
},
"x-appwrite": {
"method": "getQueueBuilds",
- "weight": 136,
+ "weight": 135,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10964,7 +10911,7 @@
},
"x-appwrite": {
"method": "getQueueCertificates",
- "weight": 135,
+ "weight": 134,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11026,7 +10973,7 @@
},
"x-appwrite": {
"method": "getQueueDatabases",
- "weight": 137,
+ "weight": 136,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11097,7 +11044,7 @@
},
"x-appwrite": {
"method": "getQueueDeletes",
- "weight": 138,
+ "weight": 137,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11197,8 +11144,9 @@
"v1-audits",
"v1-mails",
"v1-functions",
- "v1-usage",
- "v1-usage-dump",
+ "v1-stats-resources",
+ "v1-stats-usage",
+ "v1-stats-usage-dump",
"v1-webhooks",
"v1-certificates",
"v1-builds",
@@ -11245,7 +11193,7 @@
},
"x-appwrite": {
"method": "getQueueFunctions",
- "weight": 142,
+ "weight": 141,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11307,7 +11255,7 @@
},
"x-appwrite": {
"method": "getQueueLogs",
- "weight": 133,
+ "weight": 132,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11369,7 +11317,7 @@
},
"x-appwrite": {
"method": "getQueueMails",
- "weight": 139,
+ "weight": 138,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11431,7 +11379,7 @@
},
"x-appwrite": {
"method": "getQueueMessaging",
- "weight": 140,
+ "weight": 139,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11493,7 +11441,7 @@
},
"x-appwrite": {
"method": "getQueueMigrations",
- "weight": 141,
+ "weight": 140,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11531,10 +11479,10 @@
]
}
},
- "\/health\/queue\/usage": {
+ "\/health\/queue\/stats-resources": {
"get": {
- "summary": "Get usage queue",
- "operationId": "healthGetQueueUsage",
+ "summary": "Get stats resources queue",
+ "operationId": "healthGetQueueStatsResources",
"consumes": [
"application\/json"
],
@@ -11544,7 +11492,7 @@
"tags": [
"health"
],
- "description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
+ "description": "Get the number of metrics that are waiting to be processed in the Appwrite stats resources queue.",
"responses": {
"200": {
"description": "Health Queue",
@@ -11554,13 +11502,13 @@
}
},
"x-appwrite": {
- "method": "getQueueUsage",
- "weight": 143,
+ "method": "getQueueStatsResources",
+ "weight": 142,
"cookies": false,
"type": "",
"deprecated": false,
- "demo": "health\/get-queue-usage.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage.md",
+ "demo": "health\/get-queue-stats-resources.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-resources.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -11593,10 +11541,72 @@
]
}
},
- "\/health\/queue\/usage-dump": {
+ "\/health\/queue\/stats-usage": {
+ "get": {
+ "summary": "Get stats usage queue",
+ "operationId": "healthGetQueueUsage",
+ "consumes": [
+ "application\/json"
+ ],
+ "produces": [
+ "application\/json"
+ ],
+ "tags": [
+ "health"
+ ],
+ "description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
+ "responses": {
+ "200": {
+ "description": "Health Queue",
+ "schema": {
+ "$ref": "#\/definitions\/healthQueue"
+ }
+ }
+ },
+ "x-appwrite": {
+ "method": "getQueueUsage",
+ "weight": 143,
+ "cookies": false,
+ "type": "",
+ "deprecated": false,
+ "demo": "health\/get-queue-usage.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage.md",
+ "rate-limit": 0,
+ "rate-time": 3600,
+ "rate-key": "url:{url},ip:{ip}",
+ "scope": "health.read",
+ "platforms": [
+ "server"
+ ],
+ "packaging": false,
+ "auth": {
+ "Project": [],
+ "Key": []
+ }
+ },
+ "security": [
+ {
+ "Project": [],
+ "Key": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "threshold",
+ "description": "Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.",
+ "required": false,
+ "type": "integer",
+ "format": "int32",
+ "default": 5000,
+ "in": "query"
+ }
+ ]
+ }
+ },
+ "\/health\/queue\/stats-usage-dump": {
"get": {
"summary": "Get usage dump queue",
- "operationId": "healthGetQueueUsageDump",
+ "operationId": "healthGetQueueStatsUsageDump",
"consumes": [
"application\/json"
],
@@ -11616,13 +11626,13 @@
}
},
"x-appwrite": {
- "method": "getQueueUsageDump",
+ "method": "getQueueStatsUsageDump",
"weight": 144,
"cookies": false,
"type": "",
"deprecated": false,
- "demo": "health\/get-queue-usage-dump.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage-dump.md",
+ "demo": "health\/get-queue-stats-usage-dump.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage-dump.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -11679,7 +11689,7 @@
},
"x-appwrite": {
"method": "getQueueWebhooks",
- "weight": 132,
+ "weight": 131,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11843,7 +11853,7 @@
},
"x-appwrite": {
"method": "getTime",
- "weight": 131,
+ "weight": 130,
"cookies": false,
"type": "",
"deprecated": false,
diff --git a/app/config/specs/swagger2-latest-client.json b/app/config/specs/swagger2-latest-client.json
index c0980c44ce..2ea4847792 100644
--- a/app/config/specs/swagger2-latest-client.json
+++ b/app/config/specs/swagger2-latest-client.json
@@ -1,7 +1,7 @@
{
"swagger": "2.0",
"info": {
- "version": "1.6.1",
+ "version": "1.6.2",
"title": "Appwrite",
"description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)",
"termsOfService": "https:\/\/appwrite.io\/policy\/terms",
@@ -3557,7 +3557,7 @@
"parameters": [
{
"name": "code",
- "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro.",
+ "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro, rupay.",
"required": true,
"type": "string",
"x-example": "amex",
@@ -3577,7 +3577,8 @@
"union-china-pay",
"visa",
"mir",
- "maestro"
+ "maestro",
+ "rupay"
],
"x-enum-name": "CreditCard",
"x-enum-keys": [
@@ -3596,7 +3597,8 @@
"Union China Pay",
"Visa",
"MIR",
- "Maestro"
+ "Maestro",
+ "Rupay"
],
"in": "path"
},
@@ -4547,7 +4549,7 @@
"tags": [
"databases"
],
- "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
+ "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
@@ -7067,7 +7069,7 @@
},
{
"name": "queries",
- "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm",
+ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
"required": false,
"type": "array",
"collectionFormat": "multi",
diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json
index 94b0d55199..6e6b1317ee 100644
--- a/app/config/specs/swagger2-latest-console.json
+++ b/app/config/specs/swagger2-latest-console.json
@@ -1,7 +1,7 @@
{
"swagger": "2.0",
"info": {
- "version": "1.6.1",
+ "version": "1.6.2",
"title": "Appwrite",
"description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)",
"termsOfService": "https:\/\/appwrite.io\/policy\/terms",
@@ -3577,7 +3577,7 @@
"parameters": [
{
"name": "code",
- "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro.",
+ "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro, rupay.",
"required": true,
"type": "string",
"x-example": "amex",
@@ -3597,7 +3597,8 @@
"union-china-pay",
"visa",
"mir",
- "maestro"
+ "maestro",
+ "rupay"
],
"x-enum-name": "CreditCard",
"x-enum-keys": [
@@ -3616,7 +3617,8 @@
"Union China Pay",
"Visa",
"MIR",
- "Maestro"
+ "Maestro",
+ "Rupay"
],
"in": "path"
},
@@ -6607,8 +6609,6 @@
},
"required": [
"required",
- "min",
- "max",
"default"
]
}
@@ -6845,8 +6845,6 @@
},
"required": [
"required",
- "min",
- "max",
"default"
]
}
@@ -8012,7 +8010,7 @@
"tags": [
"databases"
],
- "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
+ "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
@@ -11810,7 +11808,7 @@
},
"x-appwrite": {
"method": "getCertificate",
- "weight": 134,
+ "weight": 133,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11919,7 +11917,7 @@
},
"x-appwrite": {
"method": "getPubSub",
- "weight": 130,
+ "weight": 129,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11945,56 +11943,6 @@
]
}
},
- "\/health\/queue": {
- "get": {
- "summary": "Get queue",
- "operationId": "healthGetQueue",
- "consumes": [
- "application\/json"
- ],
- "produces": [
- "application\/json"
- ],
- "tags": [
- "health"
- ],
- "description": "Check the Appwrite queue messaging servers are up and connection is successful.",
- "responses": {
- "200": {
- "description": "Health Status",
- "schema": {
- "$ref": "#\/definitions\/healthStatus"
- }
- }
- },
- "x-appwrite": {
- "method": "getQueue",
- "weight": 129,
- "cookies": false,
- "type": "",
- "deprecated": false,
- "demo": "health\/get-queue.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue.md",
- "rate-limit": 0,
- "rate-time": 3600,
- "rate-key": "url:{url},ip:{ip}",
- "scope": "health.read",
- "platforms": [
- "server"
- ],
- "packaging": false,
- "auth": {
- "Project": []
- }
- },
- "security": [
- {
- "Project": [],
- "Key": []
- }
- ]
- }
- },
"\/health\/queue\/builds": {
"get": {
"summary": "Get builds queue",
@@ -12019,7 +11967,7 @@
},
"x-appwrite": {
"method": "getQueueBuilds",
- "weight": 136,
+ "weight": 135,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12080,7 +12028,7 @@
},
"x-appwrite": {
"method": "getQueueCertificates",
- "weight": 135,
+ "weight": 134,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12141,7 +12089,7 @@
},
"x-appwrite": {
"method": "getQueueDatabases",
- "weight": 137,
+ "weight": 136,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12211,7 +12159,7 @@
},
"x-appwrite": {
"method": "getQueueDeletes",
- "weight": 138,
+ "weight": 137,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12309,8 +12257,9 @@
"v1-audits",
"v1-mails",
"v1-functions",
- "v1-usage",
- "v1-usage-dump",
+ "v1-stats-resources",
+ "v1-stats-usage",
+ "v1-stats-usage-dump",
"v1-webhooks",
"v1-certificates",
"v1-builds",
@@ -12357,7 +12306,7 @@
},
"x-appwrite": {
"method": "getQueueFunctions",
- "weight": 142,
+ "weight": 141,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12418,7 +12367,7 @@
},
"x-appwrite": {
"method": "getQueueLogs",
- "weight": 133,
+ "weight": 132,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12479,7 +12428,7 @@
},
"x-appwrite": {
"method": "getQueueMails",
- "weight": 139,
+ "weight": 138,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12540,7 +12489,7 @@
},
"x-appwrite": {
"method": "getQueueMessaging",
- "weight": 140,
+ "weight": 139,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12601,7 +12550,7 @@
},
"x-appwrite": {
"method": "getQueueMigrations",
- "weight": 141,
+ "weight": 140,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12638,10 +12587,10 @@
]
}
},
- "\/health\/queue\/usage": {
+ "\/health\/queue\/stats-resources": {
"get": {
- "summary": "Get usage queue",
- "operationId": "healthGetQueueUsage",
+ "summary": "Get stats resources queue",
+ "operationId": "healthGetQueueStatsResources",
"consumes": [
"application\/json"
],
@@ -12651,7 +12600,7 @@
"tags": [
"health"
],
- "description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
+ "description": "Get the number of metrics that are waiting to be processed in the Appwrite stats resources queue.",
"responses": {
"200": {
"description": "Health Queue",
@@ -12661,13 +12610,13 @@
}
},
"x-appwrite": {
- "method": "getQueueUsage",
- "weight": 143,
+ "method": "getQueueStatsResources",
+ "weight": 142,
"cookies": false,
"type": "",
"deprecated": false,
- "demo": "health\/get-queue-usage.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage.md",
+ "demo": "health\/get-queue-stats-resources.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-resources.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -12699,10 +12648,71 @@
]
}
},
- "\/health\/queue\/usage-dump": {
+ "\/health\/queue\/stats-usage": {
+ "get": {
+ "summary": "Get stats usage queue",
+ "operationId": "healthGetQueueUsage",
+ "consumes": [
+ "application\/json"
+ ],
+ "produces": [
+ "application\/json"
+ ],
+ "tags": [
+ "health"
+ ],
+ "description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
+ "responses": {
+ "200": {
+ "description": "Health Queue",
+ "schema": {
+ "$ref": "#\/definitions\/healthQueue"
+ }
+ }
+ },
+ "x-appwrite": {
+ "method": "getQueueUsage",
+ "weight": 143,
+ "cookies": false,
+ "type": "",
+ "deprecated": false,
+ "demo": "health\/get-queue-usage.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage.md",
+ "rate-limit": 0,
+ "rate-time": 3600,
+ "rate-key": "url:{url},ip:{ip}",
+ "scope": "health.read",
+ "platforms": [
+ "server"
+ ],
+ "packaging": false,
+ "auth": {
+ "Project": []
+ }
+ },
+ "security": [
+ {
+ "Project": [],
+ "Key": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "threshold",
+ "description": "Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.",
+ "required": false,
+ "type": "integer",
+ "format": "int32",
+ "default": 5000,
+ "in": "query"
+ }
+ ]
+ }
+ },
+ "\/health\/queue\/stats-usage-dump": {
"get": {
"summary": "Get usage dump queue",
- "operationId": "healthGetQueueUsageDump",
+ "operationId": "healthGetQueueStatsUsageDump",
"consumes": [
"application\/json"
],
@@ -12722,13 +12732,13 @@
}
},
"x-appwrite": {
- "method": "getQueueUsageDump",
+ "method": "getQueueStatsUsageDump",
"weight": 144,
"cookies": false,
"type": "",
"deprecated": false,
- "demo": "health\/get-queue-usage-dump.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage-dump.md",
+ "demo": "health\/get-queue-stats-usage-dump.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage-dump.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -12784,7 +12794,7 @@
},
"x-appwrite": {
"method": "getQueueWebhooks",
- "weight": 132,
+ "weight": 131,
"cookies": false,
"type": "",
"deprecated": false,
@@ -12945,7 +12955,7 @@
},
"x-appwrite": {
"method": "getTime",
- "weight": 131,
+ "weight": 130,
"cookies": false,
"type": "",
"deprecated": false,
@@ -18070,19 +18080,19 @@
},
"endpoint": {
"type": "string",
- "description": "Source's Appwrite Endpoint",
+ "description": "Source Appwrite endpoint",
"default": null,
"x-example": "https:\/\/example.com"
},
"projectId": {
"type": "string",
- "description": "Source's Project ID",
+ "description": "Source Project ID",
"default": null,
"x-example": ""
},
"apiKey": {
"type": "string",
- "description": "Source's API Key",
+ "description": "Source API Key",
"default": null,
"x-example": ""
}
@@ -26361,7 +26371,7 @@
},
{
"name": "queries",
- "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm",
+ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
"required": false,
"type": "array",
"collectionFormat": "multi",
@@ -28504,6 +28514,27 @@
"type": "string",
"x-example": "",
"in": "path"
+ },
+ {
+ "name": "queries",
+ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
+ "required": false,
+ "type": "array",
+ "collectionFormat": "multi",
+ "items": {
+ "type": "string"
+ },
+ "default": [],
+ "in": "query"
+ },
+ {
+ "name": "search",
+ "description": "Search term to filter your list results. Max length: 256 chars.",
+ "required": false,
+ "type": "string",
+ "x-example": "",
+ "default": "",
+ "in": "query"
}
]
}
@@ -36608,6 +36639,21 @@
"$ref": "#\/definitions\/metric"
},
"x-example": []
+ },
+ "imageTransformations": {
+ "type": "array",
+ "description": "Aggregated number of files transformations per period.",
+ "items": {
+ "type": "object",
+ "$ref": "#\/definitions\/metric"
+ },
+ "x-example": []
+ },
+ "imageTransformationsTotal": {
+ "type": "integer",
+ "description": "Total aggregated number of files transformations.",
+ "x-example": 0,
+ "format": "int32"
}
},
"required": [
@@ -36615,7 +36661,9 @@
"filesTotal",
"filesStorageTotal",
"files",
- "storage"
+ "storage",
+ "imageTransformations",
+ "imageTransformationsTotal"
]
},
"usageFunctions": {
@@ -37185,6 +37233,21 @@
"$ref": "#\/definitions\/metric"
},
"x-example": []
+ },
+ "imageTransformations": {
+ "type": "array",
+ "description": "An array of aggregated number of image transformations.",
+ "items": {
+ "type": "object",
+ "$ref": "#\/definitions\/metric"
+ },
+ "x-example": []
+ },
+ "imageTransformationsTotal": {
+ "type": "integer",
+ "description": "Total aggregated number of image transformations.",
+ "x-example": 0,
+ "format": "int32"
}
},
"required": [
@@ -37216,7 +37279,9 @@
"authPhoneEstimate",
"authPhoneCountryBreakdown",
"databasesReads",
- "databasesWrites"
+ "databasesWrites",
+ "imageTransformations",
+ "imageTransformationsTotal"
]
},
"headers": {
diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json
index e38495629c..7c65c89764 100644
--- a/app/config/specs/swagger2-latest-server.json
+++ b/app/config/specs/swagger2-latest-server.json
@@ -1,7 +1,7 @@
{
"swagger": "2.0",
"info": {
- "version": "1.6.1",
+ "version": "1.6.2",
"title": "Appwrite",
"description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)",
"termsOfService": "https:\/\/appwrite.io\/policy\/terms",
@@ -3261,7 +3261,7 @@
"parameters": [
{
"name": "code",
- "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro.",
+ "description": "Credit Card Code. Possible values: amex, argencard, cabal, cencosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa, mir, maestro, rupay.",
"required": true,
"type": "string",
"x-example": "amex",
@@ -3281,7 +3281,8 @@
"union-china-pay",
"visa",
"mir",
- "maestro"
+ "maestro",
+ "rupay"
],
"x-enum-name": "CreditCard",
"x-enum-keys": [
@@ -3300,7 +3301,8 @@
"Union China Pay",
"Visa",
"MIR",
- "Maestro"
+ "Maestro",
+ "Rupay"
],
"in": "path"
},
@@ -6133,8 +6135,6 @@
},
"required": [
"required",
- "min",
- "max",
"default"
]
}
@@ -6373,8 +6373,6 @@
},
"required": [
"required",
- "min",
- "max",
"default"
]
}
@@ -7552,7 +7550,7 @@
"tags": [
"databases"
],
- "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
+ "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.\n",
"responses": {
"201": {
"description": "Document",
@@ -10689,7 +10687,7 @@
},
"x-appwrite": {
"method": "getCertificate",
- "weight": 134,
+ "weight": 133,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10800,7 +10798,7 @@
},
"x-appwrite": {
"method": "getPubSub",
- "weight": 130,
+ "weight": 129,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10827,57 +10825,6 @@
]
}
},
- "\/health\/queue": {
- "get": {
- "summary": "Get queue",
- "operationId": "healthGetQueue",
- "consumes": [
- "application\/json"
- ],
- "produces": [
- "application\/json"
- ],
- "tags": [
- "health"
- ],
- "description": "Check the Appwrite queue messaging servers are up and connection is successful.",
- "responses": {
- "200": {
- "description": "Health Status",
- "schema": {
- "$ref": "#\/definitions\/healthStatus"
- }
- }
- },
- "x-appwrite": {
- "method": "getQueue",
- "weight": 129,
- "cookies": false,
- "type": "",
- "deprecated": false,
- "demo": "health\/get-queue.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue.md",
- "rate-limit": 0,
- "rate-time": 3600,
- "rate-key": "url:{url},ip:{ip}",
- "scope": "health.read",
- "platforms": [
- "server"
- ],
- "packaging": false,
- "auth": {
- "Project": [],
- "Key": []
- }
- },
- "security": [
- {
- "Project": [],
- "Key": []
- }
- ]
- }
- },
"\/health\/queue\/builds": {
"get": {
"summary": "Get builds queue",
@@ -10902,7 +10849,7 @@
},
"x-appwrite": {
"method": "getQueueBuilds",
- "weight": 136,
+ "weight": 135,
"cookies": false,
"type": "",
"deprecated": false,
@@ -10964,7 +10911,7 @@
},
"x-appwrite": {
"method": "getQueueCertificates",
- "weight": 135,
+ "weight": 134,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11026,7 +10973,7 @@
},
"x-appwrite": {
"method": "getQueueDatabases",
- "weight": 137,
+ "weight": 136,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11097,7 +11044,7 @@
},
"x-appwrite": {
"method": "getQueueDeletes",
- "weight": 138,
+ "weight": 137,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11197,8 +11144,9 @@
"v1-audits",
"v1-mails",
"v1-functions",
- "v1-usage",
- "v1-usage-dump",
+ "v1-stats-resources",
+ "v1-stats-usage",
+ "v1-stats-usage-dump",
"v1-webhooks",
"v1-certificates",
"v1-builds",
@@ -11245,7 +11193,7 @@
},
"x-appwrite": {
"method": "getQueueFunctions",
- "weight": 142,
+ "weight": 141,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11307,7 +11255,7 @@
},
"x-appwrite": {
"method": "getQueueLogs",
- "weight": 133,
+ "weight": 132,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11369,7 +11317,7 @@
},
"x-appwrite": {
"method": "getQueueMails",
- "weight": 139,
+ "weight": 138,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11431,7 +11379,7 @@
},
"x-appwrite": {
"method": "getQueueMessaging",
- "weight": 140,
+ "weight": 139,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11493,7 +11441,7 @@
},
"x-appwrite": {
"method": "getQueueMigrations",
- "weight": 141,
+ "weight": 140,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11531,10 +11479,10 @@
]
}
},
- "\/health\/queue\/usage": {
+ "\/health\/queue\/stats-resources": {
"get": {
- "summary": "Get usage queue",
- "operationId": "healthGetQueueUsage",
+ "summary": "Get stats resources queue",
+ "operationId": "healthGetQueueStatsResources",
"consumes": [
"application\/json"
],
@@ -11544,7 +11492,7 @@
"tags": [
"health"
],
- "description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
+ "description": "Get the number of metrics that are waiting to be processed in the Appwrite stats resources queue.",
"responses": {
"200": {
"description": "Health Queue",
@@ -11554,13 +11502,13 @@
}
},
"x-appwrite": {
- "method": "getQueueUsage",
- "weight": 143,
+ "method": "getQueueStatsResources",
+ "weight": 142,
"cookies": false,
"type": "",
"deprecated": false,
- "demo": "health\/get-queue-usage.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage.md",
+ "demo": "health\/get-queue-stats-resources.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-resources.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -11593,10 +11541,72 @@
]
}
},
- "\/health\/queue\/usage-dump": {
+ "\/health\/queue\/stats-usage": {
+ "get": {
+ "summary": "Get stats usage queue",
+ "operationId": "healthGetQueueUsage",
+ "consumes": [
+ "application\/json"
+ ],
+ "produces": [
+ "application\/json"
+ ],
+ "tags": [
+ "health"
+ ],
+ "description": "Get the number of metrics that are waiting to be processed in the Appwrite internal queue server.",
+ "responses": {
+ "200": {
+ "description": "Health Queue",
+ "schema": {
+ "$ref": "#\/definitions\/healthQueue"
+ }
+ }
+ },
+ "x-appwrite": {
+ "method": "getQueueUsage",
+ "weight": 143,
+ "cookies": false,
+ "type": "",
+ "deprecated": false,
+ "demo": "health\/get-queue-usage.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage.md",
+ "rate-limit": 0,
+ "rate-time": 3600,
+ "rate-key": "url:{url},ip:{ip}",
+ "scope": "health.read",
+ "platforms": [
+ "server"
+ ],
+ "packaging": false,
+ "auth": {
+ "Project": [],
+ "Key": []
+ }
+ },
+ "security": [
+ {
+ "Project": [],
+ "Key": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "threshold",
+ "description": "Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.",
+ "required": false,
+ "type": "integer",
+ "format": "int32",
+ "default": 5000,
+ "in": "query"
+ }
+ ]
+ }
+ },
+ "\/health\/queue\/stats-usage-dump": {
"get": {
"summary": "Get usage dump queue",
- "operationId": "healthGetQueueUsageDump",
+ "operationId": "healthGetQueueStatsUsageDump",
"consumes": [
"application\/json"
],
@@ -11616,13 +11626,13 @@
}
},
"x-appwrite": {
- "method": "getQueueUsageDump",
+ "method": "getQueueStatsUsageDump",
"weight": 144,
"cookies": false,
"type": "",
"deprecated": false,
- "demo": "health\/get-queue-usage-dump.md",
- "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage-dump.md",
+ "demo": "health\/get-queue-stats-usage-dump.md",
+ "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-stats-usage-dump.md",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
@@ -11679,7 +11689,7 @@
},
"x-appwrite": {
"method": "getQueueWebhooks",
- "weight": 132,
+ "weight": 131,
"cookies": false,
"type": "",
"deprecated": false,
@@ -11843,7 +11853,7 @@
},
"x-appwrite": {
"method": "getTime",
- "weight": 131,
+ "weight": 130,
"cookies": false,
"type": "",
"deprecated": false,
@@ -18555,7 +18565,7 @@
},
{
"name": "queries",
- "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm",
+ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
"required": false,
"type": "array",
"collectionFormat": "multi",
@@ -20659,6 +20669,27 @@
"type": "string",
"x-example": "",
"in": "path"
+ },
+ {
+ "name": "queries",
+ "description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
+ "required": false,
+ "type": "array",
+ "collectionFormat": "multi",
+ "items": {
+ "type": "string"
+ },
+ "default": [],
+ "in": "query"
+ },
+ {
+ "name": "search",
+ "description": "Search term to filter your list results. Max length: 256 chars.",
+ "required": false,
+ "type": "string",
+ "x-example": "",
+ "default": "",
+ "in": "query"
}
]
}
diff --git a/app/config/variables.php b/app/config/variables.php
index 98dd9ffec1..27463d2fee 100644
--- a/app/config/variables.php
+++ b/app/config/variables.php
@@ -1048,13 +1048,22 @@ return [
],
[
'name' => '_APP_MAINTENANCE_RETENTION_AUDIT',
- 'description' => 'IThe maximum duration (in seconds) upto which to retain audit logs. The default value is 1209600 seconds (14 days).',
+ 'description' => 'The maximum duration (in seconds) upto which to retain audit logs. The default value is 1209600 seconds (14 days).',
'introduction' => '0.7.0',
'default' => '1209600',
'required' => false,
'question' => '',
'filter' => ''
],
+ [
+ 'name' => '_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE',
+ 'description' => 'The maximum duration (in seconds) upto which to retain console audit logs. The default value is 15778800 seconds (6 months).',
+ 'introduction' => '1.6.2',
+ 'default' => '15778800',
+ 'required' => false,
+ 'question' => '',
+ 'filter' => ''
+ ],
[
'name' => '_APP_MAINTENANCE_RETENTION_ABUSE',
'description' => 'The maximum duration (in seconds) upto which to retain abuse logs. The default value is 86400 seconds (1 day).',
diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php
index 21a04800b5..20f64496ac 100644
--- a/app/controllers/api/account.php
+++ b/app/controllers/api/account.php
@@ -2400,7 +2400,7 @@ App::put('/v1/account/sessions/phone')
App::post('/v1/account/tokens/phone')
->alias('/v1/account/sessions/phone')
->desc('Create phone token')
- ->groups(['api', 'account'])
+ ->groups(['api', 'account', 'auth'])
->label('scope', 'sessions.write')
->label('auth.type', 'phone')
->label('audits.event', 'session.create')
@@ -2715,13 +2715,15 @@ App::get('/v1/account/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
- $grouped = Query::groupByType($queries);
- $limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
- $offset = $grouped['offset'] ?? 0;
+ // Temp fix for logs
+ $queries[] = Query::or([
+ Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
+ Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
+ ]);
$audit = new EventAudit($dbForProject);
- $logs = $audit->getLogsByUser($user->getInternalId(), $limit, $offset);
+ $logs = $audit->getLogsByUser($user->getInternalId(), $queries);
$output = [];
@@ -2750,7 +2752,7 @@ App::get('/v1/account/logs')
}
$response->dynamic(new Document([
- 'total' => $audit->countLogsByUser($user->getInternalId()),
+ 'total' => $audit->countLogsByUser($user->getInternalId(), $queries),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php
index c563d1e8e0..0c37e1a765 100644
--- a/app/controllers/api/databases.php
+++ b/app/controllers/api/databases.php
@@ -23,6 +23,7 @@ use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Config\Config;
use Utopia\Database\Database;
+use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Authorization as AuthorizationException;
use Utopia\Database\Exception\Conflict as ConflictException;
@@ -242,8 +243,8 @@ function updateAttribute(
string $filter = null,
string|bool|int|float $default = null,
bool $required = null,
- int|float $min = null,
- int|float $max = null,
+ int|float|null $min = null,
+ int|float|null $max = null,
array $elements = null,
array $options = [],
string $newKey = null,
@@ -299,6 +300,9 @@ function updateAttribute(
switch ($attribute->getAttribute('format')) {
case APP_DATABASE_ATTRIBUTE_INT_RANGE:
case APP_DATABASE_ATTRIBUTE_FLOAT_RANGE:
+ $min ??= $attribute->getAttribute('formatOptions')['min'];
+ $max ??= $attribute->getAttribute('formatOptions')['max'];
+
if ($min > $max) {
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Minimum value must be lesser than maximum value');
}
@@ -669,13 +673,15 @@ App::get('/v1/databases/:databaseId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
- $grouped = Query::groupByType($queries);
- $limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
- $offset = $grouped['offset'] ?? 0;
+ // Temp fix for logs
+ $queries[] = Query::or([
+ Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
+ Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
+ ]);
$audit = new Audit($dbForProject);
$resource = 'database/' . $databaseId;
- $logs = $audit->getLogsByResource($resource, $limit, $offset);
+ $logs = $audit->getLogsByResource($resource, $queries);
$output = [];
@@ -723,7 +729,7 @@ App::get('/v1/databases/:databaseId/logs')
}
$response->dynamic(new Document([
- 'total' => $audit->countLogsByResource($resource),
+ 'total' => $audit->countLogsByResource($resource, $queries),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -1057,14 +1063,21 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs')
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
- $queries = Query::parseQueries($queries);
- $grouped = Query::groupByType($queries);
- $limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
- $offset = $grouped['offset'] ?? 0;
+ try {
+ $queries = Query::parseQueries($queries);
+ } catch (QueryException $e) {
+ throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
+ }
+
+ // Temp fix for logs
+ $queries[] = Query::or([
+ Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
+ Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
+ ]);
$audit = new Audit($dbForProject);
$resource = 'database/' . $databaseId . '/collection/' . $collectionId;
- $logs = $audit->getLogsByResource($resource, $limit, $offset);
+ $logs = $audit->getLogsByResource($resource, $queries);
$output = [];
@@ -1112,7 +1125,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs')
}
$response->dynamic(new Document([
- 'total' => $audit->countLogsByResource($resource),
+ 'total' => $audit->countLogsByResource($resource, $queries),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -1550,8 +1563,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 : $min;
- $max = \is_null($max) ? PHP_INT_MAX : $max;
+ $min ??= PHP_INT_MIN;
+ $max ??= PHP_INT_MAX;
if ($min > $max) {
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Minimum value must be lesser than maximum value');
@@ -1627,8 +1640,8 @@ 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 : $min;
- $max = \is_null($max) ? PHP_FLOAT_MAX : $max;
+ $min ??= -PHP_FLOAT_MAX;
+ $max ??= PHP_FLOAT_MAX;
if ($min > $max) {
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Minimum value must be lesser than maximum value');
@@ -2339,8 +2352,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/integ
->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('min', null, new Integer(), 'Minimum value to enforce on new documents')
- ->param('max', null, new Integer(), 'Maximum value to enforce on new documents')
+ ->param('min', null, new Integer(), 'Minimum value to enforce on new documents', true)
+ ->param('max', null, new Integer(), 'Maximum value to enforce on new documents', true)
->param('default', null, new Nullable(new Integer()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
->param('newKey', null, new Key(), 'New attribute key.', true)
->inject('response')
@@ -2398,8 +2411,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/float
->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('min', null, new FloatValidator(), 'Minimum value to enforce on new documents')
- ->param('max', null, new FloatValidator(), 'Maximum value to enforce on new documents')
+ ->param('min', null, new FloatValidator(), 'Minimum value to enforce on new documents', true)
+ ->param('max', null, new FloatValidator(), 'Maximum value to enforce on new documents', true)
->param('default', null, new Nullable(new FloatValidator()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
->param('newKey', null, new Key(), 'New attribute key.', true)
->inject('response')
@@ -3342,7 +3355,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
$processDocument($collection, $document);
$queueForStatsUsage
- ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, $operations)
+ ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, max($operations, 1))
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_WRITES), $operations)
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection
@@ -3507,7 +3520,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
}
$queueForStatsUsage
- ->addMetric(METRIC_DATABASES_OPERATIONS_READS, $operations)
+ ->addMetric(METRIC_DATABASES_OPERATIONS_READS, max($operations, 1))
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_READS), $operations);
$response->addHeader('X-Debug-Operations', $operations);
@@ -3648,7 +3661,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
$processDocument($collection, $document);
$queueForStatsUsage
- ->addMetric(METRIC_DATABASES_OPERATIONS_READS, $operations)
+ ->addMetric(METRIC_DATABASES_OPERATIONS_READS, max($operations, 1))
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_READS), $operations);
$response->addHeader('X-Debug-Operations', $operations);
@@ -3709,13 +3722,15 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
- $grouped = Query::groupByType($queries);
- $limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
- $offset = $grouped['offset'] ?? 0;
+ // Temp fix for logs
+ $queries[] = Query::or([
+ Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
+ Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
+ ]);
$audit = new Audit($dbForProject);
$resource = 'database/' . $databaseId . '/collection/' . $collectionId . '/document/' . $document->getId();
- $logs = $audit->getLogsByResource($resource, $limit, $offset);
+ $logs = $audit->getLogsByResource($resource, $queries);
$output = [];
@@ -3763,7 +3778,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
}
$response->dynamic(new Document([
- 'total' => $audit->countLogsByResource($resource),
+ 'total' => $audit->countLogsByResource($resource, $queries),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -3944,7 +3959,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
$setCollection($collection, $newDocument);
$queueForStatsUsage
- ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, $operations)
+ ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, max($operations, 1))
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_WRITES), $operations);
$response->addHeader('X-Debug-Operations', $operations);
diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php
index 1adefe20ae..36517b5bad 100644
--- a/app/controllers/api/functions.php
+++ b/app/controllers/api/functions.php
@@ -6,13 +6,14 @@ use Appwrite\Event\Build;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
+use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage;
use Appwrite\Event\Validator\FunctionEvent;
+use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Functions\Validator\Headers;
use Appwrite\Functions\Validator\RuntimeSpecification;
-use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Platform\Tasks\ScheduleExecutions;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
@@ -194,9 +195,12 @@ App::post('/v1/functions')
->inject('user')
->inject('queueForEvents')
->inject('queueForBuilds')
+ ->inject('queueForWebhooks')
+ ->inject('queueForFunctions')
+ ->inject('queueForRealtime')
->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, callable $timelimit, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForPlatform, 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, callable $timelimit, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Database $dbForPlatform, GitHub $github) use ($redeployVcs) {
$functionId = ($functionId == 'unique()') ? ID::unique() : $functionId;
// Temporary abuse check
@@ -291,7 +295,7 @@ App::post('/v1/functions')
$schedule = Authorization::skip(
fn () => $dbForPlatform->createDocument('schedules', new Document([
- 'region' => System::getEnv('_APP_REGION', 'default'), // Todo replace with projects region
+ 'region' => $project->getAttribute('region'),
'resourceType' => 'function',
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getInternalId(),
@@ -383,54 +387,34 @@ App::post('/v1/functions')
'resourceInternalId' => $function->getInternalId(),
'status' => 'verified',
'certificateId' => '',
+ 'owner' => 'Appwrite',
+ 'region' => $project->getAttribute('region')
]))
);
- /** Trigger Webhook */
$ruleModel = new Rule();
$ruleCreate =
$queueForEvents
- ->setClass(Event::WEBHOOK_CLASS_NAME)
- ->setQueue(Event::WEBHOOK_QUEUE_NAME);
+ ->setProject($project)
+ ->setEvent('rules.[ruleId].create')
+ ->setParam('ruleId', $rule->getId())
+ ->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())));
- $ruleCreate
- ->setProject($project)
- ->setEvent('rules.[ruleId].create')
- ->setParam('ruleId', $rule->getId())
- ->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())))
+ /** Trigger Webhook */
+ $queueForWebhooks
+ ->from($ruleCreate)
->trigger();
/** Trigger Functions */
- $ruleCreate
- ->setClass(Event::FUNCTIONS_CLASS_NAME)
- ->setQueue(Event::FUNCTIONS_QUEUE_NAME)
+ $queueForFunctions
+ ->from($ruleCreate)
->trigger();
- /** Trigger realtime event */
- $allEvents = Event::generateEvents('rules.[ruleId].create', [
- 'ruleId' => $rule->getId(),
- ]);
-
- $target = Realtime::fromPayload(
- // Pass first, most verbose event pattern
- event: $allEvents[0],
- payload: $rule,
- project: $project
- );
- Realtime::send(
- projectId: 'console',
- payload: $rule->getArrayCopy(),
- events: $allEvents,
- channels: $target['channels'],
- roles: $target['roles']
- );
- Realtime::send(
- projectId: $project->getId(),
- payload: $rule->getArrayCopy(),
- events: $allEvents,
- channels: $target['channels'],
- roles: $target['roles']
- );
+ /** Trigger Realtime Events */
+ $queueForRealtime
+ ->from($ruleCreate)
+ ->setSubscribers(['console', $project->getId()])
+ ->trigger();
}
$queueForEvents->setParam('functionId', $function->getId());
@@ -885,7 +869,8 @@ App::put('/v1/functions/:functionId')
->inject('queueForBuilds')
->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 $dbForPlatform, GitHub $github) use ($redeployVcs) {
+ ->inject('executor')
+ ->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, Executor $executor) use ($redeployVcs) {
// TODO: If only branch changes, re-deploy
$function = $dbForProject->getDocument('functions', $functionId);
@@ -988,7 +973,6 @@ App::put('/v1/functions/:functionId')
// Enforce Cold Start if spec limits change.
if ($function->getAttribute('specification') !== $specification && !empty($function->getAttribute('deployment'))) {
- $executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
try {
$executor->deleteRuntime($project->getId(), $function->getAttribute('deployment'));
} catch (\Throwable $th) {
@@ -1795,7 +1779,8 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId/build')
->inject('dbForProject')
->inject('project')
->inject('queueForEvents')
- ->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Document $project, Event $queueForEvents) {
+ ->inject('executor')
+ ->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Executor $executor) {
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
@@ -1850,7 +1835,6 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId/build')
$dbForProject->purgeCachedDocument('deployments', $deployment->getId());
try {
- $executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
$executor->deleteRuntime($project->getId(), $deploymentId . "-build");
} catch (\Throwable $th) {
// Don't throw if the deployment doesn't exist
@@ -1902,8 +1886,9 @@ App::post('/v1/functions/:functionId/executions')
->inject('queueForEvents')
->inject('queueForStatsUsage')
->inject('queueForFunctions')
+ ->inject('executor')
->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 $dbForPlatform, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, 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, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb) {
$async = \strval($async) === 'true' || \strval($async) === '1';
if (!$async && !is_null($scheduledAt)) {
@@ -2095,7 +2080,7 @@ App::post('/v1/functions/:functionId/executions')
];
$schedule = $dbForPlatform->createDocument('schedules', new Document([
- 'region' => System::getEnv('_APP_REGION', 'default'),
+ 'region' => $project->getAttribute('region'),
'resourceType' => ScheduleExecutions::getSupportedResource(),
'resourceId' => $execution->getId(),
'resourceInternalId' => $execution->getInternalId(),
@@ -2176,7 +2161,6 @@ App::post('/v1/functions/:functionId/executions')
]);
/** Execute function */
- $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST'));
try {
$version = $function->getAttribute('version', 'v2');
$command = $runtime['startCommand'];
diff --git a/app/controllers/api/locale.php b/app/controllers/api/locale.php
index 5b4c1ac47f..523ac3976b 100644
--- a/app/controllers/api/locale.php
+++ b/app/controllers/api/locale.php
@@ -118,7 +118,7 @@ App::get('/v1/locale/countries')
->inject('response')
->inject('locale')
->action(function (Response $response, Locale $locale) {
- $list = Config::getParam('locale-countries'); /* @var $list array */
+ $list = array_keys(Config::getParam('locale-countries')); /* @var $list array */
$output = [];
foreach ($list as $value) {
@@ -229,7 +229,7 @@ App::get('/v1/locale/continents')
->inject('response')
->inject('locale')
->action(function (Response $response, Locale $locale) {
- $list = Config::getParam('locale-continents');
+ $list = array_keys(Config::getParam('locale-continents'));
foreach ($list as $value) {
$output[] = new Document([
diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php
index d7d3750ccf..178266db60 100644
--- a/app/controllers/api/messaging.php
+++ b/app/controllers/api/messaging.php
@@ -994,13 +994,15 @@ App::get('/v1/messaging/providers/:providerId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
- $grouped = Query::groupByType($queries);
- $limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
- $offset = $grouped['offset'] ?? 0;
+ // Temp fix for logs
+ $queries[] = Query::or([
+ Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
+ Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
+ ]);
$audit = new Audit($dbForProject);
$resource = 'provider/' . $providerId;
- $logs = $audit->getLogsByResource($resource, $limit, $offset);
+ $logs = $audit->getLogsByResource($resource, $queries);
$output = [];
foreach ($logs as $i => &$log) {
@@ -1047,7 +1049,7 @@ App::get('/v1/messaging/providers/:providerId/logs')
}
$response->dynamic(new Document([
- 'total' => $audit->countLogsByResource($resource),
+ 'total' => $audit->countLogsByResource($resource, $queries),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -2222,13 +2224,15 @@ App::get('/v1/messaging/topics/:topicId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
- $grouped = Query::groupByType($queries);
- $limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
- $offset = $grouped['offset'] ?? 0;
+ // Temp fix for logs
+ $queries[] = Query::or([
+ Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
+ Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
+ ]);
$audit = new Audit($dbForProject);
$resource = 'topic/' . $topicId;
- $logs = $audit->getLogsByResource($resource, $limit, $offset);
+ $logs = $audit->getLogsByResource($resource, $queries);
$output = [];
@@ -2276,7 +2280,7 @@ App::get('/v1/messaging/topics/:topicId/logs')
}
$response->dynamic(new Document([
- 'total' => $audit->countLogsByResource($resource),
+ 'total' => $audit->countLogsByResource($resource, $queries),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -2632,13 +2636,15 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
- $grouped = Query::groupByType($queries);
- $limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
- $offset = $grouped['offset'] ?? 0;
+ // Temp fix for logs
+ $queries[] = Query::or([
+ Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
+ Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
+ ]);
$audit = new Audit($dbForProject);
$resource = 'subscriber/' . $subscriberId;
- $logs = $audit->getLogsByResource($resource, $limit, $offset);
+ $logs = $audit->getLogsByResource($resource, $queries);
$output = [];
@@ -2686,7 +2692,7 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
}
$response->dynamic(new Document([
- 'total' => $audit->countLogsByResource($resource),
+ 'total' => $audit->countLogsByResource($resource, $queries),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -2930,7 +2936,7 @@ App::post('/v1/messaging/messages/email')
break;
case MessageStatus::SCHEDULED:
$schedule = $dbForPlatform->createDocument('schedules', new Document([
- 'region' => System::getEnv('_APP_REGION', 'default'),
+ 'region' => $project->getAttribute('region'),
'resourceType' => 'message',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getInternalId(),
@@ -3052,7 +3058,7 @@ App::post('/v1/messaging/messages/sms')
break;
case MessageStatus::SCHEDULED:
$schedule = $dbForPlatform->createDocument('schedules', new Document([
- 'region' => System::getEnv('_APP_REGION', 'default'),
+ 'region' => $project->getAttribute('region'),
'resourceType' => 'message',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getInternalId(),
@@ -3269,7 +3275,7 @@ App::post('/v1/messaging/messages/push')
break;
case MessageStatus::SCHEDULED:
$schedule = $dbForPlatform->createDocument('schedules', new Document([
- 'region' => System::getEnv('_APP_REGION', 'default'),
+ 'region' => $project->getAttribute('region'),
'resourceType' => 'message',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getInternalId(),
@@ -3397,13 +3403,15 @@ App::get('/v1/messaging/messages/:messageId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
- $grouped = Query::groupByType($queries);
- $limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
- $offset = $grouped['offset'] ?? 0;
+ // Temp fix for logs
+ $queries[] = Query::or([
+ Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
+ Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
+ ]);
$audit = new Audit($dbForProject);
$resource = 'message/' . $messageId;
- $logs = $audit->getLogsByResource($resource, $limit, $offset);
+ $logs = $audit->getLogsByResource($resource, $queries);
$output = [];
@@ -3451,7 +3459,7 @@ App::get('/v1/messaging/messages/:messageId/logs')
}
$response->dynamic(new Document([
- 'total' => $audit->countLogsByResource($resource),
+ 'total' => $audit->countLogsByResource($resource, $queries),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -3653,7 +3661,7 @@ App::patch('/v1/messaging/messages/email/:messageId')
if (\is_null($currentScheduledAt) && !\is_null($scheduledAt)) {
$schedule = $dbForPlatform->createDocument('schedules', new Document([
- 'region' => System::getEnv('_APP_REGION', 'default'),
+ 'region' => $project->getAttribute('region'),
'resourceType' => 'message',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getInternalId(),
@@ -3854,7 +3862,7 @@ App::patch('/v1/messaging/messages/sms/:messageId')
if (\is_null($currentScheduledAt) && !\is_null($scheduledAt)) {
$schedule = $dbForPlatform->createDocument('schedules', new Document([
- 'region' => System::getEnv('_APP_REGION', 'default'),
+ 'region' => $project->getAttribute('region'),
'resourceType' => 'message',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getInternalId(),
@@ -4027,7 +4035,7 @@ App::patch('/v1/messaging/messages/push/:messageId')
if (\is_null($currentScheduledAt) && !\is_null($scheduledAt)) {
$schedule = $dbForPlatform->createDocument('schedules', new Document([
- 'region' => System::getEnv('_APP_REGION', 'default'),
+ 'region' => $project->getAttribute('region'),
'resourceType' => 'message',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getInternalId(),
diff --git a/app/controllers/api/migrations.php b/app/controllers/api/migrations.php
index 75afc7ed2c..4a1e5de227 100644
--- a/app/controllers/api/migrations.php
+++ b/app/controllers/api/migrations.php
@@ -1,5 +1,6 @@
dynamic($migration, Response::MODEL_MIGRATION);
});
-
App::post('/v1/migrations/firebase')
->groups(['api', 'migrations'])
->desc('Migrate Firebase data')
@@ -290,6 +297,98 @@ App::post('/v1/migrations/nhost')
->dynamic($migration, Response::MODEL_MIGRATION);
});
+App::post('/v1/migrations/csv')
+ ->groups(['api', 'migrations'])
+ ->desc('Import documents from a CSV')
+ ->label('scope', 'migrations.write')
+ ->label('event', 'migrations.[migrationId].create')
+ ->label('audits.event', 'migration.create')
+ ->label('sdk', new Method(
+ namespace: 'migrations',
+ name: 'createCsvMigration',
+ description: '/docs/references/migrations/migration-csv.md',
+ auth: [AuthType::ADMIN],
+ responses: [
+ new SDKResponse(
+ code: Response::STATUS_CODE_ACCEPTED,
+ model: Response::MODEL_MIGRATION,
+ )
+ ]
+ ))
+ ->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
+ ->param('fileId', '', new UID(), 'File ID.')
+ ->param('resourceId', null, new CompoundUID(), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database.')
+ ->inject('response')
+ ->inject('dbForProject')
+ ->inject('project')
+ ->inject('deviceForFiles')
+ ->inject('deviceForImports')
+ ->inject('queueForEvents')
+ ->inject('queueForMigrations')
+ ->action(function (string $bucketId, string $fileId, string $resourceId, Response $response, Database $dbForProject, Document $project, Device $deviceForFiles, Device $deviceForImports, Event $queueForEvents, Migration $queueForMigrations) {
+ $isAPIKey = Auth::isAppUser(Authorization::getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
+
+ $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
+
+ if ($bucket->isEmpty() || (!$isAPIKey && !$isPrivilegedUser)) {
+ throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
+ }
+
+ $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
+ if ($file->isEmpty()) {
+ throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
+ }
+
+ $path = $file->getAttribute('path', '');
+ if (!$deviceForFiles->exists($path)) {
+ throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path);
+ }
+
+ if (!empty($file->getAttribute('openSSLCipher')) || $file->getAttribute('algorithm', Compression::NONE) !== Compression::NONE) {
+ throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED, "Only uncompressed, unencrypted CSV files can be used for document import.");
+ }
+
+ // copy to temporary folder
+ $migrationId = ID::unique();
+ $newPath = $deviceForImports->getPath('/' . $migrationId . '_' . $fileId . '.csv');
+ if (!$deviceForFiles->transfer($path, $newPath, $deviceForImports)) {
+ throw new \Exception("Unable to copy file");
+ }
+
+ $fileSize = $deviceForImports->getFileSize($path);
+ $resources = Transfer::extractServices([Transfer::GROUP_DATABASES]);
+
+ $migration = $dbForProject->createDocument('migrations', new Document([
+ '$id' => $migrationId,
+ 'status' => 'pending',
+ 'stage' => 'init',
+ 'source' => CSV::getName(),
+ 'destination' => Appwrite::getName(),
+ 'resources' => $resources,
+ 'resourceId' => $resourceId,
+ 'resourceType' => Resource::TYPE_DATABASE,
+ 'statusCounters' => [],
+ 'resourceData' => [],
+ 'errors' => [],
+ 'options' => [
+ 'path' => $newPath,
+ 'size' => $fileSize,
+ ],
+ ]));
+
+ $queueForEvents->setParam('migrationId', $migration->getId());
+
+ $queueForMigrations
+ ->setMigration($migration)
+ ->setProject($project)
+ ->trigger();
+
+ $response
+ ->setStatusCode(Response::STATUS_CODE_ACCEPTED)
+ ->dynamic($migration, Response::MODEL_MIGRATION);
+ });
+
App::get('/v1/migrations')
->groups(['api', 'migrations'])
->desc('List migrations')
diff --git a/app/controllers/api/project.php b/app/controllers/api/project.php
index 0544760e73..a3c1933d8c 100644
--- a/app/controllers/api/project.php
+++ b/app/controllers/api/project.php
@@ -41,14 +41,18 @@ App::get('/v1/project/usage')
->param('endDate', '', new DateTimeValidator(), 'End date for the usage')
->param('period', '1d', new WhiteList(['1h', '1d']), 'Period used', true)
->inject('response')
+ ->inject('project')
->inject('dbForProject')
+ ->inject('getLogsDB')
->inject('smsRates')
- ->action(function (string $startDate, string $endDate, string $period, Response $response, Database $dbForProject, array $smsRates) {
+ ->action(function (string $startDate, string $endDate, string $period, Response $response, Document $project, Database $dbForProject, callable $getLogsDB, array $smsRates) {
$stats = $total = $usage = [];
$format = 'Y-m-d 00:00:00';
$firstDay = (new DateTime($startDate))->format($format);
$lastDay = (new DateTime($endDate))->format($format);
+ $dbForLogs = call_user_func($getLogsDB, $project);
+
$metrics = [
'total' => [
METRIC_EXECUTIONS,
@@ -64,6 +68,7 @@ App::get('/v1/project/usage')
METRIC_BUILDS_STORAGE,
METRIC_DATABASES_OPERATIONS_READS,
METRIC_DATABASES_OPERATIONS_WRITES,
+ METRIC_FILES_IMAGES_TRANSFORMED,
],
'period' => [
METRIC_NETWORK_REQUESTS,
@@ -76,6 +81,7 @@ App::get('/v1/project/usage')
METRIC_BUILDS_MB_SECONDS,
METRIC_DATABASES_OPERATIONS_READS,
METRIC_DATABASES_OPERATIONS_WRITES,
+ METRIC_FILES_IMAGES_TRANSFORMED,
]
];
@@ -94,9 +100,11 @@ App::get('/v1/project/usage')
'1d' => 'Y-m-d\T00:00:00.000P',
};
- Authorization::skip(function () use ($dbForProject, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
+ Authorization::skip(function () use ($dbForProject, $dbForLogs, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
foreach ($metrics['total'] as $metric) {
- $result = $dbForProject->findOne('stats', [
+ $db = ($metric === METRIC_FILES_IMAGES_TRANSFORMED) ? $dbForLogs : $dbForProject;
+
+ $result = $db->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
@@ -104,7 +112,9 @@ App::get('/v1/project/usage')
}
foreach ($metrics['period'] as $metric) {
- $results = $dbForProject->find('stats', [
+ $db = ($metric === METRIC_FILES_IMAGES_TRANSFORMED) ? $dbForLogs : $dbForProject;
+
+ $results = $db->find('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', [$period]),
Query::greaterThanEqual('time', $firstDay),
@@ -364,6 +374,8 @@ App::get('/v1/project/usage')
'authPhoneTotal' => $authPhoneTotal,
'authPhoneEstimate' => $authPhoneEstimate,
'authPhoneCountryBreakdown' => $authPhoneCountryBreakdown,
+ 'imageTransformations' => $usage[METRIC_FILES_IMAGES_TRANSFORMED],
+ 'imageTransformationsTotal' => $total[METRIC_FILES_IMAGES_TRANSFORMED],
]), Response::MODEL_USAGE_PROJECT);
});
diff --git a/app/controllers/api/proxy.php b/app/controllers/api/proxy.php
index 2567c23be6..393caad93d 100644
--- a/app/controllers/api/proxy.php
+++ b/app/controllers/api/proxy.php
@@ -130,6 +130,8 @@ App::post('/v1/proxy/rules')
'resourceId' => $resourceId,
'resourceInternalId' => $resourceInternalId,
'certificateId' => '',
+ 'owner' => '',
+ 'region' => $project->getAttribute('region')
]);
$status = 'created';
diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php
index 4d324ad2ca..541a0b42d3 100644
--- a/app/controllers/api/storage.php
+++ b/app/controllers/api/storage.php
@@ -6,7 +6,6 @@ use Appwrite\Auth\Auth;
use Appwrite\ClamAV\Network;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
-use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\SDK\AuthType;
@@ -935,16 +934,12 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->param('rotation', 0, new Range(-360, 360), 'Preview image rotation in degrees. Pass an integer between -360 and 360.', true)
->param('background', '', new HexColor(), 'Preview image background color. Only works with transparent images (png). Use a valid HEX color, no # is needed for prefix.', true)
->param('output', '', new WhiteList(\array_keys(Config::getParam('storage-outputs')), true), 'Output format type (jpeg, jpg, png, gif and webp).', true)
- ->inject('request')
->inject('response')
- ->inject('project')
->inject('dbForProject')
->inject('resourceToken')
- ->inject('mode')
->inject('deviceForFiles')
->inject('deviceForLocal')
- ->inject('queueForStatsUsage')
- ->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, Document $resourceToken, string $mode, Device $deviceForFiles, Device $deviceForLocal, StatsUsage $queueForStatsUsage) {
+ ->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Response $response, Database $dbForProject, Document $resourceToken, Device $deviceForFiles, Device $deviceForLocal) {
if (!\extension_loaded('imagick')) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
@@ -1077,22 +1072,19 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
$contentType = (\array_key_exists($output, $outputs)) ? $outputs[$output] : $outputs['jpg'];
- $queueForStatsUsage
- ->addMetric(METRIC_FILES_TRANSFORMATIONS, 1)
- ->addMetric(str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_TRANSFORMATIONS), 1)
- ;
-
- $transformedAt = $file->getAttribute('transformedAt', '');
- if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
- $file->setAttribute('transformedAt', DateTime::now());
- Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
+ //Do not update transformedAt if it's a console user
+ if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
+ $transformedAt = $file->getAttribute('transformedAt', '');
+ if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
+ $file->setAttribute('transformedAt', DateTime::now());
+ Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
+ }
}
$response
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
->setContentType($contentType)
- ->file($data)
- ;
+ ->file($data);
unset($image);
});
@@ -1886,9 +1878,12 @@ App::get('/v1/storage/:bucketId/usage')
->param('bucketId', '', new UID(), 'Bucket ID.')
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->inject('response')
+ ->inject('project')
->inject('dbForProject')
- ->action(function (string $bucketId, string $range, Response $response, Database $dbForProject) {
+ ->inject('getLogsDB')
+ ->action(function (string $bucketId, string $range, Response $response, Document $project, Database $dbForProject, callable $getLogsDB) {
+ $dbForLogs = call_user_func($getLogsDB, $project);
$bucket = $dbForProject->getDocument('buckets', $bucketId);
if ($bucket->isEmpty()) {
@@ -1901,12 +1896,16 @@ App::get('/v1/storage/:bucketId/usage')
$metrics = [
str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES),
str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_STORAGE),
+ str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_IMAGES_TRANSFORMED),
];
-
- Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) {
+ Authorization::skip(function () use ($dbForProject, $dbForLogs, $bucket, $days, $metrics, &$stats) {
foreach ($metrics as $metric) {
- $result = $dbForProject->findOne('stats', [
+ $db = ($metric === str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_IMAGES_TRANSFORMED))
+ ? $dbForLogs
+ : $dbForProject;
+
+ $result = $db->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
@@ -1914,7 +1913,7 @@ App::get('/v1/storage/:bucketId/usage')
$stats[$metric]['total'] = $result['value'] ?? 0;
$limit = $days['limit'];
$period = $days['period'];
- $results = $dbForProject->find('stats', [
+ $results = $db->find('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', [$period]),
Query::limit($limit),
@@ -1955,5 +1954,7 @@ App::get('/v1/storage/:bucketId/usage')
'filesStorageTotal' => $usage[$metrics[1]]['total'],
'files' => $usage[$metrics[0]]['data'],
'storage' => $usage[$metrics[1]]['data'],
+ 'imageTransformations' => $usage[$metrics[2]]['data'],
+ 'imageTransformationsTotal' => $usage[$metrics[2]]['total'],
]), Response::MODEL_USAGE_BUCKETS);
});
diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php
index 06e653c105..3e0e366b6b 100644
--- a/app/controllers/api/teams.php
+++ b/app/controllers/api/teams.php
@@ -488,7 +488,7 @@ App::post('/v1/teams/:teamId/memberships')
}
$email = \strtolower($email);
- $name = (empty($name)) ? $email : $name;
+ $name = empty($name) ? $email : $name;
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
@@ -507,7 +507,7 @@ App::post('/v1/teams/:teamId/memberships')
}
$email = $invitee->getAttribute('email', '');
$phone = $invitee->getAttribute('phone', '');
- $name = empty($name) ? $invitee->getAttribute('name', '') : $name;
+ $name = $invitee->getAttribute('name', '') ?: $name;
} elseif (!empty($email)) {
$invitee = $dbForProject->findOne('users', [Query::equal('email', [$email])]); // Get user by email address
if (!$invitee->isEmpty() && !empty($phone) && $invitee->getAttribute('phone', '') !== $phone) {
@@ -615,7 +615,10 @@ App::post('/v1/teams/:teamId/memberships')
$membership = ($isPrivilegedUser || $isAppUser) ?
Authorization::skip(fn () => $dbForProject->createDocument('memberships', $membership)) :
$dbForProject->createDocument('memberships', $membership);
- Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
+
+ if ($isPrivilegedUser || $isAppUser) {
+ Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
+ }
} elseif ($membership->getAttribute('confirm') === false) {
$membership->setAttribute('secret', Auth::hash($secret));
@@ -715,7 +718,7 @@ App::post('/v1/teams/:teamId/memberships')
->setSubject($subject)
->setBody($body)
->setRecipient($invitee->getAttribute('email'))
- ->setName($invitee->getAttribute('name'))
+ ->setName($invitee->getAttribute('name', ''))
->setVariables($emailVariables)
->trigger();
@@ -1031,7 +1034,6 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
->param('membershipId', '', new UID(), 'Membership ID.')
->param('roles', [], function (Document $project) {
if ($project->getId() === 'console') {
- ;
$roles = array_keys(Config::getParam('roles', []));
array_filter($roles, function ($role) {
return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]);
@@ -1043,9 +1045,10 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
->inject('request')
->inject('response')
->inject('user')
+ ->inject('project')
->inject('dbForProject')
->inject('queueForEvents')
- ->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
+ ->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents) {
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
@@ -1066,6 +1069,21 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
$isAppUser = Auth::isAppUser(Authorization::getRoles());
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
+ if ($project->getId() === 'console') {
+ // Quick check: fetch up to 2 owners to determine if only one exists
+ $ownersCount = $dbForProject->count(
+ collection: 'memberships',
+ queries: [Query::contains('roles', ['owner'])],
+ max: 2
+ );
+
+ // Prevent role change if there's only one owner left,
+ // the requester is that owner, and the new `$roles` no longer include 'owner'!
+ if ($ownersCount === 1 && $isOwner && !\in_array('owner', $roles)) {
+ throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'There must be at least one owner in the organization.');
+ }
+ }
+
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)
throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to modify roles');
}
@@ -1362,13 +1380,15 @@ App::get('/v1/teams/:teamId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
- $grouped = Query::groupByType($queries);
- $limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
- $offset = $grouped['offset'] ?? 0;
+ // Temp fix for logs
+ $queries[] = Query::or([
+ Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
+ Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
+ ]);
$audit = new Audit($dbForProject);
$resource = 'team/' . $team->getId();
- $logs = $audit->getLogsByResource($resource, $limit, $offset);
+ $logs = $audit->getLogsByResource($resource, $queries);
$output = [];
@@ -1415,7 +1435,7 @@ App::get('/v1/teams/:teamId/logs')
}
}
$response->dynamic(new Document([
- 'total' => $audit->countLogsByResource($resource),
+ 'total' => $audit->countLogsByResource($resource, $queries),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php
index 962022927f..17096731fa 100644
--- a/app/controllers/api/users.php
+++ b/app/controllers/api/users.php
@@ -21,6 +21,7 @@ use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Identities;
+use Appwrite\Utopia\Database\Validator\Queries\Memberships;
use Appwrite\Utopia\Database\Validator\Queries\Targets;
use Appwrite\Utopia\Database\Validator\Queries\Users;
use Appwrite\Utopia\Request;
@@ -799,9 +800,11 @@ App::get('/v1/users/:userId/memberships')
]
))
->param('userId', '', new UID(), 'User ID.')
+ ->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('dbForProject')
- ->action(function (string $userId, Response $response, Database $dbForProject) {
+ ->action(function (string $userId, array $queries, string $search, Response $response, Database $dbForProject) {
$user = $dbForProject->getDocument('users', $userId);
@@ -809,6 +812,19 @@ App::get('/v1/users/:userId/memberships')
throw new Exception(Exception::USER_NOT_FOUND);
}
+ try {
+ $queries = Query::parseQueries($queries);
+ } catch (QueryException $e) {
+ throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
+ }
+
+ if (!empty($search)) {
+ $queries[] = Query::search('search', $search);
+ }
+
+ // Set internal queries
+ $queries[] = Query::equal('userInternalId', [$user->getInternalId()]);
+
$memberships = array_map(function ($membership) use ($dbForProject, $user) {
$team = $dbForProject->getDocument('teams', $membership->getAttribute('teamId'));
@@ -818,7 +834,7 @@ App::get('/v1/users/:userId/memberships')
->setAttribute('userEmail', $user->getAttribute('email'));
return $membership;
- }, $user->getAttribute('memberships', []));
+ }, $dbForProject->find('memberships', $queries));
$response->dynamic(new Document([
'memberships' => $memberships,
@@ -862,13 +878,15 @@ App::get('/v1/users/:userId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
- $grouped = Query::groupByType($queries);
- $limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
- $offset = $grouped['offset'] ?? 0;
+ // Temp fix for logs
+ $queries[] = Query::or([
+ Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
+ Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
+ ]);
$audit = new Audit($dbForProject);
- $logs = $audit->getLogsByUser($user->getInternalId(), $limit, $offset);
+ $logs = $audit->getLogsByUser($user->getInternalId(), $queries);
$output = [];
@@ -915,7 +933,7 @@ App::get('/v1/users/:userId/logs')
}
$response->dynamic(new Document([
- 'total' => $audit->countLogsByUser($user->getInternalId()),
+ 'total' => $audit->countLogsByUser($user->getInternalId(), $queries),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
diff --git a/app/controllers/general.php b/app/controllers/general.php
index 678103e07d..8979f40abb 100644
--- a/app/controllers/general.php
+++ b/app/controllers/general.php
@@ -52,7 +52,7 @@ Config::setParam('domainVerification', false);
Config::setParam('cookieDomain', 'localhost');
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
-function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked, string $previewHostname)
+function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname)
{
$utopia->getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml');
@@ -341,7 +341,6 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
]);
/** Execute function */
- $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST'));
try {
$version = $function->getAttribute('version', 'v2');
$command = $runtime['startCommand'];
@@ -505,9 +504,10 @@ App::init()
->inject('queueForEvents')
->inject('queueForCertificates')
->inject('queueForFunctions')
+ ->inject('executor')
->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, StatsUsage $queueForStatsUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, callable $isResourceBlocked, string $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, StatsUsage $queueForStatsUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, Executor $executor, callable $isResourceBlocked, string $previewHostname) {
/*
* Appwrite Router
*/
@@ -515,7 +515,7 @@ App::init()
$mainDomain = System::getEnv('_APP_DOMAIN', '');
// Only run Router when external domain
if ($host !== $mainDomain || !empty($previewHostname)) {
- if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname)) {
+ if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname)) {
return;
}
}
@@ -584,6 +584,12 @@ App::init()
]);
}
+ $owner = '';
+ $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
+ if (!empty($functionsDomain) && \str_ends_with($domain->get(), $functionsDomain)) {
+ $owner = 'Appwrite';
+ }
+
if ($domainDocument->isEmpty()) {
$domainDocument = new Document([
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
@@ -591,8 +597,10 @@ App::init()
'domain' => $domain->get(),
'resourceType' => 'api',
'status' => 'verifying',
- 'projectId' => 'console',
- 'projectInternalId' => 'console'
+ 'projectId' => $console->getId(),
+ 'projectInternalId' => $console->getInternalId(),
+ 'owner' => $owner,
+ 'region' => $console->getAttribute('region')
]);
$domainDocument = $dbForPlatform->createDocument('rules', $domainDocument);
@@ -736,10 +744,12 @@ App::options()
->inject('queueForEvents')
->inject('queueForStatsUsage')
->inject('queueForFunctions')
+ ->inject('executor')
->inject('geodb')
->inject('isResourceBlocked')
->inject('previewHostname')
- ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked, string $previewHostname) {
+ ->inject('project')
+ ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, Document $project) {
/*
* Appwrite Router
*/
@@ -747,7 +757,7 @@ App::options()
$mainDomain = System::getEnv('_APP_DOMAIN', '');
// Only run Router when external domain
if ($host !== $mainDomain || !empty($previewHostname)) {
- if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname)) {
+ if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname)) {
return;
}
}
@@ -762,6 +772,16 @@ App::options()
->addHeader('Access-Control-Allow-Origin', $origin)
->addHeader('Access-Control-Allow-Credentials', 'true')
->noContent();
+
+ /** OPTIONS requests in utopia do not execute shutdown handlers, as a result we need to track the OPTIONS requests explicitly
+ * @see https://github.com/utopia-php/http/blob/0.33.16/src/App.php#L825-L855
+ */
+ $queueForStatsUsage
+ ->addMetric(METRIC_NETWORK_REQUESTS, 1)
+ ->addMetric(METRIC_NETWORK_INBOUND, $request->getSize())
+ ->addMetric(METRIC_NETWORK_OUTBOUND, $response->getSize())
+ ->setProject($project)
+ ->trigger();
});
App::error()
@@ -850,23 +870,22 @@ App::error()
$publish = $error->getCode() === 0 || $error->getCode() >= 500;
}
- if ($error->getCode() >= 400 && $error->getCode() < 500) {
+ $providerConfig = System::getEnv('_APP_EXPERIMENT_LOGGING_CONFIG', '');
+ if (!empty($providerConfig) && $error->getCode() >= 400 && $error->getCode() < 500) {
// Register error logger
- $providerName = System::getEnv('_APP_EXPERIMENT_LOGGING_PROVIDER', '');
- $providerConfig = System::getEnv('_APP_EXPERIMENT_LOGGING_CONFIG', '');
-
try {
- $loggingProvider = new DSN($providerConfig ?? '');
+ $loggingProvider = new DSN($providerConfig);
$providerName = $loggingProvider->getScheme();
if (!empty($providerName) && $providerName === 'sentry') {
$key = $loggingProvider->getPassword();
$projectId = $loggingProvider->getUser() ?? '';
$host = 'https://' . $loggingProvider->getHost();
+ $sampleRate = $loggingProvider->getParam('sample', 0.01);
$adapter = new Sentry($projectId, $key, $host);
$logger = new Logger($adapter);
- $logger->setSample(0.01);
+ $logger->setSample($sampleRate);
$publish = true;
} else {
throw new \Exception('Invalid experimental logging provider');
@@ -876,7 +895,10 @@ App::error()
}
}
- if ($publish && $project->getId() !== 'console') {
+ /**
+ * If its not a publishable error, track usage stats. Publishable errors are >= 500 or those explicitly marked as publish=true in errors.php
+ */
+ if (!$publish && $project->getId() !== 'console') {
if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
$fileSize = 0;
$file = $request->getFiles('file');
@@ -1043,10 +1065,11 @@ App::get('/robots.txt')
->inject('queueForEvents')
->inject('queueForStatsUsage')
->inject('queueForFunctions')
+ ->inject('executor')
->inject('geodb')
->inject('isResourceBlocked')
->inject('previewHostname')
- ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked, string $previewHostname) {
+ ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname) {
$host = $request->getHostname() ?? '';
$mainDomain = System::getEnv('_APP_DOMAIN', '');
@@ -1054,7 +1077,7 @@ App::get('/robots.txt')
$template = new View(__DIR__ . '/../views/general/robots.phtml');
$response->text($template->render(false));
} else {
- router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname);
+ router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname);
}
});
@@ -1071,10 +1094,11 @@ App::get('/humans.txt')
->inject('queueForEvents')
->inject('queueForStatsUsage')
->inject('queueForFunctions')
+ ->inject('executor')
->inject('geodb')
->inject('isResourceBlocked')
->inject('previewHostname')
- ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked, string $previewHostname) {
+ ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname) {
$host = $request->getHostname() ?? '';
$mainDomain = System::getEnv('_APP_DOMAIN', '');
@@ -1082,7 +1106,7 @@ App::get('/humans.txt')
$template = new View(__DIR__ . '/../views/general/humans.phtml');
$response->text($template->render(false));
} else {
- router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname);
+ router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname);
}
});
diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php
index 6a718a822b..5dd9966cb2 100644
--- a/app/controllers/shared/api.php
+++ b/app/controllers/shared/api.php
@@ -391,7 +391,8 @@ App::init()
->inject('resourceToken')
->inject('mode')
->inject('apiKey')
- ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey) use ($usageDatabaseListener, $eventDatabaseListener) {
+ ->inject('plan')
+ ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan) use ($usageDatabaseListener, $eventDatabaseListener) {
$route = $utopia->getRoute();
@@ -521,6 +522,10 @@ App::init()
$useCache = $route->getLabel('cache', false);
if ($useCache) {
+ $route = $utopia->match($request);
+ $isImageTransformation = $route->getPath() === '/v1/storage/buckets/:bucketId/files/:fileId/preview';
+ $isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && !Auth::isPrivilegedUser(Authorization::getRoles());
+
$key = md5($request->getURI() . '*' . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER);
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
$cache = new Cache(
@@ -533,9 +538,9 @@ App::init()
$parts = explode('/', $cacheLog->getAttribute('resourceType'));
$type = $parts[0] ?? null;
- if ($type === 'bucket') {
+ if ($type === 'bucket' && (!$isImageTransformation || !$isDisabled)) {
$bucketId = $parts[1] ?? null;
- $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
+ $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') == $bucket->getInternalId();
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
@@ -568,19 +573,23 @@ App::init()
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
-
- $transformedAt = $file->getAttribute('transformedAt', '');
- if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
- $file->setAttribute('transformedAt', DateTime::now());
- Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
+ //Do not update transformedAt if it's a console user
+ if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
+ $transformedAt = $file->getAttribute('transformedAt', '');
+ if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
+ $file->setAttribute('transformedAt', DateTime::now());
+ Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
+ }
}
}
$response
->addHeader('Cache-Control', sprintf('private, max-age=%d', $timestamp))
->addHeader('X-Appwrite-Cache', 'hit')
- ->setContentType($cacheLog->getAttribute('mimeType'))
- ->send($data);
+ ->setContentType($cacheLog->getAttribute('mimeType'));
+ if (!$isImageTransformation || !$isDisabled) {
+ $response->send($data);
+ }
} else {
$response
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
diff --git a/app/http.php b/app/http.php
index b9aa69a7cc..451a25a601 100644
--- a/app/http.php
+++ b/app/http.php
@@ -304,6 +304,54 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
}
});
+ $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');
+ }
+
+ if ($dbForProject->getCollection(Audit::COLLECTION)->isEmpty()) {
+ $audit = new Audit($dbForProject);
+ $audit->setup();
+ }
+
+ foreach ($projectCollections as $key => $collection) {
+ if (($collection['$collection'] ?? '') !== Database::METADATA) {
+ continue;
+ }
+ if (!$dbForProject->getCollection($key)->isEmpty()) {
+ continue;
+ }
+
+ $attributes = \array_map(fn ($attribute) => new Document($attribute), $collection['attributes']);
+ $indexes = \array_map(fn (array $index) => new Document($index), $collection['indexes']);
+
+ Console::success('[Setup] - Creating project collection: ' . $collection['$id'] . '...');
+
+ $dbForProject->createCollection($key, $attributes, $indexes);
+ }
+ }
+
$pools->reclaim();
Console::success('[Setup] - Server database init completed...');
});
diff --git a/app/init.php b/app/init.php
index 9f7361ebd2..c32f1eb9a8 100644
--- a/app/init.php
+++ b/app/init.php
@@ -8,6 +8,8 @@
*
*/
+use Utopia\System\System;
+
if (\file_exists(__DIR__ . '/../vendor/autoload.php')) {
require_once __DIR__ . '/../vendor/autoload.php';
}
@@ -18,1116 +20,13 @@ if (\file_exists(__DIR__ . '/../vendor/autoload.php')) {
\ini_set('default_socket_timeout', -1);
\error_reporting(E_ALL);
-use Ahc\Jwt\JWT;
-use Ahc\Jwt\JWTException;
-use Appwrite\Auth\Auth;
-use Appwrite\Auth\Key;
-use Appwrite\Event\Audit;
-use Appwrite\Event\Build;
-use Appwrite\Event\Certificate;
-use Appwrite\Event\Database as EventDatabase;
-use Appwrite\Event\Delete;
-use Appwrite\Event\Event;
-use Appwrite\Event\Func;
-use Appwrite\Event\Mail;
-use Appwrite\Event\Messaging;
-use Appwrite\Event\Migration;
-use Appwrite\Event\Realtime;
-use Appwrite\Event\StatsUsage;
-use Appwrite\Event\Webhook;
-use Appwrite\Extend\Exception;
-use Appwrite\Functions\Specification;
-use Appwrite\GraphQL\Promises\Adapter\Swoole;
-use Appwrite\GraphQL\Schema;
-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;
-use PHPMailer\PHPMailer\PHPMailer;
-use Swoole\Database\PDOProxy;
-use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
-use Utopia\App;
-use Utopia\Cache\Adapter\Redis as RedisCache;
-use Utopia\Cache\Adapter\Sharding;
-use Utopia\Cache\Cache;
-use Utopia\CLI\Console;
-use Utopia\Config\Config;
-use Utopia\Database\Adapter\MariaDB;
-use Utopia\Database\Adapter\MySQL;
-use Utopia\Database\Adapter\SQL;
-use Utopia\Database\Database;
-use Utopia\Database\Document;
-use Utopia\Database\Helpers\ID;
-use Utopia\Database\Query;
-use Utopia\Database\Validator\Authorization;
-use Utopia\Database\Validator\Datetime as DatetimeValidator;
-use Utopia\Database\Validator\Structure;
-use Utopia\Domains\Validator\PublicDomain;
-use Utopia\DSN\DSN;
-use Utopia\Locale\Locale;
-use Utopia\Logger\Adapter\AppSignal;
-use Utopia\Logger\Adapter\LogOwl;
-use Utopia\Logger\Adapter\Raygun;
-use Utopia\Logger\Adapter\Sentry;
-use Utopia\Logger\Log;
-use Utopia\Logger\Logger;
-use Utopia\Pools\Group;
-use Utopia\Pools\Pool;
-use Utopia\Queue;
-use Utopia\Registry\Registry;
-use Utopia\Storage\Device;
-use Utopia\Storage\Device\Backblaze;
-use Utopia\Storage\Device\DOSpaces;
-use Utopia\Storage\Device\Linode;
-use Utopia\Storage\Device\Local;
-use Utopia\Storage\Device\S3;
-use Utopia\Storage\Device\Wasabi;
-use Utopia\Storage\Storage;
-use Utopia\System\System;
-use Utopia\Validator\Hostname;
-use Utopia\Validator\IP;
-use Utopia\Validator\Range;
-use Utopia\Validator\URL;
-use Utopia\Validator\WhiteList;
-use Utopia\VCS\Adapter\Git\GitHub as VcsGitHub;
-
-const APP_NAME = 'Appwrite';
-const APP_DOMAIN = 'appwrite.io';
-const APP_EMAIL_TEAM = 'team@localhost.test'; // Default email address
-const APP_EMAIL_SECURITY = ''; // Default security email address
-const APP_USERAGENT = APP_NAME . '-Server v%s. Please report abuse at %s';
-const APP_MODE_DEFAULT = 'default';
-const APP_MODE_ADMIN = 'admin';
-const APP_PAGING_LIMIT = 12;
-const APP_LIMIT_COUNT = 5000;
-const APP_LIMIT_USERS = 10_000;
-const APP_LIMIT_USER_PASSWORD_HISTORY = 20;
-const APP_LIMIT_USER_SESSIONS_MAX = 100;
-const APP_LIMIT_USER_SESSIONS_DEFAULT = 10;
-const APP_LIMIT_ANTIVIRUS = 20_000_000; //20MB
-const APP_LIMIT_ENCRYPTION = 20_000_000; //20MB
-const APP_LIMIT_COMPRESSION = 20_000_000; //20MB
-const APP_LIMIT_ARRAY_PARAMS_SIZE = 100; // Default maximum of how many elements can there be in API parameter that expects array value
-const APP_LIMIT_ARRAY_LABELS_SIZE = 1000; // Default maximum of how many labels elements can there be in API parameter that expects array value
-const APP_LIMIT_ARRAY_ELEMENT_SIZE = 4096; // Default maximum length of element in array parameter represented by maximum URL length.
-const APP_LIMIT_SUBQUERY = 1000;
-const APP_LIMIT_SUBSCRIBERS_SUBQUERY = 1_000_000;
-const APP_LIMIT_WRITE_RATE_DEFAULT = 60; // Default maximum write rate per rate period
-const APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT = 60; // Default maximum write rate period in seconds
-const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return in list API calls
-const APP_KEY_ACCESS = 24 * 60 * 60; // 24 hours
-const APP_USER_ACCESS = 24 * 60 * 60; // 24 hours
-const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours
-const APP_FILE_ACCESS = 24 * 60 * 60; // 24 hours
-const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
-const APP_CACHE_BUSTER = 4318;
-const APP_VERSION_STABLE = '1.6.1';
-const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
-const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
-const APP_DATABASE_ATTRIBUTE_IP = 'ip';
-const APP_DATABASE_ATTRIBUTE_DATETIME = 'datetime';
-const APP_DATABASE_ATTRIBUTE_URL = 'url';
-const APP_DATABASE_ATTRIBUTE_INT_RANGE = 'intRange';
-const APP_DATABASE_ATTRIBUTE_FLOAT_RANGE = 'floatRange';
-const APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH = 1_073_741_824; // 2^32 bits / 4 bits per char
-const APP_DATABASE_TIMEOUT_MILLISECONDS = 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';
-const APP_STORAGE_CACHE = '/storage/cache';
-const APP_STORAGE_CERTIFICATES = '/storage/certificates';
-const APP_STORAGE_CONFIG = '/storage/config';
-const APP_STORAGE_READ_BUFFER = 20 * (1000 * 1000); //20MB other names `APP_STORAGE_MEMORY_LIMIT`, `APP_STORAGE_MEMORY_BUFFER`, `APP_STORAGE_READ_LIMIT`, `APP_STORAGE_BUFFER_LIMIT`
-const APP_SOCIAL_TWITTER = 'https://twitter.com/appwrite';
-const APP_SOCIAL_TWITTER_HANDLE = 'appwrite';
-const APP_SOCIAL_FACEBOOK = 'https://www.facebook.com/appwrite.io';
-const APP_SOCIAL_LINKEDIN = 'https://www.linkedin.com/company/appwrite';
-const APP_SOCIAL_INSTAGRAM = 'https://www.instagram.com/appwrite.io';
-const APP_SOCIAL_GITHUB = 'https://github.com/appwrite';
-const APP_SOCIAL_DISCORD = 'https://appwrite.io/discord';
-const APP_SOCIAL_DISCORD_CHANNEL = '564160730845151244';
-const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
-const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
-const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1';
-const APP_HOSTNAME_INTERNAL = 'appwrite';
-const APP_FUNCTION_SPECIFICATION_DEFAULT = Specification::S_1VCPU_512MB;
-const APP_FUNCTION_CPUS_DEFAULT = 0.5;
-const APP_FUNCTION_MEMORY_DEFAULT = 512;
-const APP_PLATFORM_SERVER = 'server';
-const APP_PLATFORM_CLIENT = 'client';
-const APP_PLATFORM_CONSOLE = 'console';
-
-// Database Reconnect
-const DATABASE_RECONNECT_SLEEP = 2;
-const DATABASE_RECONNECT_MAX_ATTEMPTS = 10;
-
-// Database Worker Types
-const DATABASE_TYPE_CREATE_ATTRIBUTE = 'createAttribute';
-const DATABASE_TYPE_CREATE_INDEX = 'createIndex';
-const DATABASE_TYPE_DELETE_ATTRIBUTE = 'deleteAttribute';
-const DATABASE_TYPE_DELETE_INDEX = 'deleteIndex';
-const DATABASE_TYPE_DELETE_COLLECTION = 'deleteCollection';
-const DATABASE_TYPE_DELETE_DATABASE = 'deleteDatabase';
-
-// Build Worker Types
-const BUILD_TYPE_DEPLOYMENT = 'deployment';
-const BUILD_TYPE_RETRY = 'retry';
-
-// Deletion Types
-const DELETE_TYPE_DATABASES = 'databases';
-const DELETE_TYPE_DOCUMENT = 'document';
-const DELETE_TYPE_COLLECTIONS = 'collections';
-const DELETE_TYPE_PROJECTS = 'projects';
-const DELETE_TYPE_FUNCTIONS = 'functions';
-const DELETE_TYPE_DEPLOYMENTS = 'deployments';
-const DELETE_TYPE_USERS = 'users';
-const DELETE_TYPE_TEAM_PROJECTS = 'teams_projects';
-const DELETE_TYPE_EXECUTIONS = 'executions';
-const DELETE_TYPE_AUDIT = 'audit';
-const DELETE_TYPE_ABUSE = 'abuse';
-const DELETE_TYPE_USAGE = 'usage';
-const DELETE_TYPE_REALTIME = 'realtime';
-const DELETE_TYPE_BUCKETS = 'buckets';
-const DELETE_TYPE_INSTALLATIONS = 'installations';
-const DELETE_TYPE_RULES = 'rules';
-const DELETE_TYPE_SESSIONS = 'sessions';
-const DELETE_TYPE_CACHE_BY_TIMESTAMP = 'cacheByTimeStamp';
-const DELETE_TYPE_CACHE_BY_RESOURCE = 'cacheByResource';
-const DELETE_TYPE_SCHEDULES = 'schedules';
-const DELETE_TYPE_TOPIC = 'topic';
-const DELETE_TYPE_TARGET = 'target';
-const DELETE_TYPE_EXPIRED_TARGETS = 'invalid_targets';
-const DELETE_TYPE_SESSION_TARGETS = 'session_targets';
-const DELETE_TYPE_MAINTENANCE = 'maintenance';
-
-// Message types
-const MESSAGE_SEND_TYPE_INTERNAL = 'internal';
-const MESSAGE_SEND_TYPE_EXTERNAL = 'external';
-// Mail Types
-const MAIL_TYPE_VERIFICATION = 'verification';
-const MAIL_TYPE_MAGIC_SESSION = 'magicSession';
-const MAIL_TYPE_RECOVERY = 'recovery';
-const MAIL_TYPE_INVITATION = 'invitation';
-const MAIL_TYPE_CERTIFICATE = 'certificate';
-// Auth Types
-const APP_AUTH_TYPE_SESSION = 'Session';
-const APP_AUTH_TYPE_JWT = 'JWT';
-const APP_AUTH_TYPE_KEY = 'Key';
-const APP_AUTH_TYPE_ADMIN = 'Admin';
-// Response related
-const MAX_OUTPUT_CHUNK_SIZE = 10 * 1024 * 1024; // 10MB
-// Function headers
-const FUNCTION_ALLOWLIST_HEADERS_REQUEST = ['content-type', 'agent', 'content-length', 'host'];
-const FUNCTION_ALLOWLIST_HEADERS_RESPONSE = ['content-type', 'content-length'];
-// Message types
-const MESSAGE_TYPE_EMAIL = 'email';
-const MESSAGE_TYPE_SMS = 'sms';
-const MESSAGE_TYPE_PUSH = 'push';
-// API key types
-const API_KEY_STANDARD = 'standard';
-const API_KEY_DYNAMIC = 'dynamic';
-// Usage metrics
-const METRIC_TEAMS = 'teams';
-const METRIC_USERS = 'users';
-const METRIC_WEBHOOKS_SENT = 'webhooks.events.sent';
-const METRIC_WEBHOOKS_FAILED = 'webhooks.events.failed';
-const METRIC_WEBHOOK_ID_SENT = '{webhookInternalId}.webhooks.events.sent';
-const METRIC_WEBHOOK_ID_FAILED = '{webhookInternalId}.webhooks.events.failed';
-const METRIC_AUTH_METHOD_PHONE = 'auth.method.phone';
-const METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE = METRIC_AUTH_METHOD_PHONE . '.{countryCode}';
-const METRIC_MESSAGES = 'messages';
-const METRIC_MESSAGES_SENT = METRIC_MESSAGES . '.sent';
-const METRIC_MESSAGES_FAILED = METRIC_MESSAGES . '.failed';
-const METRIC_MESSAGES_TYPE = METRIC_MESSAGES . '.{type}';
-const METRIC_MESSAGES_TYPE_SENT = METRIC_MESSAGES . '.{type}.sent';
-const METRIC_MESSAGES_TYPE_FAILED = METRIC_MESSAGES . '.{type}.failed';
-const METRIC_MESSAGES_TYPE_PROVIDER = METRIC_MESSAGES . '.{type}.{provider}';
-const METRIC_MESSAGES_TYPE_PROVIDER_SENT = METRIC_MESSAGES . '.{type}.{provider}.sent';
-const METRIC_MESSAGES_TYPE_PROVIDER_FAILED = METRIC_MESSAGES . '.{type}.{provider}.failed';
-const METRIC_SESSIONS = 'sessions';
-const METRIC_DATABASES = 'databases';
-const METRIC_COLLECTIONS = 'collections';
-const METRIC_DATABASES_STORAGE = 'databases.storage';
-const METRIC_DATABASE_ID_COLLECTIONS = '{databaseInternalId}.collections';
-const METRIC_DATABASE_ID_STORAGE = '{databaseInternalId}.databases.storage';
-const METRIC_DOCUMENTS = 'documents';
-const METRIC_DATABASE_ID_DOCUMENTS = '{databaseInternalId}.documents';
-const METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS = '{databaseInternalId}.{collectionInternalId}.documents';
-const METRIC_DATABASE_ID_COLLECTION_ID_STORAGE = '{databaseInternalId}.{collectionInternalId}.databases.storage';
-const METRIC_DATABASES_OPERATIONS_READS = 'databases.operations.reads';
-const METRIC_DATABASE_ID_OPERATIONS_READS = '{databaseInternalId}.databases.operations.reads';
-const METRIC_DATABASES_OPERATIONS_WRITES = 'databases.operations.writes';
-const METRIC_DATABASE_ID_OPERATIONS_WRITES = '{databaseInternalId}.databases.operations.writes';
-const METRIC_BUCKETS = 'buckets';
-const METRIC_FILES = 'files';
-const METRIC_FILES_STORAGE = 'files.storage';
-const METRIC_FILES_TRANSFORMATIONS = 'files.transformations';
-const METRIC_BUCKET_ID_FILES_TRANSFORMATIONS = '{bucketInternalId}.files.transformations';
-const METRIC_FILES_IMAGES_TRANSFORMED = 'files.imagesTransformed';
-const METRIC_BUCKET_ID_FILES_IMAGES_TRANSFORMED = '{bucketInternalId}.files.imagesTransformed';
-const METRIC_BUCKET_ID_FILES = '{bucketInternalId}.files';
-const METRIC_BUCKET_ID_FILES_STORAGE = '{bucketInternalId}.files.storage';
-const METRIC_FUNCTIONS = 'functions';
-const METRIC_DEPLOYMENTS = 'deployments';
-const METRIC_DEPLOYMENTS_STORAGE = 'deployments.storage';
-const METRIC_BUILDS = 'builds';
-const METRIC_BUILDS_SUCCESS = 'builds.success';
-const METRIC_BUILDS_FAILED = 'builds.failed';
-const METRIC_BUILDS_STORAGE = 'builds.storage';
-const METRIC_BUILDS_COMPUTE = 'builds.compute';
-const METRIC_BUILDS_COMPUTE_SUCCESS = 'builds.compute.success';
-const METRIC_BUILDS_COMPUTE_FAILED = 'builds.compute.failed';
-const METRIC_BUILDS_MB_SECONDS = 'builds.mbSeconds';
-const METRIC_FUNCTION_ID_BUILDS = '{functionInternalId}.builds';
-const METRIC_FUNCTION_ID_BUILDS_SUCCESS = '{functionInternalId}.builds.success';
-const METRIC_FUNCTION_ID_BUILDS_FAILED = '{functionInternalId}.builds.failed';
-const METRIC_FUNCTION_ID_BUILDS_STORAGE = '{functionInternalId}.builds.storage';
-const METRIC_FUNCTION_ID_BUILDS_COMPUTE = '{functionInternalId}.builds.compute';
-const METRIC_FUNCTION_ID_BUILDS_COMPUTE_SUCCESS = '{functionInternalId}.builds.compute.success';
-const METRIC_FUNCTION_ID_BUILDS_COMPUTE_FAILED = '{functionInternalId}.builds.compute.failed';
-const METRIC_FUNCTION_ID_DEPLOYMENTS = '{resourceType}.{resourceInternalId}.deployments';
-const METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE = '{resourceType}.{resourceInternalId}.deployments.storage';
-const METRIC_FUNCTION_ID_BUILDS_MB_SECONDS = '{functionInternalId}.builds.mbSeconds';
-const METRIC_EXECUTIONS = 'executions';
-const METRIC_EXECUTIONS_COMPUTE = 'executions.compute';
-const METRIC_EXECUTIONS_MB_SECONDS = 'executions.mbSeconds';
-const METRIC_FUNCTION_ID_EXECUTIONS = '{functionInternalId}.executions';
-const METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE = '{functionInternalId}.executions.compute';
-const METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS = '{functionInternalId}.executions.mbSeconds';
-const METRIC_NETWORK_REQUESTS = 'network.requests';
-const METRIC_NETWORK_INBOUND = 'network.inbound';
-const METRIC_NETWORK_OUTBOUND = 'network.outbound';
-const METRIC_MAU = 'users.mau';
-const METRIC_DAU = 'users.dau';
-const METRIC_WAU = 'users.wau';
-const METRIC_WEBHOOKS = 'webhooks';
-const METRIC_PLATFORMS = 'platforms';
-const METRIC_PROVIDERS = 'providers';
-const METRIC_TOPICS = 'topics';
-const METRIC_KEYS = 'keys';
-const METRIC_RESOURCE_TYPE_ID_BUILDS = '{resourceType}.{resourceInternalId}.builds';
-const METRIC_RESOURCE_TYPE_ID_BUILDS_STORAGE = '{resourceType}.{resourceInternalId}.builds.storage';
-const METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS = '{resourceType}.{resourceInternalId}.deployments';
-const METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS_STORAGE = '{resourceType}.{resourceInternalId}.deployments.storage';
-
-// Resource types
-
-const RESOURCE_TYPE_PROJECTS = 'projects';
-const RESOURCE_TYPE_FUNCTIONS = 'functions';
-const RESOURCE_TYPE_DATABASES = 'databases';
-const RESOURCE_TYPE_BUCKETS = 'buckets';
-const RESOURCE_TYPE_PROVIDERS = 'providers';
-const RESOURCE_TYPE_TOPICS = 'topics';
-const RESOURCE_TYPE_SUBSCRIBERS = 'subscribers';
-const RESOURCE_TYPE_MESSAGES = 'messages';
-
-$register = new Registry();
-
-App::setMode(System::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION));
-
-if (!App::isProduction()) {
- // Allow specific domains to skip public domain validation in dev environment
- // Useful for existing tests involving webhooks
- PublicDomain::allow(['request-catcher']);
-}
-
-/*
- * ENV vars
- */
-Config::load('events', __DIR__ . '/config/events.php');
-Config::load('auth', __DIR__ . '/config/auth.php');
-Config::load('apis', __DIR__ . '/config/apis.php'); // List of APIs
-Config::load('errors', __DIR__ . '/config/errors.php');
-Config::load('oAuthProviders', __DIR__ . '/config/oAuthProviders.php');
-Config::load('platforms', __DIR__ . '/config/platforms.php');
-Config::load('collections', __DIR__ . '/config/collections.php');
-Config::load('runtimes', __DIR__ . '/config/runtimes.php');
-Config::load('runtimes-v2', __DIR__ . '/config/runtimes-v2.php');
-Config::load('usage', __DIR__ . '/config/usage.php');
-Config::load('roles', __DIR__ . '/config/roles.php'); // User roles and scopes
-Config::load('scopes', __DIR__ . '/config/scopes.php'); // User roles and scopes
-Config::load('services', __DIR__ . '/config/services.php'); // List of services
-Config::load('variables', __DIR__ . '/config/variables.php'); // List of env variables
-Config::load('regions', __DIR__ . '/config/regions.php'); // List of available regions
-Config::load('avatar-browsers', __DIR__ . '/config/avatars/browsers.php');
-Config::load('avatar-credit-cards', __DIR__ . '/config/avatars/credit-cards.php');
-Config::load('avatar-flags', __DIR__ . '/config/avatars/flags.php');
-Config::load('locale-codes', __DIR__ . '/config/locale/codes.php');
-Config::load('locale-currencies', __DIR__ . '/config/locale/currencies.php');
-Config::load('locale-eu', __DIR__ . '/config/locale/eu.php');
-Config::load('locale-languages', __DIR__ . '/config/locale/languages.php');
-Config::load('locale-phones', __DIR__ . '/config/locale/phones.php');
-Config::load('locale-countries', __DIR__ . '/config/locale/countries.php');
-Config::load('locale-continents', __DIR__ . '/config/locale/continents.php');
-Config::load('locale-templates', __DIR__ . '/config/locale/templates.php');
-Config::load('storage-logos', __DIR__ . '/config/storage/logos.php');
-Config::load('storage-mimes', __DIR__ . '/config/storage/mimes.php');
-Config::load('storage-inputs', __DIR__ . '/config/storage/inputs.php');
-Config::load('storage-outputs', __DIR__ . '/config/storage/outputs.php');
-Config::load('runtime-specifications', __DIR__ . '/config/runtimes/specifications.php');
-Config::load('function-templates', __DIR__ . '/config/function-templates.php');
-
-/**
- * New DB Filters
- */
-Database::addFilter(
- 'casting',
- function (mixed $value) {
- return json_encode(['value' => $value], JSON_PRESERVE_ZERO_FRACTION);
- },
- function (mixed $value) {
- if (is_null($value)) {
- return;
- }
-
- return json_decode($value, true)['value'];
- }
-);
-
-Database::addFilter(
- 'enum',
- function (mixed $value, Document $attribute) {
- if ($attribute->isSet('elements')) {
- $attribute->removeAttribute('elements');
- }
-
- return $value;
- },
- function (mixed $value, Document $attribute) {
- $formatOptions = \json_decode($attribute->getAttribute('formatOptions', '[]'), true);
- if (isset($formatOptions['elements'])) {
- $attribute->setAttribute('elements', $formatOptions['elements']);
- }
-
- return $value;
- }
-);
-
-Database::addFilter(
- 'range',
- function (mixed $value, Document $attribute) {
- if ($attribute->isSet('min')) {
- $attribute->removeAttribute('min');
- }
- if ($attribute->isSet('max')) {
- $attribute->removeAttribute('max');
- }
-
- return $value;
- },
- function (mixed $value, Document $attribute) {
- $formatOptions = json_decode($attribute->getAttribute('formatOptions', '[]'), true);
- if (isset($formatOptions['min']) || isset($formatOptions['max'])) {
- $attribute
- ->setAttribute('min', $formatOptions['min'])
- ->setAttribute('max', $formatOptions['max']);
- }
-
- return $value;
- }
-);
-
-Database::addFilter(
- 'subQueryAttributes',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- $attributes = $database->find('attributes', [
- Query::equal('collectionInternalId', [$document->getInternalId()]),
- Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
- Query::limit($database->getLimitForAttributes()),
- ]);
-
- foreach ($attributes as $attribute) {
- if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) {
- $options = $attribute->getAttribute('options');
- foreach ($options as $key => $value) {
- $attribute->setAttribute($key, $value);
- }
- $attribute->removeAttribute('options');
- }
- }
-
- return $attributes;
- }
-);
-
-Database::addFilter(
- 'subQueryIndexes',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return $database
- ->find('indexes', [
- Query::equal('collectionInternalId', [$document->getInternalId()]),
- Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
- Query::limit($database->getLimitForIndexes()),
- ]);
- }
-);
-
-Database::addFilter(
- 'subQueryPlatforms',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return $database
- ->find('platforms', [
- Query::equal('projectInternalId', [$document->getInternalId()]),
- Query::limit(APP_LIMIT_SUBQUERY),
- ]);
- }
-);
-
-Database::addFilter(
- 'subQueryKeys',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return $database
- ->find('keys', [
- Query::equal('projectInternalId', [$document->getInternalId()]),
- Query::limit(APP_LIMIT_SUBQUERY),
- ]);
- }
-);
-
-Database::addFilter(
- 'subQueryWebhooks',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return $database
- ->find('webhooks', [
- Query::equal('projectInternalId', [$document->getInternalId()]),
- Query::limit(APP_LIMIT_SUBQUERY),
- ]);
- }
-);
-
-Database::addFilter(
- 'subQuerySessions',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return Authorization::skip(fn () => $database->find('sessions', [
- Query::equal('userInternalId', [$document->getInternalId()]),
- Query::limit(APP_LIMIT_SUBQUERY),
- ]));
- }
-);
-
-Database::addFilter(
- 'subQueryTokens',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return Authorization::skip(fn () => $database
- ->find('tokens', [
- Query::equal('userInternalId', [$document->getInternalId()]),
- Query::limit(APP_LIMIT_SUBQUERY),
- ]));
- }
-);
-
-Database::addFilter(
- 'subQueryChallenges',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return Authorization::skip(fn () => $database
- ->find('challenges', [
- Query::equal('userInternalId', [$document->getInternalId()]),
- Query::limit(APP_LIMIT_SUBQUERY),
- ]));
- }
-);
-
-Database::addFilter(
- 'subQueryAuthenticators',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return Authorization::skip(fn () => $database
- ->find('authenticators', [
- Query::equal('userInternalId', [$document->getInternalId()]),
- Query::limit(APP_LIMIT_SUBQUERY),
- ]));
- }
-);
-
-Database::addFilter(
- 'subQueryMemberships',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return Authorization::skip(fn () => $database
- ->find('memberships', [
- Query::equal('userInternalId', [$document->getInternalId()]),
- Query::limit(APP_LIMIT_SUBQUERY),
- ]));
- }
-);
-
-Database::addFilter(
- 'subQueryVariables',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return $database
- ->find('variables', [
- Query::equal('resourceInternalId', [$document->getInternalId()]),
- Query::equal('resourceType', ['function']),
- Query::limit(APP_LIMIT_SUBQUERY),
- ]);
- }
-);
-
-Database::addFilter(
- 'encrypt',
- function (mixed $value) {
- $key = System::getEnv('_APP_OPENSSL_KEY_V1');
- $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
- $tag = null;
-
- return json_encode([
- 'data' => OpenSSL::encrypt($value, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
- 'method' => OpenSSL::CIPHER_AES_128_GCM,
- 'iv' => \bin2hex($iv),
- 'tag' => \bin2hex($tag ?? ''),
- 'version' => '1',
- ]);
- },
- function (mixed $value) {
- if (is_null($value)) {
- return;
- }
- $value = json_decode($value, true);
- $key = System::getEnv('_APP_OPENSSL_KEY_V' . $value['version']);
-
- return OpenSSL::decrypt($value['data'], $value['method'], $key, 0, hex2bin($value['iv']), hex2bin($value['tag']));
- }
-);
-
-Database::addFilter(
- 'subQueryProjectVariables',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return $database
- ->find('variables', [
- Query::equal('resourceType', ['project']),
- Query::limit(APP_LIMIT_SUBQUERY)
- ]);
- }
-);
-
-Database::addFilter(
- 'userSearch',
- function (mixed $value, Document $user) {
- $searchValues = [
- $user->getId(),
- $user->getAttribute('email', ''),
- $user->getAttribute('name', ''),
- $user->getAttribute('phone', '')
- ];
-
- foreach ($user->getAttribute('labels', []) as $label) {
- $searchValues[] = 'label:' . $label;
- }
-
- $search = implode(' ', \array_filter($searchValues));
-
- return $search;
- },
- function (mixed $value) {
- return $value;
- }
-);
-
-Database::addFilter(
- 'subQueryTargets',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return Authorization::skip(fn () => $database
- ->find('targets', [
- Query::equal('userInternalId', [$document->getInternalId()]),
- Query::limit(APP_LIMIT_SUBQUERY)
- ]));
- }
-);
-
-Database::addFilter(
- 'subQueryTopicTargets',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- $targetIds = Authorization::skip(fn () => \array_map(
- fn ($document) => $document->getAttribute('targetInternalId'),
- $database->find('subscribers', [
- Query::equal('topicInternalId', [$document->getInternalId()]),
- Query::limit(APP_LIMIT_SUBSCRIBERS_SUBQUERY)
- ])
- ));
- if (\count($targetIds) > 0) {
- return $database->skipValidation(fn () => $database->find('targets', [
- Query::equal('$internalId', $targetIds)
- ]));
- }
- return [];
- }
-);
-
-Database::addFilter(
- 'providerSearch',
- function (mixed $value, Document $provider) {
- $searchValues = [
- $provider->getId(),
- $provider->getAttribute('name', ''),
- $provider->getAttribute('provider', ''),
- $provider->getAttribute('type', '')
- ];
-
- $search = \implode(' ', \array_filter($searchValues));
-
- return $search;
- },
- function (mixed $value) {
- return $value;
- }
-);
-
-Database::addFilter(
- 'topicSearch',
- function (mixed $value, Document $topic) {
- $searchValues = [
- $topic->getId(),
- $topic->getAttribute('name', ''),
- $topic->getAttribute('description', ''),
- ];
-
- $search = \implode(' ', \array_filter($searchValues));
-
- return $search;
- },
- function (mixed $value) {
- return $value;
- }
-);
-
-Database::addFilter(
- 'messageSearch',
- function (mixed $value, Document $message) {
- $searchValues = [
- $message->getId(),
- $message->getAttribute('description', ''),
- $message->getAttribute('status', ''),
- ];
-
- $data = \json_decode($message->getAttribute('data', []), true);
- $providerType = $message->getAttribute('providerType', '');
-
- switch ($providerType) {
- case MESSAGE_TYPE_EMAIL:
- $searchValues[] = $data['subject'];
- $searchValues[] = MESSAGE_TYPE_EMAIL;
- break;
- case MESSAGE_TYPE_SMS:
- $searchValues[] = $data['content'];
- $searchValues[] = MESSAGE_TYPE_SMS;
- break;
- case MESSAGE_TYPE_PUSH:
- $searchValues[] = $data['title'] ?? '';
- $searchValues[] = MESSAGE_TYPE_PUSH;
- break;
- }
-
- $search = \implode(' ', \array_filter($searchValues));
-
- return $search;
- },
- function (mixed $value) {
- return $value;
- }
-);
-
-/**
- * DB Formats
- */
-Structure::addFormat(APP_DATABASE_ATTRIBUTE_EMAIL, function () {
- return new Email();
-}, Database::VAR_STRING);
-
-Structure::addFormat(APP_DATABASE_ATTRIBUTE_DATETIME, function () {
- return new DatetimeValidator();
-}, Database::VAR_DATETIME);
-
-Structure::addFormat(APP_DATABASE_ATTRIBUTE_ENUM, function ($attribute) {
- $elements = $attribute['formatOptions']['elements'] ?? [];
- return new WhiteList($elements, true);
-}, Database::VAR_STRING);
-
-Structure::addFormat(APP_DATABASE_ATTRIBUTE_IP, function () {
- return new IP();
-}, Database::VAR_STRING);
-
-Structure::addFormat(APP_DATABASE_ATTRIBUTE_URL, function () {
- return new URL();
-}, Database::VAR_STRING);
-
-Structure::addFormat(APP_DATABASE_ATTRIBUTE_INT_RANGE, function ($attribute) {
- $min = $attribute['formatOptions']['min'] ?? -INF;
- $max = $attribute['formatOptions']['max'] ?? INF;
- return new Range($min, $max, Range::TYPE_INTEGER);
-}, Database::VAR_INTEGER);
-
-Structure::addFormat(APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, function ($attribute) {
- $min = $attribute['formatOptions']['min'] ?? -INF;
- $max = $attribute['formatOptions']['max'] ?? INF;
- return new Range($min, $max, Range::TYPE_FLOAT);
-}, Database::VAR_FLOAT);
-
-/*
- * Registry
- */
-$register->set('logger', function () {
- // Register error logger
- $providerName = System::getEnv('_APP_LOGGING_PROVIDER', '');
- $providerConfig = System::getEnv('_APP_LOGGING_CONFIG', '');
-
- try {
- $loggingProvider = new DSN($providerConfig ?? '');
-
- $providerName = $loggingProvider->getScheme();
- $providerConfig = match ($providerName) {
- 'sentry' => ['key' => $loggingProvider->getPassword(), 'projectId' => $loggingProvider->getUser() ?? '', 'host' => 'https://' . $loggingProvider->getHost()],
- 'logowl' => ['ticket' => $loggingProvider->getUser() ?? '', 'host' => $loggingProvider->getHost()],
- default => ['key' => $loggingProvider->getHost()],
- };
- } catch (Throwable $th) {
- // Fallback for older Appwrite versions up to 1.5.x that use _APP_LOGGING_PROVIDER and _APP_LOGGING_CONFIG environment variables
- Console::warning('Using deprecated logging configuration. Please update your configuration to use DSN format.' . $th->getMessage());
- $configChunks = \explode(";", $providerConfig);
-
- $providerConfig = match ($providerName) {
- 'sentry' => [ 'key' => $configChunks[0], 'projectId' => $configChunks[1] ?? '', 'host' => '',],
- 'logowl' => ['ticket' => $configChunks[0] ?? '', 'host' => ''],
- default => ['key' => $providerConfig],
- };
- }
-
- if (empty($providerName) || empty($providerConfig)) {
- return;
- }
-
- if (!Logger::hasProvider($providerName)) {
- throw new Exception(Exception::GENERAL_SERVER_ERROR, "Logging provider not supported. Logging is disabled");
- }
-
- try {
- $adapter = match ($providerName) {
- 'sentry' => new Sentry($providerConfig['projectId'], $providerConfig['key'], $providerConfig['host']),
- 'logowl' => new LogOwl($providerConfig['ticket'], $providerConfig['host']),
- 'raygun' => new Raygun($providerConfig['key']),
- 'appsignal' => new AppSignal($providerConfig['key']),
- default => null
- };
- } catch (Throwable $th) {
- $adapter = null;
- }
-
- if ($adapter === null) {
- Console::error("Logging provider not supported. Logging is disabled");
- return;
- }
-
- return new Logger($adapter);
-});
-
-$register->set('pools', function () {
- $group = new Group();
-
- $fallbackForDB = 'db_main=' . AppwriteURL::unparse([
- 'scheme' => 'mariadb',
- 'host' => System::getEnv('_APP_DB_HOST', 'mariadb'),
- 'port' => System::getEnv('_APP_DB_PORT', '3306'),
- 'user' => System::getEnv('_APP_DB_USER', ''),
- 'pass' => System::getEnv('_APP_DB_PASS', ''),
- 'path' => System::getEnv('_APP_DB_SCHEMA', ''),
- ]);
- $fallbackForRedis = 'redis_main=' . AppwriteURL::unparse([
- 'scheme' => 'redis',
- 'host' => System::getEnv('_APP_REDIS_HOST', 'redis'),
- 'port' => System::getEnv('_APP_REDIS_PORT', '6379'),
- 'user' => System::getEnv('_APP_REDIS_USER', ''),
- 'pass' => System::getEnv('_APP_REDIS_PASS', ''),
- ]);
-
- $connections = [
- 'console' => [
- 'type' => 'database',
- 'dsns' => $fallbackForDB,
- 'multiple' => false,
- 'schemes' => ['mariadb', 'mysql'],
- ],
- 'database' => [
- 'type' => 'database',
- 'dsns' => $fallbackForDB,
- 'multiple' => true,
- 'schemes' => ['mariadb', 'mysql'],
- ],
- 'logs' => [
- 'type' => 'database',
- 'dsns' => System::getEnv('_APP_CONNECTIONS_DB_LOGS', $fallbackForDB),
- 'multiple' => false,
- 'schemes' => ['mariadb', 'mysql'],
- ],
- 'publisher' => [
- 'type' => 'publisher',
- 'dsns' => $fallbackForRedis,
- 'multiple' => false,
- 'schemes' => ['redis'],
- ],
- 'consumer' => [
- 'type' => 'consumer',
- 'dsns' => $fallbackForRedis,
- 'multiple' => false,
- 'schemes' => ['redis'],
- ],
- 'pubsub' => [
- 'type' => 'pubsub',
- 'dsns' => $fallbackForRedis,
- 'multiple' => false,
- 'schemes' => ['redis'],
- ],
- 'cache' => [
- 'type' => 'cache',
- 'dsns' => $fallbackForRedis,
- 'multiple' => true,
- 'schemes' => ['redis'],
- ],
- ];
-
- $maxConnections = System::getEnv('_APP_CONNECTIONS_MAX', 151);
- $instanceConnections = $maxConnections / System::getEnv('_APP_POOL_CLIENTS', 14);
-
- $multiprocessing = System::getEnv('_APP_SERVER_MULTIPROCESS', 'disabled') === 'enabled';
-
- if ($multiprocessing) {
- $workerCount = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
- } else {
- $workerCount = 1;
- }
-
- if ($workerCount > $instanceConnections) {
- throw new \Exception('Pool size is too small. Increase the number of allowed database connections or decrease the number of workers.', 500);
- }
-
- $poolSize = (int)($instanceConnections / $workerCount);
-
- foreach ($connections as $key => $connection) {
- $type = $connection['type'] ?? '';
- $multiple = $connection['multiple'] ?? false;
- $schemes = $connection['schemes'] ?? [];
- $config = [];
- $dsns = explode(',', $connection['dsns'] ?? '');
- foreach ($dsns as &$dsn) {
- $dsn = explode('=', $dsn);
- $name = ($multiple) ? $key . '_' . $dsn[0] : $key;
- $dsn = $dsn[1] ?? '';
- $config[] = $name;
- if (empty($dsn)) {
- //throw new Exception(Exception::GENERAL_SERVER_ERROR, "Missing value for DSN connection in {$key}");
- continue;
- }
-
- $dsn = new DSN($dsn);
- $dsnHost = $dsn->getHost();
- $dsnPort = $dsn->getPort();
- $dsnUser = $dsn->getUser();
- $dsnPass = $dsn->getPassword();
- $dsnScheme = $dsn->getScheme();
- $dsnDatabase = $dsn->getPath();
-
- if (!in_array($dsnScheme, $schemes)) {
- throw new Exception(Exception::GENERAL_SERVER_ERROR, "Invalid console database scheme");
- }
-
- /**
- * Get Resource
- *
- * Creation could be reused across connection types like database, cache, queue, etc.
- *
- * Resource assignment to an adapter will happen below.
- */
- $resource = match ($dsnScheme) {
- 'mysql',
- 'mariadb' => function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
- return new PDOProxy(function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
- return new PDO("mysql:host={$dsnHost};port={$dsnPort};dbname={$dsnDatabase};charset=utf8mb4", $dsnUser, $dsnPass, array(
- PDO::ATTR_TIMEOUT => 3, // Seconds
- PDO::ATTR_PERSISTENT => true,
- PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
- PDO::ATTR_EMULATE_PREPARES => true,
- PDO::ATTR_STRINGIFY_FETCHES => true
- ));
- });
- },
- 'redis' => function () use ($dsnHost, $dsnPort, $dsnPass) {
- $redis = new \Redis();
- @$redis->pconnect($dsnHost, (int)$dsnPort);
- if ($dsnPass) {
- $redis->auth($dsnPass);
- }
- $redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
-
- return $redis;
- },
- default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Invalid scheme'),
- };
-
- $pool = new Pool($name, $poolSize, function () use ($type, $resource, $dsn) {
- // Get Adapter
- switch ($type) {
- case 'database':
- $adapter = match ($dsn->getScheme()) {
- 'mariadb' => new MariaDB($resource()),
- 'mysql' => new MySQL($resource()),
- default => null
- };
-
- $adapter->setDatabase($dsn->getPath());
- return $adapter;
- case 'pubsub':
- return match ($dsn->getScheme()) {
- 'redis' => new PubSub($resource()),
- default => null
- };
- case 'publisher':
- case 'consumer':
- return match ($dsn->getScheme()) {
- 'redis' => new Queue\Broker\Redis(new Queue\Connection\Redis($dsn->getHost(), $dsn->getPort())),
- default => null
- };
- case 'cache':
- return match ($dsn->getScheme()) {
- 'redis' => new RedisCache($resource()),
- default => null
- };
- default:
- throw new Exception(Exception::GENERAL_SERVER_ERROR, "Server error: Missing adapter implementation.");
- }
- });
-
- $group->add($pool);
- }
-
- Config::setParam('pools-' . $key, $config);
- }
-
- return $group;
-});
-
-$register->set('db', function () {
- // This is usually for our workers or CLI commands scope
- $dbHost = System::getEnv('_APP_DB_HOST', '');
- $dbPort = System::getEnv('_APP_DB_PORT', '');
- $dbUser = System::getEnv('_APP_DB_USER', '');
- $dbPass = System::getEnv('_APP_DB_PASS', '');
- $dbScheme = System::getEnv('_APP_DB_SCHEMA', '');
-
- return new PDO(
- "mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4",
- $dbUser,
- $dbPass,
- SQL::getPDOAttributes()
- );
-});
-
-$register->set('smtp', function () {
- $mail = new PHPMailer(true);
-
- $mail->isSMTP();
-
- $username = System::getEnv('_APP_SMTP_USERNAME');
- $password = System::getEnv('_APP_SMTP_PASSWORD');
-
- $mail->XMailer = 'Appwrite Mailer';
- $mail->Host = System::getEnv('_APP_SMTP_HOST', 'smtp');
- $mail->Port = System::getEnv('_APP_SMTP_PORT', 25);
- $mail->SMTPAuth = !empty($username) && !empty($password);
- $mail->Username = $username;
- $mail->Password = $password;
- $mail->SMTPSecure = System::getEnv('_APP_SMTP_SECURE', '');
- $mail->SMTPAutoTLS = false;
- $mail->CharSet = 'UTF-8';
-
- $from = \urldecode(System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'));
- $email = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
-
- $mail->setFrom($email, $from);
- $mail->addReplyTo($email, $from);
-
- $mail->isHTML(true);
-
- return $mail;
-});
-$register->set('geodb', function () {
- return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2024-09.mmdb');
-});
-$register->set('passwordsDictionary', function () {
- $content = \file_get_contents(__DIR__ . '/assets/security/10k-common-passwords');
- $content = explode("\n", $content);
- $content = array_flip($content);
- return $content;
-});
-$register->set('promiseAdapter', function () {
- return new Swoole();
-});
-$register->set('hooks', function () {
- return new Hooks();
-});
-/*
- * Localization
- */
-Locale::$exceptions = false;
-
-$locales = Config::getParam('locale-codes', []);
-
-foreach ($locales as $locale) {
- $code = $locale['code'];
-
- $path = __DIR__ . '/config/locale/translations/' . $code . '.json';
-
- if (!\file_exists($path)) {
- $path = __DIR__ . '/config/locale/translations/' . \substr($code, 0, 2) . '.json'; // if `ar-ae` doesn't exist, look for `ar`
- if (!\file_exists($path)) {
- $path = __DIR__ . '/config/locale/translations/en.json'; // if none translation exists, use default from `en.json`
- }
- }
-
- Locale::setLanguageFromJSON($code, $path);
-}
+require_once __DIR__ . '/init/constants.php';
+require_once __DIR__ . '/init/configs.php';
+require_once __DIR__ . '/init/database/filters.php';
+require_once __DIR__ . '/init/database/formats.php';
+require_once __DIR__ . '/init/locales.php';
+require_once __DIR__ . '/init/registers.php';
+require_once __DIR__ . '/init/resources.php';
\stream_context_set_default([ // Set global user agent and http settings
'http' => [
@@ -1140,861 +39,3 @@ foreach ($locales as $locale) {
'timeout' => 2,
],
]);
-
-// Runtime Execution
-App::setResource('log', fn () => new Log());
-App::setResource('logger', function ($register) {
- return $register->get('logger');
-}, ['register']);
-
-App::setResource('hooks', function ($register) {
- return $register->get('hooks');
-}, ['register']);
-
-App::setResource('register', fn () => $register);
-App::setResource('locale', fn () => new Locale(System::getEnv('_APP_LOCALE', 'en')));
-
-App::setResource('localeCodes', function () {
- return array_map(fn ($locale) => $locale['code'], Config::getParam('locale-codes', []));
-});
-
-// Queues
-App::setResource('publisher', function (Group $pools) {
- return $pools->get('publisher')->pop()->getResource();
-}, ['pools']);
-App::setResource('consumer', function (Group $pools) {
- return $pools->get('consumer')->pop()->getResource();
-}, ['pools']);
-App::setResource('queueForMessaging', function (Queue\Publisher $publisher) {
- return new Messaging($publisher);
-}, ['publisher']);
-App::setResource('queueForMails', function (Queue\Publisher $publisher) {
- return new Mail($publisher);
-}, ['publisher']);
-App::setResource('queueForBuilds', function (Queue\Publisher $publisher) {
- return new Build($publisher);
-}, ['publisher']);
-App::setResource('queueForDatabase', function (Queue\Publisher $publisher) {
- return new EventDatabase($publisher);
-}, ['publisher']);
-App::setResource('queueForDeletes', function (Queue\Publisher $publisher) {
- return new Delete($publisher);
-}, ['publisher']);
-App::setResource('queueForEvents', function (Queue\Publisher $publisher) {
- return new Event($publisher);
-}, ['publisher']);
-App::setResource('queueForWebhooks', function (Queue\Publisher $publisher) {
- return new Webhook($publisher);
-}, ['publisher']);
-App::setResource('queueForRealtime', function () {
- return new Realtime();
-}, []);
-App::setResource('queueForStatsUsage', function (Queue\Publisher $publisher) {
- return new StatsUsage($publisher);
-}, ['publisher']);
-App::setResource('queueForAudits', function (Queue\Publisher $publisher) {
- return new Audit($publisher);
-}, ['publisher']);
-App::setResource('queueForFunctions', function (Queue\Publisher $publisher) {
- return new Func($publisher);
-}, ['publisher']);
-App::setResource('queueForUsage', function (Queue\Publisher $publisher) {
- return new Usage($publisher);
-}, ['publisher']);
-App::setResource('queueForCertificates', function (Queue\Publisher $publisher) {
- return new Certificate($publisher);
-}, ['publisher']);
-App::setResource('queueForMigrations', function (Queue\Publisher $publisher) {
- return new Migration($publisher);
-}, ['publisher']);
-App::setResource('clients', function ($request, $console, $project) {
- $console->setAttribute('platforms', [ // Always allow current host
- '$collection' => ID::custom('platforms'),
- 'name' => 'Current Host',
- 'type' => Origin::CLIENT_TYPE_WEB,
- 'hostname' => $request->getHostname(),
- ], Document::SET_TYPE_APPEND);
-
- $hostnames = explode(',', System::getEnv('_APP_CONSOLE_HOSTNAMES', ''));
- $validator = new Hostname();
- foreach ($hostnames as $hostname) {
- $hostname = trim($hostname);
- if (!$validator->isValid($hostname)) {
- continue;
- }
- $console->setAttribute('platforms', [
- '$collection' => ID::custom('platforms'),
- 'type' => Origin::CLIENT_TYPE_WEB,
- 'name' => $hostname,
- 'hostname' => $hostname,
- ], Document::SET_TYPE_APPEND);
- }
-
- /**
- * Get All verified client URLs for both console and current projects
- * + Filter for duplicated entries
- */
- $clientsConsole = \array_map(
- fn ($node) => $node['hostname'],
- \array_filter(
- $console->getAttribute('platforms', []),
- fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB) && !empty($node['hostname']))
- )
- );
-
- $clients = $clientsConsole;
- $platforms = $project->getAttribute('platforms', []);
-
- foreach ($platforms as $node) {
- if (
- isset($node['type']) &&
- ($node['type'] === Origin::CLIENT_TYPE_WEB ||
- $node['type'] === Origin::CLIENT_TYPE_FLUTTER_WEB) &&
- !empty($node['hostname'])
- ) {
- $clients[] = $node['hostname'];
- }
- }
-
- return \array_unique($clients);
-}, ['request', 'console', 'project']);
-
-App::setResource('resourceToken', function ($project, $dbForProject, $request) {
- $tokenJWT = $request->getParam('token');
-
- if (!empty($tokenJWT) && !$project->isEmpty()) { // JWT authentication
- $jwt = new JWT(App::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
-
- try {
- $payload = $jwt->decode($tokenJWT);
- } catch (JWTException $error) {
- return new Document([]);
- }
-
- $tokenId = $payload['tokenId'] ?? '';
- $secret = $payload['secret'] ?? '';
- if (empty($tokenId) || empty($secret)) {
- return new Document([]);
- }
-
- $token = Authorization::skip(fn () => $dbForProject->getDocument('resourceTokens', $tokenId));
-
- if ($token->isEmpty() || $token->getAttribute('secret') != $secret) {
- return new Document([]);
- }
-
- if ($token->getAttribute('resourceType') === 'file') {
- $internalIds = explode(':', $token->getAttribute('resourceInternalId'));
- $ids = explode(':', $token->getAttribute('resourceId'));
-
- if (count($internalIds) != 2 || count($ids) != 2) {
- return new Document([]);
- }
-
- return new Document([
- 'bucketId' => $ids[0],
- 'fileId' => $ids[1],
- 'bucketInternalId' => $internalIds[0],
- 'fileInternalId' => $internalIds[1],
- ]);
- }
- }
- return new Document([]);
-}, ['project', 'dbForProject', 'request']);
-
-App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform) {
- /** @var Appwrite\Utopia\Request $request */
- /** @var Appwrite\Utopia\Response $response */
- /** @var Utopia\Database\Document $project */
- /** @var Utopia\Database\Database $dbForProject */
- /** @var Utopia\Database\Database $dbForPlatform */
- /** @var string $mode */
-
- Authorization::setDefaultStatus(true);
-
- Auth::setCookieName('a_session_' . $project->getId());
-
- if (APP_MODE_ADMIN === $mode) {
- Auth::setCookieName('a_session_' . $console->getId());
- }
-
- $session = Auth::decodeSession(
- $request->getCookie(
- Auth::$cookieName, // Get sessions
- $request->getCookie(Auth::$cookieName . '_legacy', '')
- )
- );
-
- // Get session from header for SSR clients
- if (empty($session['id']) && empty($session['secret'])) {
- $sessionHeader = $request->getHeader('x-appwrite-session', '');
-
- if (!empty($sessionHeader)) {
- $session = Auth::decodeSession($sessionHeader);
- }
- }
-
- // Get fallback session from old clients (no SameSite support) or clients who block 3rd-party cookies
- if ($response) {
- $response->addHeader('X-Debug-Fallback', 'false');
- }
-
- if (empty($session['id']) && empty($session['secret'])) {
- if ($response) {
- $response->addHeader('X-Debug-Fallback', 'true');
- }
- $fallback = $request->getHeader('x-fallback-cookies', '');
- $fallback = \json_decode($fallback, true);
- $session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : ''));
- }
-
- Auth::$unique = $session['id'] ?? '';
- Auth::$secret = $session['secret'] ?? '';
-
- if (APP_MODE_ADMIN !== $mode) {
- if ($project->isEmpty()) {
- $user = new Document([]);
- } else {
- if ($project->getId() === 'console') {
- $user = $dbForPlatform->getDocument('users', Auth::$unique);
- } else {
- $user = $dbForProject->getDocument('users', Auth::$unique);
- }
- }
- } else {
- $user = $dbForPlatform->getDocument('users', Auth::$unique);
- }
-
- if (
- $user->isEmpty() // Check a document has been found in the DB
- || !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret)
- ) { // Validate user has valid login token
- $user = new Document([]);
- }
-
- // if (APP_MODE_ADMIN === $mode) {
- // if ($user->find('teamInternalId', $project->getAttribute('teamInternalId'), 'memberships')) {
- // Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
- // } else {
- // $user = new Document([]);
- // }
- // }
-
- $authJWT = $request->getHeader('x-appwrite-jwt', '');
-
- if (!empty($authJWT) && !$project->isEmpty()) { // JWT authentication
- $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
-
- try {
- $payload = $jwt->decode($authJWT);
- } catch (JWTException $error) {
- throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage());
- }
-
- $jwtUserId = $payload['userId'] ?? '';
- if (!empty($jwtUserId)) {
- $user = $dbForProject->getDocument('users', $jwtUserId);
- }
-
- $jwtSessionId = $payload['sessionId'] ?? '';
- if (!empty($jwtSessionId)) {
- if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token
- $user = new Document([]);
- }
- }
- }
-
- $dbForProject->setMetadata('user', $user->getId());
- $dbForPlatform->setMetadata('user', $user->getId());
-
- return $user;
-}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform']);
-
-App::setResource('project', function ($dbForPlatform, $request, $console) {
- /** @var Appwrite\Utopia\Request $request */
- /** @var Utopia\Database\Database $dbForPlatform */
- /** @var Utopia\Database\Document $console */
-
- $projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', ''));
-
- if (empty($projectId) || $projectId === 'console') {
- return $console;
- }
-
- $project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
-
- return $project;
-}, ['dbForPlatform', 'request', 'console']);
-
-App::setResource('session', function (Document $user) {
- if ($user->isEmpty()) {
- return;
- }
-
- $sessions = $user->getAttribute('sessions', []);
- $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret);
-
- if (!$sessionId) {
- return;
- }
-
- foreach ($sessions as $session) {/** @var Document $session */
- if ($sessionId === $session->getId()) {
- return $session;
- }
- }
-
- return;
-}, ['user']);
-
-App::setResource('console', function () {
- return new Document([
- '$id' => ID::custom('console'),
- '$internalId' => ID::custom('console'),
- 'name' => 'Appwrite',
- '$collection' => ID::custom('projects'),
- 'description' => 'Appwrite core engine',
- 'logo' => '',
- 'teamId' => null,
- 'webhooks' => [],
- 'keys' => [],
- 'platforms' => [
- [
- '$collection' => ID::custom('platforms'),
- 'name' => 'Localhost',
- 'type' => Origin::CLIENT_TYPE_WEB,
- 'hostname' => 'localhost',
- ], // Current host is added on app init
- ],
- 'legalName' => '',
- 'legalCountry' => '',
- 'legalState' => '',
- 'legalCity' => '',
- 'legalAddress' => '',
- 'legalTaxId' => '',
- 'auths' => [
- 'mockNumbers' => [],
- 'invites' => System::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled',
- 'limit' => (System::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user
- 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds
- 'sessionAlerts' => System::getEnv('_APP_CONSOLE_SESSION_ALERTS', 'disabled') === 'enabled'
- ],
- 'authWhitelistEmails' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [],
- 'authWhitelistIPs' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [],
- 'oAuthProviders' => [
- 'githubEnabled' => true,
- 'githubSecret' => System::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''),
- 'githubAppid' => System::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '')
- ],
- ]);
-}, []);
-
-App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project) {
- if ($project->isEmpty() || $project->getId() === 'console') {
- return $dbForPlatform;
- }
-
- try {
- $dsn = new DSN($project->getAttribute('database'));
- } catch (\InvalidArgumentException) {
- // TODO: Temporary until all projects are using shared tables
- $dsn = new DSN('mysql://' . $project->getAttribute('database'));
- }
-
- $dbAdapter = $pools
- ->get($dsn->getHost())
- ->pop()
- ->getResource();
-
- $database = new Database($dbAdapter, $cache);
-
- $database
- ->setMetadata('host', \gethostname())
- ->setMetadata('project', $project->getId())
- ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS)
- ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
-
- $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
-
- if (\in_array($dsn->getHost(), $sharedTables)) {
- $database
- ->setSharedTables(true)
- ->setTenant($project->getInternalId())
- ->setNamespace($dsn->getParam('namespace'));
- } else {
- $database
- ->setSharedTables(false)
- ->setTenant(null)
- ->setNamespace('_' . $project->getInternalId());
- }
-
- return $database;
-}, ['pools', 'dbForPlatform', 'cache', 'project']);
-
-App::setResource('dbForPlatform', function (Group $pools, Cache $cache) {
- $dbAdapter = $pools
- ->get('console')
- ->pop()
- ->getResource();
-
- $database = new Database($dbAdapter, $cache);
-
- $database
- ->setNamespace('_console')
- ->setMetadata('host', \gethostname())
- ->setMetadata('project', 'console')
- ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS)
- ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
-
- return $database;
-}, ['pools', 'cache']);
-
-App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
- $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
-
- return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) {
- if ($project->isEmpty() || $project->getId() === 'console') {
- return $dbForPlatform;
- }
-
- try {
- $dsn = new DSN($project->getAttribute('database'));
- } catch (\InvalidArgumentException) {
- // TODO: Temporary until all projects are using shared tables
- $dsn = new DSN('mysql://' . $project->getAttribute('database'));
- }
-
- $configure = (function (Database $database) use ($project, $dsn) {
- $database
- ->setMetadata('host', \gethostname())
- ->setMetadata('project', $project->getId())
- ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS)
- ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
-
- $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
-
- if (\in_array($dsn->getHost(), $sharedTables)) {
- $database
- ->setSharedTables(true)
- ->setTenant($project->getInternalId())
- ->setNamespace($dsn->getParam('namespace'));
- } else {
- $database
- ->setSharedTables(false)
- ->setTenant(null)
- ->setNamespace('_' . $project->getInternalId());
- }
- });
-
- if (isset($databases[$dsn->getHost()])) {
- $database = $databases[$dsn->getHost()];
- $configure($database);
- return $database;
- }
-
- $dbAdapter = $pools
- ->get($dsn->getHost())
- ->pop()
- ->getResource();
-
- $database = new Database($dbAdapter, $cache);
- $databases[$dsn->getHost()] = $database;
- $configure($database);
-
- return $database;
- };
-}, ['pools', 'dbForPlatform', 'cache']);
-
-App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
- $database = null;
- return function (?Document $project = null) use ($pools, $cache, $database) {
- if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
- $database->setTenant($project->getInternalId());
- return $database;
- }
-
- $dbAdapter = $pools
- ->get('logs')
- ->pop()
- ->getResource();
-
- $database = new Database(
- $dbAdapter,
- $cache
- );
-
- $database
- ->setSharedTables(true)
- ->setNamespace('logsV1')
- ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS)
- ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
-
- // set tenant
- if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
- $database->setTenant($project->getInternalId());
- }
-
- return $database;
- };
-}, ['pools', 'cache']);
-
-App::setResource('cache', function (Group $pools) {
- $list = Config::getParam('pools-cache', []);
- $adapters = [];
-
- foreach ($list as $value) {
- $adapters[] = $pools
- ->get($value)
- ->pop()
- ->getResource()
- ;
- }
-
- return new Cache(new Sharding($adapters));
-}, ['pools']);
-
-App::setResource('redis', function () {
- $host = System::getEnv('_APP_REDIS_HOST', 'localhost');
- $port = System::getEnv('_APP_REDIS_PORT', 6379);
- $pass = System::getEnv('_APP_REDIS_PASS', '');
-
- $redis = new \Redis();
- @$redis->pconnect($host, (int)$port);
- if ($pass) {
- $redis->auth($pass);
- }
- $redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
-
- return $redis;
-});
-
-App::setResource('timelimit', function (\Redis $redis) {
- return function (string $key, int $limit, int $time) use ($redis) {
- return new TimeLimitRedis($key, $limit, $time, $redis);
- };
-}, ['redis']);
-
-App::setResource('deviceForLocal', function () {
- return new Local();
-});
-
-App::setResource('deviceForFiles', function ($project) {
- return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
-}, ['project']);
-
-App::setResource('deviceForFunctions', function ($project) {
- return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
-}, ['project']);
-
-App::setResource('deviceForBuilds', function ($project) {
- return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
-}, ['project']);
-
-function getDevice(string $root, string $connection = ''): Device
-{
- $connection = !empty($connection) ? $connection : System::getEnv('_APP_CONNECTIONS_STORAGE', '');
-
- if (!empty($connection)) {
- $acl = 'private';
- $device = Storage::DEVICE_LOCAL;
- $accessKey = '';
- $accessSecret = '';
- $bucket = '';
- $region = '';
- $url = App::getEnv('_APP_STORAGE_S3_ENDPOINT', '');
-
- try {
- $dsn = new DSN($connection);
- $device = $dsn->getScheme();
- $accessKey = $dsn->getUser() ?? '';
- $accessSecret = $dsn->getPassword() ?? '';
- $bucket = $dsn->getPath() ?? '';
- $region = $dsn->getParam('region');
- } catch (\Throwable $e) {
- Console::warning($e->getMessage() . 'Invalid DSN. Defaulting to Local device.');
- }
-
- switch ($device) {
- case Storage::DEVICE_S3:
- return new S3($root, $accessKey, $accessSecret, $bucket, $region, $acl, $url);
- case STORAGE::DEVICE_DO_SPACES:
- $device = new DOSpaces($root, $accessKey, $accessSecret, $bucket, $region, $acl);
- $device->setHttpVersion(S3::HTTP_VERSION_1_1);
- return $device;
- case Storage::DEVICE_BACKBLAZE:
- return new Backblaze($root, $accessKey, $accessSecret, $bucket, $region, $acl);
- case Storage::DEVICE_LINODE:
- return new Linode($root, $accessKey, $accessSecret, $bucket, $region, $acl);
- case Storage::DEVICE_WASABI:
- return new Wasabi($root, $accessKey, $accessSecret, $bucket, $region, $acl);
- case Storage::DEVICE_LOCAL:
- default:
- return new Local($root);
- }
- } else {
- switch (strtolower(System::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) ?? '')) {
- case Storage::DEVICE_LOCAL:
- default:
- return new Local($root);
- case Storage::DEVICE_S3:
- $s3AccessKey = System::getEnv('_APP_STORAGE_S3_ACCESS_KEY', '');
- $s3SecretKey = System::getEnv('_APP_STORAGE_S3_SECRET', '');
- $s3Region = System::getEnv('_APP_STORAGE_S3_REGION', '');
- $s3Bucket = System::getEnv('_APP_STORAGE_S3_BUCKET', '');
- $s3Acl = 'private';
- $s3EndpointUrl = App::getEnv('_APP_STORAGE_S3_ENDPOINT', '');
- return new S3($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl, $s3EndpointUrl);
- case Storage::DEVICE_DO_SPACES:
- $doSpacesAccessKey = System::getEnv('_APP_STORAGE_DO_SPACES_ACCESS_KEY', '');
- $doSpacesSecretKey = System::getEnv('_APP_STORAGE_DO_SPACES_SECRET', '');
- $doSpacesRegion = System::getEnv('_APP_STORAGE_DO_SPACES_REGION', '');
- $doSpacesBucket = System::getEnv('_APP_STORAGE_DO_SPACES_BUCKET', '');
- $doSpacesAcl = 'private';
- $device = new DOSpaces($root, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl);
- $device->setHttpVersion(S3::HTTP_VERSION_1_1);
- return $device;
- case Storage::DEVICE_BACKBLAZE:
- $backblazeAccessKey = System::getEnv('_APP_STORAGE_BACKBLAZE_ACCESS_KEY', '');
- $backblazeSecretKey = System::getEnv('_APP_STORAGE_BACKBLAZE_SECRET', '');
- $backblazeRegion = System::getEnv('_APP_STORAGE_BACKBLAZE_REGION', '');
- $backblazeBucket = System::getEnv('_APP_STORAGE_BACKBLAZE_BUCKET', '');
- $backblazeAcl = 'private';
- return new Backblaze($root, $backblazeAccessKey, $backblazeSecretKey, $backblazeBucket, $backblazeRegion, $backblazeAcl);
- case Storage::DEVICE_LINODE:
- $linodeAccessKey = System::getEnv('_APP_STORAGE_LINODE_ACCESS_KEY', '');
- $linodeSecretKey = System::getEnv('_APP_STORAGE_LINODE_SECRET', '');
- $linodeRegion = System::getEnv('_APP_STORAGE_LINODE_REGION', '');
- $linodeBucket = System::getEnv('_APP_STORAGE_LINODE_BUCKET', '');
- $linodeAcl = 'private';
- return new Linode($root, $linodeAccessKey, $linodeSecretKey, $linodeBucket, $linodeRegion, $linodeAcl);
- case Storage::DEVICE_WASABI:
- $wasabiAccessKey = System::getEnv('_APP_STORAGE_WASABI_ACCESS_KEY', '');
- $wasabiSecretKey = System::getEnv('_APP_STORAGE_WASABI_SECRET', '');
- $wasabiRegion = System::getEnv('_APP_STORAGE_WASABI_REGION', '');
- $wasabiBucket = System::getEnv('_APP_STORAGE_WASABI_BUCKET', '');
- $wasabiAcl = 'private';
- return new Wasabi($root, $wasabiAccessKey, $wasabiSecretKey, $wasabiBucket, $wasabiRegion, $wasabiAcl);
- }
- }
-}
-
-App::setResource('mode', function ($request) {
- /** @var Appwrite\Utopia\Request $request */
-
- /**
- * Defines the mode for the request:
- * - 'default' => Requests for Client and Server Side
- * - 'admin' => Request from the Console on non-console projects
- */
- return $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT));
-}, ['request']);
-
-App::setResource('geodb', function ($register) {
- /** @var Utopia\Registry\Registry $register */
- return $register->get('geodb');
-}, ['register']);
-
-App::setResource('passwordsDictionary', function ($register) {
- /** @var Utopia\Registry\Registry $register */
- return $register->get('passwordsDictionary');
-}, ['register']);
-
-
-App::setResource('servers', function () {
- $platforms = Config::getParam('platforms');
- $server = $platforms[APP_PLATFORM_SERVER];
-
- $languages = array_map(function ($language) {
- return strtolower($language['name']);
- }, $server['sdks']);
-
- return $languages;
-});
-
-App::setResource('promiseAdapter', function ($register) {
- return $register->get('promiseAdapter');
-}, ['register']);
-
-App::setResource('schema', function ($utopia, $dbForProject) {
-
- $complexity = function (int $complexity, array $args) {
- $queries = Query::parseQueries($args['queries'] ?? []);
- $query = Query::getByType($queries, [Query::TYPE_LIMIT])[0] ?? null;
- $limit = $query ? $query->getValue() : APP_LIMIT_LIST_DEFAULT;
-
- return $complexity * $limit;
- };
-
- $attributes = function (int $limit, int $offset) use ($dbForProject) {
- $attrs = Authorization::skip(fn () => $dbForProject->find('attributes', [
- Query::limit($limit),
- Query::offset($offset),
- ]));
-
- return \array_map(function ($attr) {
- return $attr->getArrayCopy();
- }, $attrs);
- };
-
- $urls = [
- 'list' => function (string $databaseId, string $collectionId, array $args) {
- return "/v1/databases/$databaseId/collections/$collectionId/documents";
- },
- 'create' => function (string $databaseId, string $collectionId, array $args) {
- return "/v1/databases/$databaseId/collections/$collectionId/documents";
- },
- 'read' => function (string $databaseId, string $collectionId, array $args) {
- return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
- },
- 'update' => function (string $databaseId, string $collectionId, array $args) {
- return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
- },
- 'delete' => function (string $databaseId, string $collectionId, array $args) {
- return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
- },
- ];
-
- $params = [
- 'list' => function (string $databaseId, string $collectionId, array $args) {
- return [ 'queries' => $args['queries']];
- },
- 'create' => function (string $databaseId, string $collectionId, array $args) {
- $id = $args['id'] ?? 'unique()';
- $permissions = $args['permissions'] ?? null;
-
- unset($args['id']);
- unset($args['permissions']);
-
- // Order must be the same as the route params
- return [
- 'databaseId' => $databaseId,
- 'documentId' => $id,
- 'collectionId' => $collectionId,
- 'data' => $args,
- 'permissions' => $permissions,
- ];
- },
- 'update' => function (string $databaseId, string $collectionId, array $args) {
- $documentId = $args['id'];
- $permissions = $args['permissions'] ?? null;
-
- unset($args['id']);
- unset($args['permissions']);
-
- // Order must be the same as the route params
- return [
- 'databaseId' => $databaseId,
- 'collectionId' => $collectionId,
- 'documentId' => $documentId,
- 'data' => $args,
- 'permissions' => $permissions,
- ];
- },
- ];
-
- return Schema::build(
- $utopia,
- $complexity,
- $attributes,
- $urls,
- $params,
- );
-}, ['utopia', 'dbForProject']);
-
-App::setResource('contributors', function () {
- $path = 'app/config/contributors.json';
- $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
- return $list;
-});
-
-App::setResource('employees', function () {
- $path = 'app/config/employees.json';
- $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
- return $list;
-});
-
-App::setResource('heroes', function () {
- $path = 'app/config/heroes.json';
- $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
- return $list;
-});
-
-App::setResource('gitHub', function (Cache $cache) {
- return new VcsGitHub($cache);
-}, ['cache']);
-
-App::setResource('requestTimestamp', function ($request) {
- //TODO: Move this to the Request class itself
- $timestampHeader = $request->getHeader('x-appwrite-timestamp');
- $requestTimestamp = null;
- if (!empty($timestampHeader)) {
- try {
- $requestTimestamp = new \DateTime($timestampHeader);
- } catch (\Throwable $e) {
- throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid X-Appwrite-Timestamp header value');
- }
- }
- return $requestTimestamp;
-}, ['request']);
-
-App::setResource('plan', function (array $plan = []) {
- return [];
-});
-
-App::setResource('smsRates', function () {
- return [];
-});
-
-App::setResource('team', function (Document $project, Database $dbForPlatform, App $utopia, Request $request) {
- $teamInternalId = '';
- if ($project->getId() !== 'console') {
- $teamInternalId = $project->getAttribute('teamInternalId', '');
- } else {
- $route = $utopia->match($request);
- $path = $route->getPath();
- if (str_starts_with($path, '/v1/projects/:projectId')) {
- $uri = $request->getURI();
- $pid = explode('/', $uri)[3];
- $p = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $pid));
- $teamInternalId = $p->getAttribute('teamInternalId', '');
- } elseif ($path === '/v1/projects') {
- $teamId = $request->getParam('teamId', '');
- $team = Authorization::skip(fn () => $dbForPlatform->getDocument('teams', $teamId));
- return $team;
- }
- }
-
- $team = Authorization::skip(function () use ($dbForPlatform, $teamInternalId) {
- return $dbForPlatform->findOne('teams', [
- Query::equal('$internalId', [$teamInternalId]),
- ]);
- });
-
- return $team;
-}, ['project', 'dbForPlatform', 'utopia', 'request']);
-
-App::setResource(
- 'isResourceBlocked',
- fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
-);
-
-App::setResource('previewHostname', function (Request $request) {
- if (App::isDevelopment()) {
- $host = $request->getQuery('appwrite-hostname') ?? '';
- if (!empty($host)) {
- return $host;
- }
- }
-
- return '';
-}, ['request']);
-
-App::setResource('apiKey', function (Request $request, Document $project): ?Key {
- $key = $request->getHeader('x-appwrite-key');
-
- if (empty($key)) {
- return null;
- }
-
- return Key::decode($project, $key);
-}, ['request', 'project']);
diff --git a/app/init/configs.php b/app/init/configs.php
new file mode 100644
index 0000000000..1a5dbced1b
--- /dev/null
+++ b/app/init/configs.php
@@ -0,0 +1,37 @@
+ $value], JSON_PRESERVE_ZERO_FRACTION);
+ },
+ function (mixed $value) {
+ if (is_null($value)) {
+ return;
+ }
+
+ return json_decode($value, true)['value'];
+ }
+);
+
+Database::addFilter(
+ 'enum',
+ function (mixed $value, Document $attribute) {
+ if ($attribute->isSet('elements')) {
+ $attribute->removeAttribute('elements');
+ }
+
+ return $value;
+ },
+ function (mixed $value, Document $attribute) {
+ $formatOptions = \json_decode($attribute->getAttribute('formatOptions', '[]'), true);
+ if (isset($formatOptions['elements'])) {
+ $attribute->setAttribute('elements', $formatOptions['elements']);
+ }
+
+ return $value;
+ }
+);
+
+Database::addFilter(
+ 'range',
+ function (mixed $value, Document $attribute) {
+ if ($attribute->isSet('min')) {
+ $attribute->removeAttribute('min');
+ }
+ if ($attribute->isSet('max')) {
+ $attribute->removeAttribute('max');
+ }
+
+ return $value;
+ },
+ function (mixed $value, Document $attribute) {
+ $formatOptions = json_decode($attribute->getAttribute('formatOptions', '[]'), true);
+ if (isset($formatOptions['min']) || isset($formatOptions['max'])) {
+ $attribute
+ ->setAttribute('min', $formatOptions['min'])
+ ->setAttribute('max', $formatOptions['max']);
+ }
+
+ return $value;
+ }
+);
+
+Database::addFilter(
+ 'subQueryAttributes',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ $attributes = $database->find('attributes', [
+ Query::equal('collectionInternalId', [$document->getInternalId()]),
+ Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
+ Query::limit($database->getLimitForAttributes()),
+ ]);
+
+ foreach ($attributes as $attribute) {
+ if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) {
+ $options = $attribute->getAttribute('options');
+ foreach ($options as $key => $value) {
+ $attribute->setAttribute($key, $value);
+ }
+ $attribute->removeAttribute('options');
+ }
+ }
+
+ return $attributes;
+ }
+);
+
+Database::addFilter(
+ 'subQueryIndexes',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return $database
+ ->find('indexes', [
+ Query::equal('collectionInternalId', [$document->getInternalId()]),
+ Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
+ Query::limit($database->getLimitForIndexes()),
+ ]);
+ }
+);
+
+Database::addFilter(
+ 'subQueryPlatforms',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return $database
+ ->find('platforms', [
+ Query::equal('projectInternalId', [$document->getInternalId()]),
+ Query::limit(APP_LIMIT_SUBQUERY),
+ ]);
+ }
+);
+
+Database::addFilter(
+ 'subQueryKeys',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return $database
+ ->find('keys', [
+ Query::equal('projectInternalId', [$document->getInternalId()]),
+ Query::limit(APP_LIMIT_SUBQUERY),
+ ]);
+ }
+);
+
+Database::addFilter(
+ 'subQueryWebhooks',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return $database
+ ->find('webhooks', [
+ Query::equal('projectInternalId', [$document->getInternalId()]),
+ Query::limit(APP_LIMIT_SUBQUERY),
+ ]);
+ }
+);
+
+Database::addFilter(
+ 'subQuerySessions',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return Authorization::skip(fn () => $database->find('sessions', [
+ Query::equal('userInternalId', [$document->getInternalId()]),
+ Query::limit(APP_LIMIT_SUBQUERY),
+ ]));
+ }
+);
+
+Database::addFilter(
+ 'subQueryTokens',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return Authorization::skip(fn () => $database
+ ->find('tokens', [
+ Query::equal('userInternalId', [$document->getInternalId()]),
+ Query::limit(APP_LIMIT_SUBQUERY),
+ ]));
+ }
+);
+
+Database::addFilter(
+ 'subQueryChallenges',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return Authorization::skip(fn () => $database
+ ->find('challenges', [
+ Query::equal('userInternalId', [$document->getInternalId()]),
+ Query::limit(APP_LIMIT_SUBQUERY),
+ ]));
+ }
+);
+
+Database::addFilter(
+ 'subQueryAuthenticators',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return Authorization::skip(fn () => $database
+ ->find('authenticators', [
+ Query::equal('userInternalId', [$document->getInternalId()]),
+ Query::limit(APP_LIMIT_SUBQUERY),
+ ]));
+ }
+);
+
+Database::addFilter(
+ 'subQueryMemberships',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return Authorization::skip(fn () => $database
+ ->find('memberships', [
+ Query::equal('userInternalId', [$document->getInternalId()]),
+ Query::limit(APP_LIMIT_SUBQUERY),
+ ]));
+ }
+);
+
+Database::addFilter(
+ 'subQueryVariables',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return $database
+ ->find('variables', [
+ Query::equal('resourceInternalId', [$document->getInternalId()]),
+ Query::equal('resourceType', ['function']),
+ Query::limit(APP_LIMIT_SUBQUERY),
+ ]);
+ }
+);
+
+Database::addFilter(
+ 'encrypt',
+ function (mixed $value) {
+ $key = System::getEnv('_APP_OPENSSL_KEY_V1');
+ $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
+ $tag = null;
+
+ return json_encode([
+ 'data' => OpenSSL::encrypt($value, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
+ 'method' => OpenSSL::CIPHER_AES_128_GCM,
+ 'iv' => \bin2hex($iv),
+ 'tag' => \bin2hex($tag ?? ''),
+ 'version' => '1',
+ ]);
+ },
+ function (mixed $value) {
+ if (is_null($value)) {
+ return;
+ }
+ $value = json_decode($value, true);
+ $key = System::getEnv('_APP_OPENSSL_KEY_V' . $value['version']);
+
+ return OpenSSL::decrypt($value['data'], $value['method'], $key, 0, hex2bin($value['iv']), hex2bin($value['tag']));
+ }
+);
+
+Database::addFilter(
+ 'subQueryProjectVariables',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return $database
+ ->find('variables', [
+ Query::equal('resourceType', ['project']),
+ Query::limit(APP_LIMIT_SUBQUERY)
+ ]);
+ }
+);
+
+Database::addFilter(
+ 'userSearch',
+ function (mixed $value, Document $user) {
+ $searchValues = [
+ $user->getId(),
+ $user->getAttribute('email', ''),
+ $user->getAttribute('name', ''),
+ $user->getAttribute('phone', '')
+ ];
+
+ foreach ($user->getAttribute('labels', []) as $label) {
+ $searchValues[] = 'label:' . $label;
+ }
+
+ $search = implode(' ', \array_filter($searchValues));
+
+ return $search;
+ },
+ function (mixed $value) {
+ return $value;
+ }
+);
+
+Database::addFilter(
+ 'subQueryTargets',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return Authorization::skip(fn () => $database
+ ->find('targets', [
+ Query::equal('userInternalId', [$document->getInternalId()]),
+ Query::limit(APP_LIMIT_SUBQUERY)
+ ]));
+ }
+);
+
+Database::addFilter(
+ 'subQueryTopicTargets',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ $targetIds = Authorization::skip(fn () => \array_map(
+ fn ($document) => $document->getAttribute('targetInternalId'),
+ $database->find('subscribers', [
+ Query::equal('topicInternalId', [$document->getInternalId()]),
+ Query::limit(APP_LIMIT_SUBSCRIBERS_SUBQUERY)
+ ])
+ ));
+ if (\count($targetIds) > 0) {
+ return $database->skipValidation(fn () => $database->find('targets', [
+ Query::equal('$internalId', $targetIds)
+ ]));
+ }
+ return [];
+ }
+);
+
+Database::addFilter(
+ 'providerSearch',
+ function (mixed $value, Document $provider) {
+ $searchValues = [
+ $provider->getId(),
+ $provider->getAttribute('name', ''),
+ $provider->getAttribute('provider', ''),
+ $provider->getAttribute('type', '')
+ ];
+
+ $search = \implode(' ', \array_filter($searchValues));
+
+ return $search;
+ },
+ function (mixed $value) {
+ return $value;
+ }
+);
+
+Database::addFilter(
+ 'topicSearch',
+ function (mixed $value, Document $topic) {
+ $searchValues = [
+ $topic->getId(),
+ $topic->getAttribute('name', ''),
+ $topic->getAttribute('description', ''),
+ ];
+
+ $search = \implode(' ', \array_filter($searchValues));
+
+ return $search;
+ },
+ function (mixed $value) {
+ return $value;
+ }
+);
+
+Database::addFilter(
+ 'messageSearch',
+ function (mixed $value, Document $message) {
+ $searchValues = [
+ $message->getId(),
+ $message->getAttribute('description', ''),
+ $message->getAttribute('status', ''),
+ ];
+
+ $data = \json_decode($message->getAttribute('data', []), true);
+ $providerType = $message->getAttribute('providerType', '');
+
+ switch ($providerType) {
+ case MESSAGE_TYPE_EMAIL:
+ $searchValues[] = $data['subject'];
+ $searchValues[] = MESSAGE_TYPE_EMAIL;
+ break;
+ case MESSAGE_TYPE_SMS:
+ $searchValues[] = $data['content'];
+ $searchValues[] = MESSAGE_TYPE_SMS;
+ break;
+ case MESSAGE_TYPE_PUSH:
+ $searchValues[] = $data['title'] ?? '';
+ $searchValues[] = MESSAGE_TYPE_PUSH;
+ break;
+ }
+
+ $search = \implode(' ', \array_filter($searchValues));
+
+ return $search;
+ },
+ function (mixed $value) {
+ return $value;
+ }
+);
diff --git a/app/init/database/formats.php b/app/init/database/formats.php
new file mode 100644
index 0000000000..6c73877576
--- /dev/null
+++ b/app/init/database/formats.php
@@ -0,0 +1,43 @@
+set('logger', function () {
+ // Register error logger
+ $providerName = System::getEnv('_APP_LOGGING_PROVIDER', '');
+ $providerConfig = System::getEnv('_APP_LOGGING_CONFIG', '');
+
+ if (empty($providerConfig)) {
+ return;
+ }
+
+ try {
+ $loggingProvider = new DSN($providerConfig ?? '');
+
+ $providerName = $loggingProvider->getScheme();
+ $providerConfig = match ($providerName) {
+ 'sentry' => ['key' => $loggingProvider->getPassword(), 'projectId' => $loggingProvider->getUser() ?? '', 'host' => 'https://' . $loggingProvider->getHost()],
+ 'logowl' => ['ticket' => $loggingProvider->getUser() ?? '', 'host' => $loggingProvider->getHost()],
+ default => ['key' => $loggingProvider->getHost()],
+ };
+ } catch (Throwable $th) {
+ // Fallback for older Appwrite versions up to 1.5.x that use _APP_LOGGING_PROVIDER and _APP_LOGGING_CONFIG environment variables
+ Console::warning('Using deprecated logging configuration. Please update your configuration to use DSN format.' . $th->getMessage());
+ $configChunks = \explode(";", $providerConfig);
+
+ $providerConfig = match ($providerName) {
+ 'sentry' => [ 'key' => $configChunks[0], 'projectId' => $configChunks[1] ?? '', 'host' => '',],
+ 'logowl' => ['ticket' => $configChunks[0] ?? '', 'host' => ''],
+ default => ['key' => $providerConfig],
+ };
+ }
+
+ if (empty($providerName) || empty($providerConfig)) {
+ return;
+ }
+
+ if (!Logger::hasProvider($providerName)) {
+ throw new Exception(Exception::GENERAL_SERVER_ERROR, "Logging provider not supported. Logging is disabled");
+ }
+
+ try {
+ $adapter = match ($providerName) {
+ 'sentry' => new Sentry($providerConfig['projectId'], $providerConfig['key'], $providerConfig['host']),
+ 'logowl' => new LogOwl($providerConfig['ticket'], $providerConfig['host']),
+ 'raygun' => new Raygun($providerConfig['key']),
+ 'appsignal' => new AppSignal($providerConfig['key']),
+ default => null
+ };
+ } catch (Throwable $th) {
+ $adapter = null;
+ }
+
+ if ($adapter === null) {
+ Console::error("Logging provider not supported. Logging is disabled");
+ return;
+ }
+
+ return new Logger($adapter);
+});
+
+$register->set('pools', function () {
+ $group = new Group();
+
+ $fallbackForDB = 'db_main=' . AppwriteURL::unparse([
+ 'scheme' => 'mariadb',
+ 'host' => System::getEnv('_APP_DB_HOST', 'mariadb'),
+ 'port' => System::getEnv('_APP_DB_PORT', '3306'),
+ 'user' => System::getEnv('_APP_DB_USER', ''),
+ 'pass' => System::getEnv('_APP_DB_PASS', ''),
+ 'path' => System::getEnv('_APP_DB_SCHEMA', ''),
+ ]);
+ $fallbackForRedis = 'redis_main=' . AppwriteURL::unparse([
+ 'scheme' => 'redis',
+ 'host' => System::getEnv('_APP_REDIS_HOST', 'redis'),
+ 'port' => System::getEnv('_APP_REDIS_PORT', '6379'),
+ 'user' => System::getEnv('_APP_REDIS_USER', ''),
+ 'pass' => System::getEnv('_APP_REDIS_PASS', ''),
+ ]);
+
+ $connections = [
+ 'console' => [
+ 'type' => 'database',
+ 'dsns' => $fallbackForDB,
+ 'multiple' => false,
+ 'schemes' => ['mariadb', 'mysql'],
+ ],
+ 'database' => [
+ 'type' => 'database',
+ 'dsns' => $fallbackForDB,
+ 'multiple' => true,
+ 'schemes' => ['mariadb', 'mysql'],
+ ],
+ 'logs' => [
+ 'type' => 'database',
+ 'dsns' => System::getEnv('_APP_CONNECTIONS_DB_LOGS', $fallbackForDB),
+ 'multiple' => false,
+ 'schemes' => ['mariadb', 'mysql'],
+ ],
+ 'publisher' => [
+ 'type' => 'publisher',
+ 'dsns' => $fallbackForRedis,
+ 'multiple' => false,
+ 'schemes' => ['redis'],
+ ],
+ 'consumer' => [
+ 'type' => 'consumer',
+ 'dsns' => $fallbackForRedis,
+ 'multiple' => false,
+ 'schemes' => ['redis'],
+ ],
+ 'pubsub' => [
+ 'type' => 'pubsub',
+ 'dsns' => $fallbackForRedis,
+ 'multiple' => false,
+ 'schemes' => ['redis'],
+ ],
+ 'cache' => [
+ 'type' => 'cache',
+ 'dsns' => $fallbackForRedis,
+ 'multiple' => true,
+ 'schemes' => ['redis'],
+ ],
+ ];
+
+ $maxConnections = System::getEnv('_APP_CONNECTIONS_MAX', 151);
+ $instanceConnections = $maxConnections / System::getEnv('_APP_POOL_CLIENTS', 14);
+
+ $multiprocessing = System::getEnv('_APP_SERVER_MULTIPROCESS', 'disabled') === 'enabled';
+
+ if ($multiprocessing) {
+ $workerCount = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
+ } else {
+ $workerCount = 1;
+ }
+
+ if ($workerCount > $instanceConnections) {
+ throw new \Exception('Pool size is too small. Increase the number of allowed database connections or decrease the number of workers.', 500);
+ }
+
+ $poolSize = (int)($instanceConnections / $workerCount);
+
+ foreach ($connections as $key => $connection) {
+ $type = $connection['type'] ?? '';
+ $multiple = $connection['multiple'] ?? false;
+ $schemes = $connection['schemes'] ?? [];
+ $config = [];
+ $dsns = explode(',', $connection['dsns'] ?? '');
+ foreach ($dsns as &$dsn) {
+ $dsn = explode('=', $dsn);
+ $name = ($multiple) ? $key . '_' . $dsn[0] : $key;
+ $dsn = $dsn[1] ?? '';
+ $config[] = $name;
+ if (empty($dsn)) {
+ //throw new Exception(Exception::GENERAL_SERVER_ERROR, "Missing value for DSN connection in {$key}");
+ continue;
+ }
+
+ $dsn = new DSN($dsn);
+ $dsnHost = $dsn->getHost();
+ $dsnPort = $dsn->getPort();
+ $dsnUser = $dsn->getUser();
+ $dsnPass = $dsn->getPassword();
+ $dsnScheme = $dsn->getScheme();
+ $dsnDatabase = $dsn->getPath();
+
+ if (!in_array($dsnScheme, $schemes)) {
+ throw new Exception(Exception::GENERAL_SERVER_ERROR, "Invalid console database scheme");
+ }
+
+ /**
+ * Get Resource
+ *
+ * Creation could be reused across connection types like database, cache, queue, etc.
+ *
+ * Resource assignment to an adapter will happen below.
+ */
+ $resource = match ($dsnScheme) {
+ 'mysql',
+ 'mariadb' => function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
+ return new PDOProxy(function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
+ return new PDO("mysql:host={$dsnHost};port={$dsnPort};dbname={$dsnDatabase};charset=utf8mb4", $dsnUser, $dsnPass, array(
+ PDO::ATTR_TIMEOUT => 3, // Seconds
+ PDO::ATTR_PERSISTENT => false,
+ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
+ PDO::ATTR_EMULATE_PREPARES => true,
+ PDO::ATTR_STRINGIFY_FETCHES => true
+ ));
+ });
+ },
+ 'redis' => function () use ($dsnHost, $dsnPort, $dsnPass) {
+ $redis = new \Redis();
+ @$redis->pconnect($dsnHost, (int)$dsnPort);
+ if ($dsnPass) {
+ $redis->auth($dsnPass);
+ }
+ $redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
+
+ return $redis;
+ },
+ default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Invalid scheme'),
+ };
+
+ $pool = new Pool($name, $poolSize, function () use ($type, $resource, $dsn) {
+ // Get Adapter
+ switch ($type) {
+ case 'database':
+ $adapter = match ($dsn->getScheme()) {
+ 'mariadb' => new MariaDB($resource()),
+ 'mysql' => new MySQL($resource()),
+ default => null
+ };
+
+ $adapter->setDatabase($dsn->getPath());
+ return $adapter;
+ case 'pubsub':
+ return match ($dsn->getScheme()) {
+ 'redis' => new PubSub($resource()),
+ default => null
+ };
+ case 'publisher':
+ case 'consumer':
+ return match ($dsn->getScheme()) {
+ 'redis' => new Queue\Broker\Redis(new Queue\Connection\Redis($dsn->getHost(), $dsn->getPort())),
+ default => null
+ };
+ case 'cache':
+ return match ($dsn->getScheme()) {
+ 'redis' => new RedisCache($resource()),
+ default => null
+ };
+ default:
+ throw new Exception(Exception::GENERAL_SERVER_ERROR, "Server error: Missing adapter implementation.");
+ }
+ });
+
+ $group->add($pool);
+ }
+
+ Config::setParam('pools-' . $key, $config);
+ }
+
+ return $group;
+});
+
+$register->set('db', function () {
+ // This is usually for our workers or CLI commands scope
+ $dbHost = System::getEnv('_APP_DB_HOST', '');
+ $dbPort = System::getEnv('_APP_DB_PORT', '');
+ $dbUser = System::getEnv('_APP_DB_USER', '');
+ $dbPass = System::getEnv('_APP_DB_PASS', '');
+ $dbScheme = System::getEnv('_APP_DB_SCHEMA', '');
+
+ return new PDO(
+ "mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4",
+ $dbUser,
+ $dbPass,
+ SQL::getPDOAttributes()
+ );
+});
+
+$register->set('smtp', function () {
+ $mail = new PHPMailer(true);
+
+ $mail->isSMTP();
+
+ $username = System::getEnv('_APP_SMTP_USERNAME');
+ $password = System::getEnv('_APP_SMTP_PASSWORD');
+
+ $mail->XMailer = 'Appwrite Mailer';
+ $mail->Host = System::getEnv('_APP_SMTP_HOST', 'smtp');
+ $mail->Port = System::getEnv('_APP_SMTP_PORT', 25);
+ $mail->SMTPAuth = !empty($username) && !empty($password);
+ $mail->Username = $username;
+ $mail->Password = $password;
+ $mail->SMTPSecure = System::getEnv('_APP_SMTP_SECURE', '');
+ $mail->SMTPAutoTLS = false;
+ $mail->CharSet = 'UTF-8';
+
+ $from = \urldecode(System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'));
+ $email = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
+
+ $mail->setFrom($email, $from);
+ $mail->addReplyTo($email, $from);
+
+ $mail->isHTML(true);
+
+ return $mail;
+});
+$register->set('geodb', function () {
+ return new Reader(__DIR__ . '/../assets/dbip/dbip-country-lite-2024-09.mmdb');
+});
+$register->set('passwordsDictionary', function () {
+ $content = \file_get_contents(__DIR__ . '/../assets/security/10k-common-passwords');
+ $content = explode("\n", $content);
+ $content = array_flip($content);
+ return $content;
+});
+$register->set('promiseAdapter', function () {
+ return new Swoole();
+});
+$register->set('hooks', function () {
+ return new Hooks();
+});
diff --git a/app/init/resources.php b/app/init/resources.php
new file mode 100644
index 0000000000..7f0d93aeaf
--- /dev/null
+++ b/app/init/resources.php
@@ -0,0 +1,885 @@
+ new Log());
+App::setResource('logger', function ($register) {
+ return $register->get('logger');
+}, ['register']);
+
+App::setResource('hooks', function ($register) {
+ return $register->get('hooks');
+}, ['register']);
+
+App::setResource('register', fn () => $register);
+App::setResource('locale', fn () => new Locale(System::getEnv('_APP_LOCALE', 'en')));
+
+App::setResource('localeCodes', function () {
+ return array_map(fn ($locale) => $locale['code'], Config::getParam('locale-codes', []));
+});
+
+// Queues
+App::setResource('publisher', function (Group $pools) {
+ return $pools->get('publisher')->pop()->getResource();
+}, ['pools']);
+App::setResource('consumer', function (Group $pools) {
+ return $pools->get('consumer')->pop()->getResource();
+}, ['pools']);
+App::setResource('queueForMessaging', function (Publisher $publisher) {
+ return new Messaging($publisher);
+}, ['publisher']);
+App::setResource('queueForMails', function (Publisher $publisher) {
+ return new Mail($publisher);
+}, ['publisher']);
+App::setResource('queueForBuilds', function (Publisher $publisher) {
+ return new Build($publisher);
+}, ['publisher']);
+App::setResource('queueForDatabase', function (Publisher $publisher) {
+ return new EventDatabase($publisher);
+}, ['publisher']);
+App::setResource('queueForDeletes', function (Publisher $publisher) {
+ return new Delete($publisher);
+}, ['publisher']);
+App::setResource('queueForEvents', function (Publisher $publisher) {
+ return new Event($publisher);
+}, ['publisher']);
+App::setResource('queueForWebhooks', function (Publisher $publisher) {
+ return new Webhook($publisher);
+}, ['publisher']);
+App::setResource('queueForRealtime', function () {
+ return new Realtime();
+}, []);
+App::setResource('queueForStatsUsage', function (Publisher $publisher) {
+ return new StatsUsage($publisher);
+}, ['publisher']);
+App::setResource('queueForAudits', function (Publisher $publisher) {
+ return new Audit($publisher);
+}, ['publisher']);
+App::setResource('queueForFunctions', function (Publisher $publisher) {
+ return new Func($publisher);
+}, ['publisher']);
+App::setResource('queueForCertificates', function (Publisher $publisher) {
+ return new Certificate($publisher);
+}, ['publisher']);
+App::setResource('queueForMigrations', function (Publisher $publisher) {
+ return new Migration($publisher);
+}, ['publisher']);
+App::setResource('clients', function ($request, $console, $project) {
+ $console->setAttribute('platforms', [ // Always allow current host
+ '$collection' => ID::custom('platforms'),
+ 'name' => 'Current Host',
+ 'type' => Origin::CLIENT_TYPE_WEB,
+ 'hostname' => $request->getHostname(),
+ ], Document::SET_TYPE_APPEND);
+
+ $hostnames = explode(',', System::getEnv('_APP_CONSOLE_HOSTNAMES', ''));
+ $validator = new Hostname();
+ foreach ($hostnames as $hostname) {
+ $hostname = trim($hostname);
+ if (!$validator->isValid($hostname)) {
+ continue;
+ }
+ $console->setAttribute('platforms', [
+ '$collection' => ID::custom('platforms'),
+ 'type' => Origin::CLIENT_TYPE_WEB,
+ 'name' => $hostname,
+ 'hostname' => $hostname,
+ ], Document::SET_TYPE_APPEND);
+ }
+
+ /**
+ * Get All verified client URLs for both console and current projects
+ * + Filter for duplicated entries
+ */
+ $clientsConsole = \array_map(
+ fn ($node) => $node['hostname'],
+ \array_filter(
+ $console->getAttribute('platforms', []),
+ fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB) && !empty($node['hostname']))
+ )
+ );
+
+ $clients = $clientsConsole;
+ $platforms = $project->getAttribute('platforms', []);
+
+ foreach ($platforms as $node) {
+ if (
+ isset($node['type']) &&
+ ($node['type'] === Origin::CLIENT_TYPE_WEB ||
+ $node['type'] === Origin::CLIENT_TYPE_FLUTTER_WEB) &&
+ !empty($node['hostname'])
+ ) {
+ $clients[] = $node['hostname'];
+ }
+ }
+
+ return \array_unique($clients);
+}, ['request', 'console', 'project']);
+
+App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform) {
+ /** @var Appwrite\Utopia\Request $request */
+ /** @var Appwrite\Utopia\Response $response */
+ /** @var Utopia\Database\Document $project */
+ /** @var Utopia\Database\Database $dbForProject */
+ /** @var Utopia\Database\Database $dbForPlatform */
+ /** @var string $mode */
+
+ Authorization::setDefaultStatus(true);
+
+ Auth::setCookieName('a_session_' . $project->getId());
+
+ if (APP_MODE_ADMIN === $mode) {
+ Auth::setCookieName('a_session_' . $console->getId());
+ }
+
+ $session = Auth::decodeSession(
+ $request->getCookie(
+ Auth::$cookieName, // Get sessions
+ $request->getCookie(Auth::$cookieName . '_legacy', '')
+ )
+ );
+
+ // Get session from header for SSR clients
+ if (empty($session['id']) && empty($session['secret'])) {
+ $sessionHeader = $request->getHeader('x-appwrite-session', '');
+
+ if (!empty($sessionHeader)) {
+ $session = Auth::decodeSession($sessionHeader);
+ }
+ }
+
+ // Get fallback session from old clients (no SameSite support) or clients who block 3rd-party cookies
+ if ($response) {
+ $response->addHeader('X-Debug-Fallback', 'false');
+ }
+
+ if (empty($session['id']) && empty($session['secret'])) {
+ if ($response) {
+ $response->addHeader('X-Debug-Fallback', 'true');
+ }
+ $fallback = $request->getHeader('x-fallback-cookies', '');
+ $fallback = \json_decode($fallback, true);
+ $session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : ''));
+ }
+
+ Auth::$unique = $session['id'] ?? '';
+ Auth::$secret = $session['secret'] ?? '';
+
+ if (APP_MODE_ADMIN !== $mode) {
+ if ($project->isEmpty()) {
+ $user = new Document([]);
+ } else {
+ if ($project->getId() === 'console') {
+ $user = $dbForPlatform->getDocument('users', Auth::$unique);
+ } else {
+ $user = $dbForProject->getDocument('users', Auth::$unique);
+ }
+ }
+ } else {
+ $user = $dbForPlatform->getDocument('users', Auth::$unique);
+ }
+
+ if (
+ $user->isEmpty() // Check a document has been found in the DB
+ || !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret)
+ ) { // Validate user has valid login token
+ $user = new Document([]);
+ }
+
+ // if (APP_MODE_ADMIN === $mode) {
+ // if ($user->find('teamInternalId', $project->getAttribute('teamInternalId'), 'memberships')) {
+ // Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
+ // } else {
+ // $user = new Document([]);
+ // }
+ // }
+
+ $authJWT = $request->getHeader('x-appwrite-jwt', '');
+
+ if (!empty($authJWT) && !$project->isEmpty()) { // JWT authentication
+ $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
+
+ try {
+ $payload = $jwt->decode($authJWT);
+ } catch (JWTException $error) {
+ throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage());
+ }
+
+ $jwtUserId = $payload['userId'] ?? '';
+ if (!empty($jwtUserId)) {
+ $user = $dbForProject->getDocument('users', $jwtUserId);
+ }
+
+ $jwtSessionId = $payload['sessionId'] ?? '';
+ if (!empty($jwtSessionId)) {
+ if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token
+ $user = new Document([]);
+ }
+ }
+ }
+
+ $dbForProject->setMetadata('user', $user->getId());
+ $dbForPlatform->setMetadata('user', $user->getId());
+
+ return $user;
+}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform']);
+
+App::setResource('project', function ($dbForPlatform, $request, $console) {
+ /** @var Appwrite\Utopia\Request $request */
+ /** @var Utopia\Database\Database $dbForPlatform */
+ /** @var Utopia\Database\Document $console */
+
+ $projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', ''));
+
+ if (empty($projectId) || $projectId === 'console') {
+ return $console;
+ }
+
+ $project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
+
+ return $project;
+}, ['dbForPlatform', 'request', 'console']);
+
+App::setResource('session', function (Document $user) {
+ if ($user->isEmpty()) {
+ return;
+ }
+
+ $sessions = $user->getAttribute('sessions', []);
+ $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret);
+
+ if (!$sessionId) {
+ return;
+ }
+
+ foreach ($sessions as $session) {/** @var Document $session */
+ if ($sessionId === $session->getId()) {
+ return $session;
+ }
+ }
+
+ return;
+}, ['user']);
+
+App::setResource('console', function () {
+ return new Document(Config::getParam('console'));
+}, []);
+
+App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project) {
+ if ($project->isEmpty() || $project->getId() === 'console') {
+ return $dbForPlatform;
+ }
+
+ try {
+ $dsn = new DSN($project->getAttribute('database'));
+ } catch (\InvalidArgumentException) {
+ // TODO: Temporary until all projects are using shared tables
+ $dsn = new DSN('mysql://' . $project->getAttribute('database'));
+ }
+
+ $dbAdapter = $pools
+ ->get($dsn->getHost())
+ ->pop()
+ ->getResource();
+
+ $database = new Database($dbAdapter, $cache);
+
+ $database
+ ->setMetadata('host', \gethostname())
+ ->setMetadata('project', $project->getId())
+ ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
+ ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
+
+ $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
+
+ if (\in_array($dsn->getHost(), $sharedTables)) {
+ $database
+ ->setSharedTables(true)
+ ->setTenant($project->getInternalId())
+ ->setNamespace($dsn->getParam('namespace'));
+ } else {
+ $database
+ ->setSharedTables(false)
+ ->setTenant(null)
+ ->setNamespace('_' . $project->getInternalId());
+ }
+
+ return $database;
+}, ['pools', 'dbForPlatform', 'cache', 'project']);
+
+App::setResource('dbForPlatform', function (Group $pools, Cache $cache) {
+ $dbAdapter = $pools
+ ->get('console')
+ ->pop()
+ ->getResource();
+
+ $database = new Database($dbAdapter, $cache);
+
+ $database
+ ->setNamespace('_console')
+ ->setMetadata('host', \gethostname())
+ ->setMetadata('project', 'console')
+ ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
+ ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
+
+ return $database;
+}, ['pools', 'cache']);
+
+App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
+ $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
+
+ return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) {
+ if ($project->isEmpty() || $project->getId() === 'console') {
+ return $dbForPlatform;
+ }
+
+ try {
+ $dsn = new DSN($project->getAttribute('database'));
+ } catch (\InvalidArgumentException) {
+ // TODO: Temporary until all projects are using shared tables
+ $dsn = new DSN('mysql://' . $project->getAttribute('database'));
+ }
+
+ $configure = (function (Database $database) use ($project, $dsn) {
+ $database
+ ->setMetadata('host', \gethostname())
+ ->setMetadata('project', $project->getId())
+ ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
+ ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
+
+ $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
+
+ if (\in_array($dsn->getHost(), $sharedTables)) {
+ $database
+ ->setSharedTables(true)
+ ->setTenant($project->getInternalId())
+ ->setNamespace($dsn->getParam('namespace'));
+ } else {
+ $database
+ ->setSharedTables(false)
+ ->setTenant(null)
+ ->setNamespace('_' . $project->getInternalId());
+ }
+ });
+
+ if (isset($databases[$dsn->getHost()])) {
+ $database = $databases[$dsn->getHost()];
+ $configure($database);
+ return $database;
+ }
+
+ $dbAdapter = $pools
+ ->get($dsn->getHost())
+ ->pop()
+ ->getResource();
+
+ $database = new Database($dbAdapter, $cache);
+ $databases[$dsn->getHost()] = $database;
+ $configure($database);
+
+ return $database;
+ };
+}, ['pools', 'dbForPlatform', 'cache']);
+
+App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
+ $database = null;
+ return function (?Document $project = null) use ($pools, $cache, $database) {
+ if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
+ $database->setTenant($project->getInternalId());
+ return $database;
+ }
+
+ $dbAdapter = $pools
+ ->get('logs')
+ ->pop()
+ ->getResource();
+
+ $database = new Database(
+ $dbAdapter,
+ $cache
+ );
+
+ $database
+ ->setSharedTables(true)
+ ->setNamespace('logsV1')
+ ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
+ ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
+
+ // set tenant
+ if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
+ $database->setTenant($project->getInternalId());
+ }
+
+ return $database;
+ };
+}, ['pools', 'cache']);
+
+App::setResource('cache', function (Group $pools) {
+ $list = Config::getParam('pools-cache', []);
+ $adapters = [];
+
+ foreach ($list as $value) {
+ $adapters[] = $pools
+ ->get($value)
+ ->pop()
+ ->getResource()
+ ;
+ }
+
+ return new Cache(new Sharding($adapters));
+}, ['pools']);
+
+App::setResource('redis', function () {
+ $host = System::getEnv('_APP_REDIS_HOST', 'localhost');
+ $port = System::getEnv('_APP_REDIS_PORT', 6379);
+ $pass = System::getEnv('_APP_REDIS_PASS', '');
+
+ $redis = new \Redis();
+ @$redis->pconnect($host, (int)$port);
+ if ($pass) {
+ $redis->auth($pass);
+ }
+ $redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
+
+ return $redis;
+});
+
+App::setResource('timelimit', function (\Redis $redis) {
+ return function (string $key, int $limit, int $time) use ($redis) {
+ return new TimeLimitRedis($key, $limit, $time, $redis);
+ };
+}, ['redis']);
+
+App::setResource('deviceForLocal', function () {
+ return new Local();
+});
+
+App::setResource('deviceForFiles', function ($project) {
+ return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
+}, ['project']);
+
+App::setResource('deviceForImports', function (Document $project) {
+ return getDevice(APP_STORAGE_IMPORTS . '/app-' . $project->getId());
+}, ['project']);
+
+App::setResource('deviceForFunctions', function ($project) {
+ return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
+}, ['project']);
+
+App::setResource('deviceForBuilds', function ($project) {
+ return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
+}, ['project']);
+
+function getDevice(string $root, string $connection = ''): Device
+{
+ $connection = !empty($connection) ? $connection : System::getEnv('_APP_CONNECTIONS_STORAGE', '');
+
+ if (!empty($connection)) {
+ $acl = 'private';
+ $device = Storage::DEVICE_LOCAL;
+ $accessKey = '';
+ $accessSecret = '';
+ $bucket = '';
+ $region = '';
+ $url = System::getEnv('_APP_STORAGE_S3_ENDPOINT', '');
+
+ try {
+ $dsn = new DSN($connection);
+ $device = $dsn->getScheme();
+ $accessKey = $dsn->getUser() ?? '';
+ $accessSecret = $dsn->getPassword() ?? '';
+ $bucket = $dsn->getPath() ?? '';
+ $region = $dsn->getParam('region');
+ } catch (\Throwable $e) {
+ Console::warning($e->getMessage() . 'Invalid DSN. Defaulting to Local device.');
+ }
+
+ switch ($device) {
+ case Storage::DEVICE_S3:
+ if (!empty($url)) {
+ return new S3($root, $accessKey, $accessSecret, $url, $region, $acl);
+ } else {
+ return new AWS($root, $accessKey, $accessSecret, $bucket, $region, $acl);
+ }
+ // no break
+ case STORAGE::DEVICE_DO_SPACES:
+ $device = new DOSpaces($root, $accessKey, $accessSecret, $bucket, $region, $acl);
+ $device->setHttpVersion(S3::HTTP_VERSION_1_1);
+ return $device;
+ case Storage::DEVICE_BACKBLAZE:
+ return new Backblaze($root, $accessKey, $accessSecret, $bucket, $region, $acl);
+ case Storage::DEVICE_LINODE:
+ return new Linode($root, $accessKey, $accessSecret, $bucket, $region, $acl);
+ case Storage::DEVICE_WASABI:
+ return new Wasabi($root, $accessKey, $accessSecret, $bucket, $region, $acl);
+ case Storage::DEVICE_LOCAL:
+ default:
+ return new Local($root);
+ }
+ } else {
+ switch (strtolower(System::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) ?? '')) {
+ case Storage::DEVICE_LOCAL:
+ default:
+ return new Local($root);
+ case Storage::DEVICE_S3:
+ $s3AccessKey = System::getEnv('_APP_STORAGE_S3_ACCESS_KEY', '');
+ $s3SecretKey = System::getEnv('_APP_STORAGE_S3_SECRET', '');
+ $s3Region = System::getEnv('_APP_STORAGE_S3_REGION', '');
+ $s3Bucket = System::getEnv('_APP_STORAGE_S3_BUCKET', '');
+ $s3Acl = 'private';
+ $s3EndpointUrl = System::getEnv('_APP_STORAGE_S3_ENDPOINT', '');
+ if (!empty($s3EndpointUrl)) {
+ return new S3($root, $s3AccessKey, $s3SecretKey, $s3EndpointUrl, $s3Region, $s3Acl);
+ } else {
+ return new AWS($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl);
+ }
+ // no break
+ case Storage::DEVICE_DO_SPACES:
+ $doSpacesAccessKey = System::getEnv('_APP_STORAGE_DO_SPACES_ACCESS_KEY', '');
+ $doSpacesSecretKey = System::getEnv('_APP_STORAGE_DO_SPACES_SECRET', '');
+ $doSpacesRegion = System::getEnv('_APP_STORAGE_DO_SPACES_REGION', '');
+ $doSpacesBucket = System::getEnv('_APP_STORAGE_DO_SPACES_BUCKET', '');
+ $doSpacesAcl = 'private';
+ $device = new DOSpaces($root, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl);
+ $device->setHttpVersion(S3::HTTP_VERSION_1_1);
+ return $device;
+ case Storage::DEVICE_BACKBLAZE:
+ $backblazeAccessKey = System::getEnv('_APP_STORAGE_BACKBLAZE_ACCESS_KEY', '');
+ $backblazeSecretKey = System::getEnv('_APP_STORAGE_BACKBLAZE_SECRET', '');
+ $backblazeRegion = System::getEnv('_APP_STORAGE_BACKBLAZE_REGION', '');
+ $backblazeBucket = System::getEnv('_APP_STORAGE_BACKBLAZE_BUCKET', '');
+ $backblazeAcl = 'private';
+ return new Backblaze($root, $backblazeAccessKey, $backblazeSecretKey, $backblazeBucket, $backblazeRegion, $backblazeAcl);
+ case Storage::DEVICE_LINODE:
+ $linodeAccessKey = System::getEnv('_APP_STORAGE_LINODE_ACCESS_KEY', '');
+ $linodeSecretKey = System::getEnv('_APP_STORAGE_LINODE_SECRET', '');
+ $linodeRegion = System::getEnv('_APP_STORAGE_LINODE_REGION', '');
+ $linodeBucket = System::getEnv('_APP_STORAGE_LINODE_BUCKET', '');
+ $linodeAcl = 'private';
+ return new Linode($root, $linodeAccessKey, $linodeSecretKey, $linodeBucket, $linodeRegion, $linodeAcl);
+ case Storage::DEVICE_WASABI:
+ $wasabiAccessKey = System::getEnv('_APP_STORAGE_WASABI_ACCESS_KEY', '');
+ $wasabiSecretKey = System::getEnv('_APP_STORAGE_WASABI_SECRET', '');
+ $wasabiRegion = System::getEnv('_APP_STORAGE_WASABI_REGION', '');
+ $wasabiBucket = System::getEnv('_APP_STORAGE_WASABI_BUCKET', '');
+ $wasabiAcl = 'private';
+ return new Wasabi($root, $wasabiAccessKey, $wasabiSecretKey, $wasabiBucket, $wasabiRegion, $wasabiAcl);
+ }
+ }
+}
+
+App::setResource('mode', function ($request) {
+ /** @var Appwrite\Utopia\Request $request */
+
+ /**
+ * Defines the mode for the request:
+ * - 'default' => Requests for Client and Server Side
+ * - 'admin' => Request from the Console on non-console projects
+ */
+ return $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT));
+}, ['request']);
+
+App::setResource('geodb', function ($register) {
+ /** @var Utopia\Registry\Registry $register */
+ return $register->get('geodb');
+}, ['register']);
+
+App::setResource('passwordsDictionary', function ($register) {
+ /** @var Utopia\Registry\Registry $register */
+ return $register->get('passwordsDictionary');
+}, ['register']);
+
+
+App::setResource('servers', function () {
+ $platforms = Config::getParam('platforms');
+ $server = $platforms[APP_PLATFORM_SERVER];
+
+ $languages = array_map(function ($language) {
+ return strtolower($language['name']);
+ }, $server['sdks']);
+
+ return $languages;
+});
+
+App::setResource('promiseAdapter', function ($register) {
+ return $register->get('promiseAdapter');
+}, ['register']);
+
+App::setResource('schema', function ($utopia, $dbForProject) {
+
+ $complexity = function (int $complexity, array $args) {
+ $queries = Query::parseQueries($args['queries'] ?? []);
+ $query = Query::getByType($queries, [Query::TYPE_LIMIT])[0] ?? null;
+ $limit = $query ? $query->getValue() : APP_LIMIT_LIST_DEFAULT;
+
+ return $complexity * $limit;
+ };
+
+ $attributes = function (int $limit, int $offset) use ($dbForProject) {
+ $attrs = Authorization::skip(fn () => $dbForProject->find('attributes', [
+ Query::limit($limit),
+ Query::offset($offset),
+ ]));
+
+ return \array_map(function ($attr) {
+ return $attr->getArrayCopy();
+ }, $attrs);
+ };
+
+ $urls = [
+ 'list' => function (string $databaseId, string $collectionId, array $args) {
+ return "/v1/databases/$databaseId/collections/$collectionId/documents";
+ },
+ 'create' => function (string $databaseId, string $collectionId, array $args) {
+ return "/v1/databases/$databaseId/collections/$collectionId/documents";
+ },
+ 'read' => function (string $databaseId, string $collectionId, array $args) {
+ return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
+ },
+ 'update' => function (string $databaseId, string $collectionId, array $args) {
+ return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
+ },
+ 'delete' => function (string $databaseId, string $collectionId, array $args) {
+ return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
+ },
+ ];
+
+ $params = [
+ 'list' => function (string $databaseId, string $collectionId, array $args) {
+ return [ 'queries' => $args['queries']];
+ },
+ 'create' => function (string $databaseId, string $collectionId, array $args) {
+ $id = $args['id'] ?? 'unique()';
+ $permissions = $args['permissions'] ?? null;
+
+ unset($args['id']);
+ unset($args['permissions']);
+
+ // Order must be the same as the route params
+ return [
+ 'databaseId' => $databaseId,
+ 'documentId' => $id,
+ 'collectionId' => $collectionId,
+ 'data' => $args,
+ 'permissions' => $permissions,
+ ];
+ },
+ 'update' => function (string $databaseId, string $collectionId, array $args) {
+ $documentId = $args['id'];
+ $permissions = $args['permissions'] ?? null;
+
+ unset($args['id']);
+ unset($args['permissions']);
+
+ // Order must be the same as the route params
+ return [
+ 'databaseId' => $databaseId,
+ 'collectionId' => $collectionId,
+ 'documentId' => $documentId,
+ 'data' => $args,
+ 'permissions' => $permissions,
+ ];
+ },
+ ];
+
+ return Schema::build(
+ $utopia,
+ $complexity,
+ $attributes,
+ $urls,
+ $params,
+ );
+}, ['utopia', 'dbForProject']);
+
+App::setResource('contributors', function () {
+ $path = 'app/config/contributors.json';
+ $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
+ return $list;
+});
+
+App::setResource('employees', function () {
+ $path = 'app/config/employees.json';
+ $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
+ return $list;
+});
+
+App::setResource('heroes', function () {
+ $path = 'app/config/heroes.json';
+ $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
+ return $list;
+});
+
+App::setResource('gitHub', function (Cache $cache) {
+ return new VcsGitHub($cache);
+}, ['cache']);
+
+App::setResource('requestTimestamp', function ($request) {
+ //TODO: Move this to the Request class itself
+ $timestampHeader = $request->getHeader('x-appwrite-timestamp');
+ $requestTimestamp = null;
+ if (!empty($timestampHeader)) {
+ try {
+ $requestTimestamp = new \DateTime($timestampHeader);
+ } catch (\Throwable $e) {
+ throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid X-Appwrite-Timestamp header value');
+ }
+ }
+ return $requestTimestamp;
+}, ['request']);
+
+App::setResource('plan', function (array $plan = []) {
+ return [];
+});
+
+App::setResource('smsRates', function () {
+ return [];
+});
+
+App::setResource('team', function (Document $project, Database $dbForPlatform, App $utopia, Request $request) {
+ $teamInternalId = '';
+ if ($project->getId() !== 'console') {
+ $teamInternalId = $project->getAttribute('teamInternalId', '');
+ } else {
+ $route = $utopia->match($request);
+ $path = $route->getPath();
+ if (str_starts_with($path, '/v1/projects/:projectId')) {
+ $uri = $request->getURI();
+ $pid = explode('/', $uri)[3];
+ $p = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $pid));
+ $teamInternalId = $p->getAttribute('teamInternalId', '');
+ } elseif ($path === '/v1/projects') {
+ $teamId = $request->getParam('teamId', '');
+ $team = Authorization::skip(fn () => $dbForPlatform->getDocument('teams', $teamId));
+ return $team;
+ }
+ }
+
+ $team = Authorization::skip(function () use ($dbForPlatform, $teamInternalId) {
+ return $dbForPlatform->findOne('teams', [
+ Query::equal('$internalId', [$teamInternalId]),
+ ]);
+ });
+
+ return $team;
+}, ['project', 'dbForPlatform', 'utopia', 'request']);
+
+App::setResource(
+ 'isResourceBlocked',
+ fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
+);
+
+App::setResource('previewHostname', function (Request $request) {
+ if (App::isDevelopment()) {
+ $host = $request->getQuery('appwrite-hostname') ?? '';
+ if (!empty($host)) {
+ return $host;
+ }
+ }
+
+ return '';
+}, ['request']);
+
+App::setResource('apiKey', function (Request $request, Document $project): ?Key {
+ $key = $request->getHeader('x-appwrite-key');
+
+ if (empty($key)) {
+ return null;
+ }
+
+ return Key::decode($project, $key);
+}, ['request', 'project']);
+
+App::setResource('executor', fn () => new Executor(fn (string $projectId, string $deploymentId) => System::getEnv('_APP_EXECUTOR_HOST')));
+
+App::setResource('resourceToken', function ($project, $dbForProject, $request) {
+ $tokenJWT = $request->getParam('token');
+
+ if (!empty($tokenJWT) && !$project->isEmpty()) { // JWT authentication
+ $jwt = new JWT(App::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
+
+ try {
+ $payload = $jwt->decode($tokenJWT);
+ } catch (JWTException $error) {
+ return new Document([]);
+ }
+
+ $tokenId = $payload['tokenId'] ?? '';
+ $secret = $payload['secret'] ?? '';
+ if (empty($tokenId) || empty($secret)) {
+ return new Document([]);
+ }
+
+ $token = Authorization::skip(fn () => $dbForProject->getDocument('resourceTokens', $tokenId));
+
+ if ($token->isEmpty() || $token->getAttribute('secret') != $secret) {
+ return new Document([]);
+ }
+
+ if ($token->getAttribute('resourceType') === 'file') {
+ $internalIds = explode(':', $token->getAttribute('resourceInternalId'));
+ $ids = explode(':', $token->getAttribute('resourceId'));
+
+ if (count($internalIds) != 2 || count($ids) != 2) {
+ return new Document([]);
+ }
+
+ return new Document([
+ 'bucketId' => $ids[0],
+ 'fileId' => $ids[1],
+ 'bucketInternalId' => $internalIds[0],
+ 'fileInternalId' => $internalIds[1],
+ ]);
+ }
+ }
+ return new Document([]);
+}, ['project', 'dbForProject', 'request']);
\ No newline at end of file
diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml
index 62fcd03624..7dfe14fcef 100644
--- a/app/views/install/compose.phtml
+++ b/app/views/install/compose.phtml
@@ -148,6 +148,7 @@ $image = $this->getParam('image', '');
- _APP_MAINTENANCE_RETENTION_CACHE
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
+ - _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
- _APP_MAINTENANCE_RETENTION_SCHEDULES
- _APP_SMS_PROVIDER
@@ -164,11 +165,10 @@ $image = $this->getParam('image', '');
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
- _APP_ASSISTANT_OPENAI_API_KEY
-
appwrite-console:
<<: *x-logging
container_name: appwrite-console
- image: /console:5.2.27
+ image: /console:5.2.53
restart: unless-stopped
networks:
- appwrite
@@ -341,7 +341,10 @@ $image = $this->getParam('image', '');
- _APP_EXECUTOR_HOST
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
+ - _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE
- _APP_MAINTENANCE_RETENTION_EXECUTION
+ - _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
+ - _APP_EMAIL_CERTIFICATES
appwrite-worker-databases:
image: /:
@@ -650,6 +653,7 @@ $image = $this->getParam('image', '');
- _APP_MAINTENANCE_RETENTION_CACHE
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
+ - _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
- _APP_MAINTENANCE_RETENTION_SCHEDULES
diff --git a/app/worker.php b/app/worker.php
index 605474e9f1..6a62fb7e7d 100644
--- a/app/worker.php
+++ b/app/worker.php
@@ -13,13 +13,12 @@ use Appwrite\Event\Func;
use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
use Appwrite\Event\Migration;
+use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage;
use Appwrite\Event\StatsUsageDump;
-/** remove */
-use Appwrite\Event\Usage;
-use Appwrite\Event\UsageDump;
-/** /remove */
+use Appwrite\Event\Webhook;
use Appwrite\Platform\Appwrite;
+use Executor\Executor;
use Swoole\Runtime;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\Cache\Adapter\Sharding;
@@ -112,6 +111,8 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
->setNamespace('_' . $project->getInternalId());
}
+ $database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
+
return $database;
}, ['cache', 'register', 'message', 'project', 'dbForPlatform']);
@@ -173,6 +174,8 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
->setNamespace('_' . $project->getInternalId());
}
+ $database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
+
return $database;
};
}, ['pools', 'dbForPlatform', 'cache']);
@@ -198,7 +201,7 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database
->setSharedTables(true)
->setNamespace('logsV1')
- ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS)
+ ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
// set tenant
@@ -211,15 +214,18 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
}, ['pools', 'cache']);
Server::setResource('abuseRetention', function () {
- return time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400);
+ return time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400); // 1 day
});
-Server::setResource('auditRetention', function () {
- return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600));
-});
+Server::setResource('auditRetention', function (Document $project) {
+ if ($project->getId() === 'console') {
+ return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE', 15778800)); // 6 months
+ }
+ return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600)); // 14 days
+}, ['project']);
Server::setResource('executionRetention', function () {
- return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600));
+ return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600)); // 14 days
});
Server::setResource('cache', function (Registry $register) {
@@ -269,14 +275,6 @@ Server::setResource('consumer', function (Group $pools) {
return $pools->get('consumer')->pop()->getResource();
}, ['pools']);
-Server::setResource('queueForUsage', function (Publisher $publisher) {
- return new Usage($publisher);
-}, ['publisher']);
-
-Server::setResource('queueForUsageDump', function (Publisher $publisher) {
- return new UsageDump($publisher);
-}, ['publisher']);
-
Server::setResource('queueForStatsUsage', function (Publisher $publisher) {
return new StatsUsage($publisher);
}, ['publisher']);
@@ -313,10 +311,18 @@ Server::setResource('queueForAudits', function (Publisher $publisher) {
return new Audit($publisher);
}, ['publisher']);
+Server::setResource('queueForWebhooks', function (Publisher $publisher) {
+ return new Webhook($publisher);
+}, ['publisher']);
+
Server::setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
}, ['publisher']);
+Server::setResource('queueForRealtime', function () {
+ return new Realtime();
+}, []);
+
Server::setResource('queueForCertificates', function (Publisher $publisher) {
return new Certificate($publisher);
}, ['publisher']);
@@ -333,6 +339,10 @@ Server::setResource('pools', function (Registry $register) {
return $register->get('pools');
}, ['register']);
+Server::setResource('deviceForImports', function (Document $project) {
+ return getDevice(APP_STORAGE_IMPORTS . '/app-' . $project->getId());
+}, ['project']);
+
Server::setResource('deviceForFunctions', function (Document $project) {
return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
}, ['project']);
@@ -408,6 +418,8 @@ Server::setResource('logError', function (Registry $register, Document $project)
};
}, ['register', 'project']);
+Server::setResource('executor', fn () => new Executor(fn (string $projectId, string $deploymentId) => System::getEnv('_APP_EXECUTOR_HOST')));
+
$pools = $register->get('pools');
$platform = new Appwrite();
$args = $platform->getEnv('argv');
diff --git a/bin/worker-usage b/bin/worker-usage
deleted file mode 100644
index e39ce8477c..0000000000
--- a/bin/worker-usage
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-php /usr/src/code/app/worker.php usage $@
\ No newline at end of file
diff --git a/bin/worker-usage-dump b/bin/worker-usage-dump
deleted file mode 100644
index 43ca87fcb3..0000000000
--- a/bin/worker-usage-dump
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-php /usr/src/code/app/worker.php usage-dump $@
\ No newline at end of file
diff --git a/composer.json b/composer.json
index d3ceb8b7a9..39477c83a0 100644
--- a/composer.json
+++ b/composer.json
@@ -45,34 +45,34 @@
"ext-sockets": "*",
"appwrite/php-runtimes": "0.16.*",
"appwrite/php-clamav": "2.0.*",
- "utopia-php/abuse": "0.50.*",
+ "utopia-php/abuse": "0.52.*",
"utopia-php/analytics": "0.10.*",
- "utopia-php/audit": "0.51.*",
- "utopia-php/cache": "0.11.*",
+ "utopia-php/audit": "0.55.*",
+ "utopia-php/cache": "0.12.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "0.2.*",
- "utopia-php/database": "0.59.0",
+ "utopia-php/database": "0.64.*",
"utopia-php/domains": "0.5.*",
"utopia-php/dsn": "0.2.1",
"utopia-php/framework": "0.33.*",
- "utopia-php/fetch": "0.3.*",
- "utopia-php/image": "0.7.*",
+ "utopia-php/fetch": "0.4.*",
+ "utopia-php/image": "0.8.*",
"utopia-php/locale": "0.4.*",
"utopia-php/logger": "0.6.*",
- "utopia-php/messaging": "0.14.*",
- "utopia-php/migration": "0.6.*",
+ "utopia-php/messaging": "0.16.*",
+ "utopia-php/migration": "0.9.1",
"utopia-php/orchestration": "0.9.*",
- "utopia-php/platform": "0.7.3",
- "utopia-php/pools": "0.5.*",
+ "utopia-php/platform": "0.7.*",
+ "utopia-php/pools": "0.8.*",
"utopia-php/preloader": "0.2.*",
- "utopia-php/queue": "0.8.*",
+ "utopia-php/queue": "0.9.*",
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "0.18.*",
"utopia-php/swoole": "0.8.*",
"utopia-php/system": "0.9.*",
"utopia-php/telemetry": "0.1.*",
- "utopia-php/vcs": "0.8.*",
- "utopia-php/websocket": "0.1.*",
+ "utopia-php/vcs": "0.9.*",
+ "utopia-php/websocket": "0.3.*",
"matomo/device-detector": "6.1.*",
"dragonmantank/cron-expression": "3.3.2",
"phpmailer/phpmailer": "6.9.1",
@@ -85,11 +85,11 @@
"require-dev": {
"ext-fileinfo": "*",
"appwrite/sdk-generator": "0.40.*",
- "phpunit/phpunit": "9.5.20",
+ "phpunit/phpunit": "9.*",
"swoole/ide-helper": "5.1.2",
- "textalk/websocket": "1.5.7",
- "laravel/pint": "^1.14",
- "phpbench/phpbench": "^1.2"
+ "textalk/websocket": "1.5.*",
+ "laravel/pint": "1.*",
+ "phpbench/phpbench": "1.*"
},
"provide": {
"ext-phpiredis": "*"
diff --git a/composer.lock b/composer.lock
index 11d2ba4c2f..7fa497a8f5 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": "b17c58729c4380afcba7714e9bced863",
+ "content-hash": "e22cfd0e495f55633218f029d8c7ec9d",
"packages": [
{
"name": "adhocore/jwt",
@@ -279,16 +279,16 @@
},
{
"name": "brick/math",
- "version": "0.12.1",
+ "version": "0.12.3",
"source": {
"type": "git",
"url": "https://github.com/brick/math.git",
- "reference": "f510c0a40911935b77b86859eb5223d58d660df1"
+ "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1",
- "reference": "f510c0a40911935b77b86859eb5223d58d660df1",
+ "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba",
+ "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba",
"shasum": ""
},
"require": {
@@ -297,7 +297,7 @@
"require-dev": {
"php-coveralls/php-coveralls": "^2.2",
"phpunit/phpunit": "^10.1",
- "vimeo/psalm": "5.16.0"
+ "vimeo/psalm": "6.8.8"
},
"type": "library",
"autoload": {
@@ -327,7 +327,7 @@
],
"support": {
"issues": "https://github.com/brick/math/issues",
- "source": "https://github.com/brick/math/tree/0.12.1"
+ "source": "https://github.com/brick/math/tree/0.12.3"
},
"funding": [
{
@@ -335,7 +335,7 @@
"type": "github"
}
],
- "time": "2023-11-29T23:19:16+00:00"
+ "time": "2025-02-28T13:11:00+00:00"
},
{
"name": "chillerlan/php-qrcode",
@@ -709,16 +709,16 @@
},
{
"name": "google/protobuf",
- "version": "v4.29.3",
+ "version": "v4.30.2",
"source": {
"type": "git",
"url": "https://github.com/protocolbuffers/protobuf-php.git",
- "reference": "ab5077c2cfdd1f415f42d11fdbdf903ba8e3d9b7"
+ "reference": "a4c4d8565b40b9f76debc9dfeb221412eacb8ced"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/ab5077c2cfdd1f415f42d11fdbdf903ba8e3d9b7",
- "reference": "ab5077c2cfdd1f415f42d11fdbdf903ba8e3d9b7",
+ "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/a4c4d8565b40b9f76debc9dfeb221412eacb8ced",
+ "reference": "a4c4d8565b40b9f76debc9dfeb221412eacb8ced",
"shasum": ""
},
"require": {
@@ -747,68 +747,9 @@
"proto"
],
"support": {
- "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.29.3"
+ "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.30.2"
},
- "time": "2025-01-08T21:00:13+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.6",
- "vimeo/psalm": "^4.3 || ^5.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Jean85\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Alessandro Lai",
- "email": "alessandro.lai85@gmail.com"
- }
- ],
- "description": "A library to get pretty versions strings of installed dependencies",
- "keywords": [
- "composer",
- "package",
- "release",
- "versions"
- ],
- "support": {
- "issues": "https://github.com/Jean85/pretty-package-versions/issues",
- "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.0"
- },
- "time": "2024-11-18T16:19:46+00:00"
+ "time": "2025-03-26T18:01:50+00:00"
},
{
"name": "league/csv",
@@ -968,75 +909,6 @@
},
"time": "2023-10-02T10:01:54+00:00"
},
- {
- "name": "mongodb/mongodb",
- "version": "1.10.0",
- "source": {
- "type": "git",
- "url": "https://github.com/mongodb/mongo-php-library.git",
- "reference": "b0bbd657f84219212487d01a8ffe93a789e1e488"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/b0bbd657f84219212487d01a8ffe93a789e1e488",
- "reference": "b0bbd657f84219212487d01a8ffe93a789e1e488",
- "shasum": ""
- },
- "require": {
- "ext-hash": "*",
- "ext-json": "*",
- "ext-mongodb": "^1.11.0",
- "jean85/pretty-package-versions": "^1.2 || ^2.0.1",
- "php": "^7.1 || ^8.0",
- "symfony/polyfill-php80": "^1.19"
- },
- "require-dev": {
- "doctrine/coding-standard": "^9.0",
- "squizlabs/php_codesniffer": "^3.6",
- "symfony/phpunit-bridge": "^5.2"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.10.x-dev"
- }
- },
- "autoload": {
- "files": [
- "src/functions.php"
- ],
- "psr-4": {
- "MongoDB\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "Apache-2.0"
- ],
- "authors": [
- {
- "name": "Andreas Braun",
- "email": "andreas.braun@mongodb.com"
- },
- {
- "name": "Jeremy Mikola",
- "email": "jmikola@gmail.com"
- }
- ],
- "description": "MongoDB driver library",
- "homepage": "https://jira.mongodb.org/browse/PHPLIB",
- "keywords": [
- "database",
- "driver",
- "mongodb",
- "persistence"
- ],
- "support": {
- "issues": "https://github.com/mongodb/mongo-php-library/issues",
- "source": "https://github.com/mongodb/mongo-php-library/tree/1.10.0"
- },
- "time": "2021-10-20T22:22:37+00:00"
- },
{
"name": "mustangostang/spyc",
"version": "0.6.3",
@@ -1237,16 +1109,16 @@
},
{
"name": "open-telemetry/api",
- "version": "1.2.2",
+ "version": "1.2.3",
"source": {
"type": "git",
"url": "https://github.com/opentelemetry-php/api.git",
- "reference": "8b925df3047628968bc5be722468db1b98b82d51"
+ "reference": "199d7ddda88f5f5619fa73463f1a5a7149ccd1f1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/8b925df3047628968bc5be722468db1b98b82d51",
- "reference": "8b925df3047628968bc5be722468db1b98b82d51",
+ "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/199d7ddda88f5f5619fa73463f1a5a7149ccd1f1",
+ "reference": "199d7ddda88f5f5619fa73463f1a5a7149ccd1f1",
"shasum": ""
},
"require": {
@@ -1303,7 +1175,7 @@
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
"source": "https://github.com/open-telemetry/opentelemetry-php"
},
- "time": "2025-02-03T21:49:11+00:00"
+ "time": "2025-03-05T21:42:54+00:00"
},
{
"name": "open-telemetry/context",
@@ -1366,16 +1238,16 @@
},
{
"name": "open-telemetry/exporter-otlp",
- "version": "1.2.0",
+ "version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/opentelemetry-php/exporter-otlp.git",
- "reference": "243d9657c44a06f740cf384f486afe954c2b725f"
+ "reference": "b7580440b7481a98da97aceabeb46e1b276c8747"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/243d9657c44a06f740cf384f486afe954c2b725f",
- "reference": "243d9657c44a06f740cf384f486afe954c2b725f",
+ "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/b7580440b7481a98da97aceabeb46e1b276c8747",
+ "reference": "b7580440b7481a98da97aceabeb46e1b276c8747",
"shasum": ""
},
"require": {
@@ -1426,7 +1298,7 @@
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
"source": "https://github.com/open-telemetry/opentelemetry-php"
},
- "time": "2025-01-08T23:50:03+00:00"
+ "time": "2025-03-06T23:21:56+00:00"
},
{
"name": "open-telemetry/gen-otlp-protobuf",
@@ -1493,16 +1365,16 @@
},
{
"name": "open-telemetry/sdk",
- "version": "1.2.2",
+ "version": "1.2.3",
"source": {
"type": "git",
"url": "https://github.com/opentelemetry-php/sdk.git",
- "reference": "37eec0fe47ddd627911f318f29b6cd48196be0c0"
+ "reference": "0e7804c176c4b09d95b7985400aa38ce544cb7fc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/37eec0fe47ddd627911f318f29b6cd48196be0c0",
- "reference": "37eec0fe47ddd627911f318f29b6cd48196be0c0",
+ "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/0e7804c176c4b09d95b7985400aa38ce544cb7fc",
+ "reference": "0e7804c176c4b09d95b7985400aa38ce544cb7fc",
"shasum": ""
},
"require": {
@@ -1579,7 +1451,7 @@
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
"source": "https://github.com/open-telemetry/opentelemetry-php"
},
- "time": "2025-01-29T21:40:28+00:00"
+ "time": "2025-04-08T09:55:41+00:00"
},
{
"name": "open-telemetry/sem-conv",
@@ -1757,16 +1629,16 @@
},
{
"name": "php-amqplib/php-amqplib",
- "version": "v3.7.2",
+ "version": "v3.7.3",
"source": {
"type": "git",
"url": "https://github.com/php-amqplib/php-amqplib.git",
- "reference": "738a73eb0019b6c99d9bc25d7a0c0dd8f56a5199"
+ "reference": "9f50fe69a9f1a19e2cb25596a354d705de36fe59"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-amqplib/php-amqplib/zipball/738a73eb0019b6c99d9bc25d7a0c0dd8f56a5199",
- "reference": "738a73eb0019b6c99d9bc25d7a0c0dd8f56a5199",
+ "url": "https://api.github.com/repos/php-amqplib/php-amqplib/zipball/9f50fe69a9f1a19e2cb25596a354d705de36fe59",
+ "reference": "9f50fe69a9f1a19e2cb25596a354d705de36fe59",
"shasum": ""
},
"require": {
@@ -1832,9 +1704,9 @@
],
"support": {
"issues": "https://github.com/php-amqplib/php-amqplib/issues",
- "source": "https://github.com/php-amqplib/php-amqplib/tree/v3.7.2"
+ "source": "https://github.com/php-amqplib/php-amqplib/tree/v3.7.3"
},
- "time": "2024-11-21T09:21:41+00:00"
+ "time": "2025-02-18T20:11:13+00:00"
},
{
"name": "php-http/discovery",
@@ -2371,16 +2243,16 @@
},
{
"name": "ramsey/collection",
- "version": "2.0.0",
+ "version": "2.1.1",
"source": {
"type": "git",
"url": "https://github.com/ramsey/collection.git",
- "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5"
+ "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5",
- "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5",
+ "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2",
+ "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2",
"shasum": ""
},
"require": {
@@ -2388,25 +2260,22 @@
},
"require-dev": {
"captainhook/plugin-composer": "^5.3",
- "ergebnis/composer-normalize": "^2.28.3",
- "fakerphp/faker": "^1.21",
+ "ergebnis/composer-normalize": "^2.45",
+ "fakerphp/faker": "^1.24",
"hamcrest/hamcrest-php": "^2.0",
- "jangregor/phpstan-prophecy": "^1.0",
- "mockery/mockery": "^1.5",
+ "jangregor/phpstan-prophecy": "^2.1",
+ "mockery/mockery": "^1.6",
"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"
+ "php-parallel-lint/php-parallel-lint": "^1.4",
+ "phpspec/prophecy-phpunit": "^2.3",
+ "phpstan/extension-installer": "^1.4",
+ "phpstan/phpstan": "^2.1",
+ "phpstan/phpstan-mockery": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpunit/phpunit": "^10.5",
+ "ramsey/coding-standard": "^2.3",
+ "ramsey/conventional-commits": "^1.6",
+ "roave/security-advisories": "dev-latest"
},
"type": "library",
"extra": {
@@ -2444,19 +2313,9 @@
],
"support": {
"issues": "https://github.com/ramsey/collection/issues",
- "source": "https://github.com/ramsey/collection/tree/2.0.0"
+ "source": "https://github.com/ramsey/collection/tree/2.1.1"
},
- "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"
+ "time": "2025-03-22T05:38:12+00:00"
},
{
"name": "ramsey/uuid",
@@ -2694,16 +2553,16 @@
},
{
"name": "symfony/http-client",
- "version": "v7.2.3",
+ "version": "v7.2.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
- "reference": "7ce6078c79a4a7afff931c413d2959d3bffbfb8d"
+ "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-client/zipball/7ce6078c79a4a7afff931c413d2959d3bffbfb8d",
- "reference": "7ce6078c79a4a7afff931c413d2959d3bffbfb8d",
+ "url": "https://api.github.com/repos/symfony/http-client/zipball/78981a2ffef6437ed92d4d7e2a86a82f256c6dc6",
+ "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6",
"shasum": ""
},
"require": {
@@ -2769,7 +2628,7 @@
"http"
],
"support": {
- "source": "https://github.com/symfony/http-client/tree/v7.2.3"
+ "source": "https://github.com/symfony/http-client/tree/v7.2.4"
},
"funding": [
{
@@ -2785,7 +2644,7 @@
"type": "tidelift"
}
],
- "time": "2025-01-28T15:51:35+00:00"
+ "time": "2025-02-13T10:27:23+00:00"
},
{
"name": "symfony/http-client-contracts",
@@ -2945,86 +2804,6 @@
],
"time": "2024-09-09T11:45:10+00:00"
},
- {
- "name": "symfony/polyfill-php80",
- "version": "v1.31.0",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/polyfill-php80.git",
- "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
- "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
- "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\\Php80\\": ""
- },
- "classmap": [
- "Resources/stubs"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Ion Bazan",
- "email": "ion.bazan@gmail.com"
- },
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
- "homepage": "https://symfony.com",
- "keywords": [
- "compatibility",
- "polyfill",
- "portable",
- "shim"
- ],
- "support": {
- "source": "https://github.com/symfony/polyfill-php80/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/polyfill-php82",
"version": "v1.31.0",
@@ -3186,16 +2965,16 @@
},
{
"name": "tbachert/spi",
- "version": "v1.0.2",
+ "version": "v1.0.3",
"source": {
"type": "git",
"url": "https://github.com/Nevay/spi.git",
- "reference": "2ddfaf815dafb45791a61b08170de8d583c16062"
+ "reference": "506a79c98e1a51522e76ee921ccb6c62d52faf3a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Nevay/spi/zipball/2ddfaf815dafb45791a61b08170de8d583c16062",
- "reference": "2ddfaf815dafb45791a61b08170de8d583c16062",
+ "url": "https://api.github.com/repos/Nevay/spi/zipball/506a79c98e1a51522e76ee921ccb6c62d52faf3a",
+ "reference": "506a79c98e1a51522e76ee921ccb6c62d52faf3a",
"shasum": ""
},
"require": {
@@ -3232,9 +3011,9 @@
],
"support": {
"issues": "https://github.com/Nevay/spi/issues",
- "source": "https://github.com/Nevay/spi/tree/v1.0.2"
+ "source": "https://github.com/Nevay/spi/tree/v1.0.3"
},
- "time": "2024-10-04T16:36:12+00:00"
+ "time": "2025-04-02T19:38:14+00:00"
},
{
"name": "thecodingmachine/safe",
@@ -3377,16 +3156,16 @@
},
{
"name": "utopia-php/abuse",
- "version": "0.50.0",
+ "version": "0.52.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/abuse.git",
- "reference": "3ff67819e9de61506c5ca070a70552f7ebe99f80"
+ "reference": "a0d6421e7e5baa3ac02755496dca9fdeaa814b93"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/abuse/zipball/3ff67819e9de61506c5ca070a70552f7ebe99f80",
- "reference": "3ff67819e9de61506c5ca070a70552f7ebe99f80",
+ "url": "https://api.github.com/repos/utopia-php/abuse/zipball/a0d6421e7e5baa3ac02755496dca9fdeaa814b93",
+ "reference": "a0d6421e7e5baa3ac02755496dca9fdeaa814b93",
"shasum": ""
},
"require": {
@@ -3394,7 +3173,7 @@
"ext-pdo": "*",
"ext-redis": "*",
"php": ">=8.0",
- "utopia-php/database": "0.59.*"
+ "utopia-php/database": "0.*.*"
},
"require-dev": {
"laravel/pint": "1.*",
@@ -3422,9 +3201,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/abuse/issues",
- "source": "https://github.com/utopia-php/abuse/tree/0.50.0"
+ "source": "https://github.com/utopia-php/abuse/tree/0.52.0"
},
- "time": "2025-02-12T09:13:59+00:00"
+ "time": "2025-03-06T03:48:29+00:00"
},
{
"name": "utopia-php/analytics",
@@ -3474,21 +3253,21 @@
},
{
"name": "utopia-php/audit",
- "version": "0.51.0",
+ "version": "0.55.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/audit.git",
- "reference": "a5a4b73a57e27a0fac8025b1d6038e145a1ca04e"
+ "reference": "9f8cfe5fa5d5011b8dbf93b710236dfa91dc5518"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/audit/zipball/a5a4b73a57e27a0fac8025b1d6038e145a1ca04e",
- "reference": "a5a4b73a57e27a0fac8025b1d6038e145a1ca04e",
+ "url": "https://api.github.com/repos/utopia-php/audit/zipball/9f8cfe5fa5d5011b8dbf93b710236dfa91dc5518",
+ "reference": "9f8cfe5fa5d5011b8dbf93b710236dfa91dc5518",
"shasum": ""
},
"require": {
"php": ">=8.0",
- "utopia-php/database": "0.59.*"
+ "utopia-php/database": "0.*.*"
},
"require-dev": {
"laravel/pint": "1.*",
@@ -3515,29 +3294,30 @@
],
"support": {
"issues": "https://github.com/utopia-php/audit/issues",
- "source": "https://github.com/utopia-php/audit/tree/0.51.0"
+ "source": "https://github.com/utopia-php/audit/tree/0.55.0"
},
- "time": "2025-02-12T09:12:44+00:00"
+ "time": "2025-03-06T03:47:47+00:00"
},
{
"name": "utopia-php/cache",
- "version": "0.11.0",
+ "version": "0.12.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/cache.git",
- "reference": "8ebcab5aac7606331cef69b0081f6c9eff2e58bc"
+ "reference": "646038f1d470b759c129348be8fc14da3c00bbd9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/cache/zipball/8ebcab5aac7606331cef69b0081f6c9eff2e58bc",
- "reference": "8ebcab5aac7606331cef69b0081f6c9eff2e58bc",
+ "url": "https://api.github.com/repos/utopia-php/cache/zipball/646038f1d470b759c129348be8fc14da3c00bbd9",
+ "reference": "646038f1d470b759c129348be8fc14da3c00bbd9",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-memcached": "*",
"ext-redis": "*",
- "php": ">=8.0"
+ "php": ">=8.0",
+ "utopia-php/telemetry": "0.1.*"
},
"require-dev": {
"laravel/pint": "1.2.*",
@@ -3565,9 +3345,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/cache/issues",
- "source": "https://github.com/utopia-php/cache/tree/0.11.0"
+ "source": "https://github.com/utopia-php/cache/tree/0.12.0"
},
- "time": "2024-11-05T16:53:58+00:00"
+ "time": "2025-02-25T09:09:21+00:00"
},
{
"name": "utopia-php/cli",
@@ -3717,25 +3497,25 @@
},
{
"name": "utopia-php/database",
- "version": "0.59.0",
+ "version": "0.64.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
- "reference": "0eed7f1ad3eb66ff4a7d73b68dd9d3e05089eb18"
+ "reference": "6530a8a6d3c1fe92d0f9a92f0f05eda698d92e0b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/database/zipball/0eed7f1ad3eb66ff4a7d73b68dd9d3e05089eb18",
- "reference": "0eed7f1ad3eb66ff4a7d73b68dd9d3e05089eb18",
+ "url": "https://api.github.com/repos/utopia-php/database/zipball/6530a8a6d3c1fe92d0f9a92f0f05eda698d92e0b",
+ "reference": "6530a8a6d3c1fe92d0f9a92f0f05eda698d92e0b",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"ext-pdo": "*",
"php": ">=8.1",
- "utopia-php/cache": "0.11.*",
+ "utopia-php/cache": "0.12.*",
"utopia-php/framework": "0.33.*",
- "utopia-php/mongo": "0.3.*"
+ "utopia-php/pools": "0.8.*"
},
"require-dev": {
"fakerphp/faker": "1.23.*",
@@ -3767,9 +3547,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
- "source": "https://github.com/utopia-php/database/tree/0.59.0"
+ "source": "https://github.com/utopia-php/database/tree/0.64.1"
},
- "time": "2025-02-12T08:08:29+00:00"
+ "time": "2025-04-02T00:35:29+00:00"
},
{
"name": "utopia-php/domains",
@@ -3880,16 +3660,16 @@
},
{
"name": "utopia-php/fetch",
- "version": "0.3.0",
+ "version": "0.4.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/fetch.git",
- "reference": "02b12c05aec13399dcc2da8d51f908e328ab63f4"
+ "reference": "46e791ff6a95864517750b9df6bbf4a17e3c9c4e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/fetch/zipball/02b12c05aec13399dcc2da8d51f908e328ab63f4",
- "reference": "02b12c05aec13399dcc2da8d51f908e328ab63f4",
+ "url": "https://api.github.com/repos/utopia-php/fetch/zipball/46e791ff6a95864517750b9df6bbf4a17e3c9c4e",
+ "reference": "46e791ff6a95864517750b9df6bbf4a17e3c9c4e",
"shasum": ""
},
"require": {
@@ -3913,22 +3693,22 @@
"description": "A simple library that provides an interface for making HTTP Requests.",
"support": {
"issues": "https://github.com/utopia-php/fetch/issues",
- "source": "https://github.com/utopia-php/fetch/tree/0.3.0"
+ "source": "https://github.com/utopia-php/fetch/tree/0.4.0"
},
- "time": "2025-01-17T06:11:10+00:00"
+ "time": "2025-03-11T21:06:56+00:00"
},
{
"name": "utopia-php/framework",
- "version": "0.33.16",
+ "version": "0.33.19",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/http.git",
- "reference": "e91d4c560d1b809e25faa63d564fef034363b50f"
+ "reference": "64c7b7bb8a8595ffe875fa8d4b7705684dbf46c0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/http/zipball/e91d4c560d1b809e25faa63d564fef034363b50f",
- "reference": "e91d4c560d1b809e25faa63d564fef034363b50f",
+ "url": "https://api.github.com/repos/utopia-php/http/zipball/64c7b7bb8a8595ffe875fa8d4b7705684dbf46c0",
+ "reference": "64c7b7bb8a8595ffe875fa8d4b7705684dbf46c0",
"shasum": ""
},
"require": {
@@ -3960,31 +3740,32 @@
],
"support": {
"issues": "https://github.com/utopia-php/http/issues",
- "source": "https://github.com/utopia-php/http/tree/0.33.16"
+ "source": "https://github.com/utopia-php/http/tree/0.33.19"
},
- "time": "2025-01-16T15:58:50+00:00"
+ "time": "2025-03-06T11:37:49+00:00"
},
{
"name": "utopia-php/image",
- "version": "0.7.0",
+ "version": "0.8.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/image.git",
- "reference": "fcea143edbad524bf871ddbebe801d981f91f181"
+ "reference": "e8cc7dd14f423270a1b7570ec0dae88a66195b63"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/image/zipball/fcea143edbad524bf871ddbebe801d981f91f181",
- "reference": "fcea143edbad524bf871ddbebe801d981f91f181",
+ "url": "https://api.github.com/repos/utopia-php/image/zipball/e8cc7dd14f423270a1b7570ec0dae88a66195b63",
+ "reference": "e8cc7dd14f423270a1b7570ec0dae88a66195b63",
"shasum": ""
},
"require": {
+ "ext-gd": "*",
"ext-imagick": "*",
"php": ">=8.1"
},
"require-dev": {
"laravel/pint": "1.2.*",
- "phpstan/phpstan": "1.9.x-dev",
+ "phpstan/phpstan": "^1.10.0",
"phpunit/phpunit": "^9.3",
"vimeo/psalm": "4.13.1"
},
@@ -4008,9 +3789,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/image/issues",
- "source": "https://github.com/utopia-php/image/tree/0.7.0"
+ "source": "https://github.com/utopia-php/image/tree/0.8.1"
},
- "time": "2024-10-02T05:45:38+00:00"
+ "time": "2025-04-04T18:55:20+00:00"
},
{
"name": "utopia-php/locale",
@@ -4119,16 +3900,16 @@
},
{
"name": "utopia-php/messaging",
- "version": "0.14.1",
+ "version": "0.16.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/messaging.git",
- "reference": "4ba356a3aa382802727f7e13e0f0152bcc1fc535"
+ "reference": "5f3083697102b1821d6624938186761b1e09c54e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/messaging/zipball/4ba356a3aa382802727f7e13e0f0152bcc1fc535",
- "reference": "4ba356a3aa382802727f7e13e0f0152bcc1fc535",
+ "url": "https://api.github.com/repos/utopia-php/messaging/zipball/5f3083697102b1821d6624938186761b1e09c54e",
+ "reference": "5f3083697102b1821d6624938186761b1e09c54e",
"shasum": ""
},
"require": {
@@ -4164,22 +3945,22 @@
],
"support": {
"issues": "https://github.com/utopia-php/messaging/issues",
- "source": "https://github.com/utopia-php/messaging/tree/0.14.1"
+ "source": "https://github.com/utopia-php/messaging/tree/0.16.0"
},
- "time": "2025-01-28T06:14:28+00:00"
+ "time": "2025-02-18T08:27:00+00:00"
},
{
"name": "utopia-php/migration",
- "version": "0.6.19",
+ "version": "0.9.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/migration.git",
- "reference": "3c9497f7a54ef88b1077c48d8326893133ad78eb"
+ "reference": "f8b54727c7b0abe416a74a2a4c9fa4350c7a59a3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/migration/zipball/3c9497f7a54ef88b1077c48d8326893133ad78eb",
- "reference": "3c9497f7a54ef88b1077c48d8326893133ad78eb",
+ "url": "https://api.github.com/repos/utopia-php/migration/zipball/f8b54727c7b0abe416a74a2a4c9fa4350c7a59a3",
+ "reference": "f8b54727c7b0abe416a74a2a4c9fa4350c7a59a3",
"shasum": ""
},
"require": {
@@ -4187,7 +3968,7 @@
"ext-curl": "*",
"ext-openssl": "*",
"php": ">=8.1",
- "utopia-php/database": "0.59.*",
+ "utopia-php/database": "0.*.*",
"utopia-php/dsn": "0.2.*",
"utopia-php/framework": "0.33.*",
"utopia-php/storage": "0.18.*"
@@ -4220,69 +4001,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/migration/issues",
- "source": "https://github.com/utopia-php/migration/tree/0.6.19"
+ "source": "https://github.com/utopia-php/migration/tree/0.9.1"
},
- "time": "2025-02-13T07:50:21+00:00"
- },
- {
- "name": "utopia-php/mongo",
- "version": "0.3.1",
- "source": {
- "type": "git",
- "url": "https://github.com/utopia-php/mongo.git",
- "reference": "52326a9a43e2d27ff0c15c48ba746dacbe9a7aee"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/utopia-php/mongo/zipball/52326a9a43e2d27ff0c15c48ba746dacbe9a7aee",
- "reference": "52326a9a43e2d27ff0c15c48ba746dacbe9a7aee",
- "shasum": ""
- },
- "require": {
- "ext-mongodb": "*",
- "mongodb/mongodb": "1.10.0",
- "php": ">=8.0"
- },
- "require-dev": {
- "fakerphp/faker": "^1.14",
- "laravel/pint": "1.2.*",
- "phpstan/phpstan": "1.8.*",
- "phpunit/phpunit": "^9.4",
- "swoole/ide-helper": "4.8.0"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Utopia\\Mongo\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Eldad Fux",
- "email": "eldad@appwrite.io"
- },
- {
- "name": "Wess",
- "email": "wess@appwrite.io"
- }
- ],
- "description": "A simple library to manage Mongo database",
- "keywords": [
- "database",
- "mongo",
- "php",
- "upf",
- "utopia"
- ],
- "support": {
- "issues": "https://github.com/utopia-php/mongo/issues",
- "source": "https://github.com/utopia-php/mongo/tree/0.3.1"
- },
- "time": "2023-09-01T17:25:28+00:00"
+ "time": "2025-04-17T05:18:58+00:00"
},
{
"name": "utopia-php/orchestration",
@@ -4336,16 +4057,16 @@
},
{
"name": "utopia-php/platform",
- "version": "0.7.3",
+ "version": "0.7.4",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/platform.git",
- "reference": "463c2d817c893d7dbb678c2eac7a8291f2710e25"
+ "reference": "a5b93d8177702ec458c3af9137663133c012b71b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/platform/zipball/463c2d817c893d7dbb678c2eac7a8291f2710e25",
- "reference": "463c2d817c893d7dbb678c2eac7a8291f2710e25",
+ "url": "https://api.github.com/repos/utopia-php/platform/zipball/a5b93d8177702ec458c3af9137663133c012b71b",
+ "reference": "a5b93d8177702ec458c3af9137663133c012b71b",
"shasum": ""
},
"require": {
@@ -4354,7 +4075,7 @@
"php": ">=8.0",
"utopia-php/cli": "0.15.*",
"utopia-php/framework": "0.33.*",
- "utopia-php/queue": "0.8.*"
+ "utopia-php/queue": "0.9.*"
},
"require-dev": {
"laravel/pint": "1.2.*",
@@ -4380,31 +4101,32 @@
],
"support": {
"issues": "https://github.com/utopia-php/platform/issues",
- "source": "https://github.com/utopia-php/platform/tree/0.7.3"
+ "source": "https://github.com/utopia-php/platform/tree/0.7.4"
},
- "time": "2025-02-04T15:09:00+00:00"
+ "time": "2025-03-13T13:00:12+00:00"
},
{
"name": "utopia-php/pools",
- "version": "0.5.0",
+ "version": "0.8.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/pools.git",
- "reference": "6f716a213a08db95eda1b5dddfa90983c1834817"
+ "reference": "60733929dc328e7ea47e800579c8bbf0d49df5ba"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/pools/zipball/6f716a213a08db95eda1b5dddfa90983c1834817",
- "reference": "6f716a213a08db95eda1b5dddfa90983c1834817",
+ "url": "https://api.github.com/repos/utopia-php/pools/zipball/60733929dc328e7ea47e800579c8bbf0d49df5ba",
+ "reference": "60733929dc328e7ea47e800579c8bbf0d49df5ba",
"shasum": ""
},
"require": {
- "php": ">=8.0"
+ "php": ">=8.3",
+ "utopia-php/telemetry": "0.1.*"
},
"require-dev": {
- "laravel/pint": "1.2.*",
- "phpstan/phpstan": "1.8.*",
- "phpunit/phpunit": "^9.3"
+ "laravel/pint": "1.*",
+ "phpstan/phpstan": "1.*",
+ "phpunit/phpunit": "11.*"
},
"type": "library",
"autoload": {
@@ -4431,9 +4153,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/pools/issues",
- "source": "https://github.com/utopia-php/pools/tree/0.5.0"
+ "source": "https://github.com/utopia-php/pools/tree/0.8.0"
},
- "time": "2024-04-19T11:11:54+00:00"
+ "time": "2025-03-19T10:22:03+00:00"
},
{
"name": "utopia-php/preloader",
@@ -4490,23 +4212,23 @@
},
{
"name": "utopia-php/queue",
- "version": "0.8.6",
+ "version": "0.9.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/queue.git",
- "reference": "b713b997285c29d120bbcbe3d6e93762d850f87c"
+ "reference": "32b6f84c55aae761db5a5ae76cc91ca8dbc8bc32"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/queue/zipball/b713b997285c29d120bbcbe3d6e93762d850f87c",
- "reference": "b713b997285c29d120bbcbe3d6e93762d850f87c",
+ "url": "https://api.github.com/repos/utopia-php/queue/zipball/32b6f84c55aae761db5a5ae76cc91ca8dbc8bc32",
+ "reference": "32b6f84c55aae761db5a5ae76cc91ca8dbc8bc32",
"shasum": ""
},
"require": {
"php": ">=8.3",
"php-amqplib/php-amqplib": "^3.7",
"utopia-php/cli": "0.15.*",
- "utopia-php/fetch": "^0.3.0",
+ "utopia-php/fetch": "0.4.*",
"utopia-php/framework": "0.33.*",
"utopia-php/telemetry": "0.1.*"
},
@@ -4549,9 +4271,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/queue/issues",
- "source": "https://github.com/utopia-php/queue/tree/0.8.6"
+ "source": "https://github.com/utopia-php/queue/tree/0.9.1"
},
- "time": "2025-02-10T03:35:00+00:00"
+ "time": "2025-03-28T19:49:36+00:00"
},
{
"name": "utopia-php/registry",
@@ -4607,22 +4329,24 @@
},
{
"name": "utopia-php/storage",
- "version": "0.18.9",
+ "version": "0.18.10",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/storage.git",
- "reference": "1cf455404e8700b3093fd73d74a38d41cdced90c"
+ "reference": "76f31158f4251abb207f7a9b16f7cb0bfdb3b39e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/storage/zipball/1cf455404e8700b3093fd73d74a38d41cdced90c",
- "reference": "1cf455404e8700b3093fd73d74a38d41cdced90c",
+ "url": "https://api.github.com/repos/utopia-php/storage/zipball/76f31158f4251abb207f7a9b16f7cb0bfdb3b39e",
+ "reference": "76f31158f4251abb207f7a9b16f7cb0bfdb3b39e",
"shasum": ""
},
"require": {
"ext-brotli": "*",
+ "ext-curl": "*",
"ext-fileinfo": "*",
"ext-lz4": "*",
+ "ext-simplexml": "*",
"ext-snappy": "*",
"ext-xz": "*",
"ext-zlib": "*",
@@ -4656,22 +4380,22 @@
],
"support": {
"issues": "https://github.com/utopia-php/storage/issues",
- "source": "https://github.com/utopia-php/storage/tree/0.18.9"
+ "source": "https://github.com/utopia-php/storage/tree/0.18.10"
},
- "time": "2025-02-11T13:10:40+00:00"
+ "time": "2025-03-03T10:47:54+00:00"
},
{
"name": "utopia-php/swoole",
- "version": "0.8.2",
+ "version": "0.8.3",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/swoole.git",
- "reference": "5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4"
+ "reference": "1af73dd3e73987cf729c7db399054e4a70befd99"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/swoole/zipball/5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4",
- "reference": "5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4",
+ "url": "https://api.github.com/repos/utopia-php/swoole/zipball/1af73dd3e73987cf729c7db399054e4a70befd99",
+ "reference": "1af73dd3e73987cf729c7db399054e4a70befd99",
"shasum": ""
},
"require": {
@@ -4707,9 +4431,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/swoole/issues",
- "source": "https://github.com/utopia-php/swoole/tree/0.8.2"
+ "source": "https://github.com/utopia-php/swoole/tree/0.8.3"
},
- "time": "2024-02-01T14:54:12+00:00"
+ "time": "2025-03-26T10:09:05+00:00"
},
{
"name": "utopia-php/system",
@@ -4769,16 +4493,16 @@
},
{
"name": "utopia-php/telemetry",
- "version": "0.1.0",
+ "version": "0.1.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/telemetry.git",
- "reference": "d35f2f0632f4ee0be63fb7ace6a94a6adda71a80"
+ "reference": "437f0021777f0e575dfb9e8a1a081b3aed75e33f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/telemetry/zipball/d35f2f0632f4ee0be63fb7ace6a94a6adda71a80",
- "reference": "d35f2f0632f4ee0be63fb7ace6a94a6adda71a80",
+ "url": "https://api.github.com/repos/utopia-php/telemetry/zipball/437f0021777f0e575dfb9e8a1a081b3aed75e33f",
+ "reference": "437f0021777f0e575dfb9e8a1a081b3aed75e33f",
"shasum": ""
},
"require": {
@@ -4799,7 +4523,7 @@
"type": "library",
"autoload": {
"psr-4": {
- "Utopia\\": "src/"
+ "Utopia\\Telemetry\\": "src/Telemetry"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -4813,29 +4537,30 @@
],
"support": {
"issues": "https://github.com/utopia-php/telemetry/issues",
- "source": "https://github.com/utopia-php/telemetry/tree/0.1.0"
+ "source": "https://github.com/utopia-php/telemetry/tree/0.1.1"
},
- "time": "2024-11-13T10:29:53+00:00"
+ "time": "2025-03-17T11:57:52+00:00"
},
{
"name": "utopia-php/vcs",
- "version": "0.8.6",
+ "version": "0.9.4",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/vcs.git",
- "reference": "b10225f54d5670f09f83e82e09de9d820ada6931"
+ "reference": "1a8d280b176acc99ea8d9e7364b8767cbb206b4a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/vcs/zipball/b10225f54d5670f09f83e82e09de9d820ada6931",
- "reference": "b10225f54d5670f09f83e82e09de9d820ada6931",
+ "url": "https://api.github.com/repos/utopia-php/vcs/zipball/1a8d280b176acc99ea8d9e7364b8767cbb206b4a",
+ "reference": "1a8d280b176acc99ea8d9e7364b8767cbb206b4a",
"shasum": ""
},
"require": {
"adhocore/jwt": "^1.1",
"php": ">=8.0",
- "utopia-php/cache": "^0.11.0",
- "utopia-php/framework": "0.*.*"
+ "utopia-php/cache": "0.12.*",
+ "utopia-php/framework": "0.*.*",
+ "utopia-php/system": "0.9.*"
},
"require-dev": {
"laravel/pint": "1.2.*",
@@ -4862,33 +4587,34 @@
],
"support": {
"issues": "https://github.com/utopia-php/vcs/issues",
- "source": "https://github.com/utopia-php/vcs/tree/0.8.6"
+ "source": "https://github.com/utopia-php/vcs/tree/0.9.4"
},
- "time": "2024-12-10T13:13:23+00:00"
+ "time": "2025-03-13T10:09:45+00:00"
},
{
"name": "utopia-php/websocket",
- "version": "0.1.0",
+ "version": "0.3.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/websocket.git",
- "reference": "51fcb86171400d8aa40d76c54593481fd273dab5"
+ "reference": "629e53640b108eab43c7cc9ab375efade8622d43"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/websocket/zipball/51fcb86171400d8aa40d76c54593481fd273dab5",
- "reference": "51fcb86171400d8aa40d76c54593481fd273dab5",
+ "url": "https://api.github.com/repos/utopia-php/websocket/zipball/629e53640b108eab43c7cc9ab375efade8622d43",
+ "reference": "629e53640b108eab43c7cc9ab375efade8622d43",
"shasum": ""
},
"require": {
"php": ">=8.0"
},
"require-dev": {
+ "laravel/pint": "^1.15",
+ "phpstan/phpstan": "^1.12",
"phpunit/phpunit": "^9.5.5",
- "swoole/ide-helper": "4.6.6",
+ "swoole/ide-helper": "5.1.2",
"textalk/websocket": "1.5.2",
- "vimeo/psalm": "^4.8.1",
- "workerman/workerman": "^4.0"
+ "workerman/workerman": "4.1.*"
},
"type": "library",
"autoload": {
@@ -4900,16 +4626,6 @@
"license": [
"MIT"
],
- "authors": [
- {
- "name": "Eldad Fux",
- "email": "eldad@appwrite.io"
- },
- {
- "name": "Torsten Dittmann",
- "email": "torsten@appwrite.io"
- }
- ],
"description": "A simple abstraction for WebSocket servers.",
"keywords": [
"framework",
@@ -4920,9 +4636,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/websocket/issues",
- "source": "https://github.com/utopia-php/websocket/tree/0.1.0"
+ "source": "https://github.com/utopia-php/websocket/tree/0.3.0"
},
- "time": "2021-12-20T10:50:09+00:00"
+ "time": "2025-03-28T01:11:13+00:00"
},
{
"name": "webmozart/assert",
@@ -5051,16 +4767,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
- "version": "0.40.0",
+ "version": "0.40.11",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
- "reference": "d2880132c900f64108d3e4484a6c1ed1bed2303c"
+ "reference": "0ec5f4a60c15e33e208bc3444ba6148b1d0f0027"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/d2880132c900f64108d3e4484a6c1ed1bed2303c",
- "reference": "d2880132c900f64108d3e4484a6c1ed1bed2303c",
+ "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/0ec5f4a60c15e33e208bc3444ba6148b1d0f0027",
+ "reference": "0ec5f4a60c15e33e208bc3444ba6148b1d0f0027",
"shasum": ""
},
"require": {
@@ -5096,9 +4812,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
- "source": "https://github.com/appwrite/sdk-generator/tree/0.40.0"
+ "source": "https://github.com/appwrite/sdk-generator/tree/0.40.11"
},
- "time": "2025-02-04T12:47:33+00:00"
+ "time": "2025-03-26T10:53:16+00:00"
},
{
"name": "doctrine/annotations",
@@ -5176,77 +4892,32 @@
},
"time": "2024-09-05T10:17:24+00:00"
},
- {
- "name": "doctrine/deprecations",
- "version": "1.1.4",
- "source": {
- "type": "git",
- "url": "https://github.com/doctrine/deprecations.git",
- "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9"
- },
- "dist": {
- "type": "zip",
- "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 || ^12",
- "phpstan/phpstan": "1.4.10 || 2.0.3",
- "phpstan/phpstan-phpunit": "^1.0 || ^2",
- "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
- "psr/log": "^1 || ^2 || ^3"
- },
- "suggest": {
- "psr/log": "Allows logging deprecations via PSR-3 logger implementation"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Doctrine\\Deprecations\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
- "homepage": "https://www.doctrine-project.org/",
- "support": {
- "issues": "https://github.com/doctrine/deprecations/issues",
- "source": "https://github.com/doctrine/deprecations/tree/1.1.4"
- },
- "time": "2024-12-07T21:18:45+00:00"
- },
{
"name": "doctrine/instantiator",
- "version": "1.5.0",
+ "version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
- "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b"
+ "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b",
- "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0",
+ "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0",
"shasum": ""
},
"require": {
- "php": "^7.1 || ^8.0"
+ "php": "^8.1"
},
"require-dev": {
- "doctrine/coding-standard": "^9 || ^11",
+ "doctrine/coding-standard": "^11",
"ext-pdo": "*",
"ext-phar": "*",
- "phpbench/phpbench": "^0.16 || ^1",
- "phpstan/phpstan": "^1.4",
- "phpstan/phpstan-phpunit": "^1",
- "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
- "vimeo/psalm": "^4.30 || ^5.4"
+ "phpbench/phpbench": "^1.2",
+ "phpstan/phpstan": "^1.9.4",
+ "phpstan/phpstan-phpunit": "^1.3",
+ "phpunit/phpunit": "^9.5.27",
+ "vimeo/psalm": "^5.4"
},
"type": "library",
"autoload": {
@@ -5273,7 +4944,7 @@
],
"support": {
"issues": "https://github.com/doctrine/instantiator/issues",
- "source": "https://github.com/doctrine/instantiator/tree/1.5.0"
+ "source": "https://github.com/doctrine/instantiator/tree/2.0.0"
},
"funding": [
{
@@ -5289,7 +4960,7 @@
"type": "tidelift"
}
],
- "time": "2022-12-30T00:15:36+00:00"
+ "time": "2022-12-30T00:23:10+00:00"
},
{
"name": "doctrine/lexer",
@@ -5370,16 +5041,16 @@
},
{
"name": "laravel/pint",
- "version": "v1.20.0",
+ "version": "v1.21.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
- "reference": "53072e8ea22213a7ed168a8a15b96fbb8b82d44b"
+ "reference": "370772e7d9e9da087678a0edf2b11b6960e40558"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/pint/zipball/53072e8ea22213a7ed168a8a15b96fbb8b82d44b",
- "reference": "53072e8ea22213a7ed168a8a15b96fbb8b82d44b",
+ "url": "https://api.github.com/repos/laravel/pint/zipball/370772e7d9e9da087678a0edf2b11b6960e40558",
+ "reference": "370772e7d9e9da087678a0edf2b11b6960e40558",
"shasum": ""
},
"require": {
@@ -5387,15 +5058,15 @@
"ext-mbstring": "*",
"ext-tokenizer": "*",
"ext-xml": "*",
- "php": "^8.1.0"
+ "php": "^8.2.0"
},
"require-dev": {
- "friendsofphp/php-cs-fixer": "^3.66.0",
- "illuminate/view": "^10.48.25",
- "larastan/larastan": "^2.9.12",
- "laravel-zero/framework": "^10.48.25",
+ "friendsofphp/php-cs-fixer": "^3.72.0",
+ "illuminate/view": "^11.44.2",
+ "larastan/larastan": "^3.2.0",
+ "laravel-zero/framework": "^11.36.1",
"mockery/mockery": "^1.6.12",
- "nunomaduro/termwind": "^1.17.0",
+ "nunomaduro/termwind": "^2.3",
"pestphp/pest": "^2.36.0"
},
"bin": [
@@ -5432,7 +5103,7 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
- "time": "2025-01-14T16:20:53+00:00"
+ "time": "2025-03-14T22:31:42+00:00"
},
{
"name": "matthiasmullie/minify",
@@ -5847,16 +5518,16 @@
},
{
"name": "phpbench/phpbench",
- "version": "1.4.0",
+ "version": "1.4.1",
"source": {
"type": "git",
"url": "https://github.com/phpbench/phpbench.git",
- "reference": "4248817222514421cba466bfa7adc7d8932345d4"
+ "reference": "78cd98a9aa34e0f8f80ca01972a8b88d2c30194b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpbench/phpbench/zipball/4248817222514421cba466bfa7adc7d8932345d4",
- "reference": "4248817222514421cba466bfa7adc7d8932345d4",
+ "url": "https://api.github.com/repos/phpbench/phpbench/zipball/78cd98a9aa34e0f8f80ca01972a8b88d2c30194b",
+ "reference": "78cd98a9aa34e0f8f80ca01972a8b88d2c30194b",
"shasum": ""
},
"require": {
@@ -5933,7 +5604,7 @@
],
"support": {
"issues": "https://github.com/phpbench/phpbench/issues",
- "source": "https://github.com/phpbench/phpbench/tree/1.4.0"
+ "source": "https://github.com/phpbench/phpbench/tree/1.4.1"
},
"funding": [
{
@@ -5941,299 +5612,7 @@
"type": "github"
}
],
- "time": "2025-01-26T19:54:45+00:00"
- },
- {
- "name": "phpdocumentor/reflection-common",
- "version": "2.2.0",
- "source": {
- "type": "git",
- "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
- "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
- "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
- "shasum": ""
- },
- "require": {
- "php": "^7.2 || ^8.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-2.x": "2.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "phpDocumentor\\Reflection\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Jaap van Otterdijk",
- "email": "opensource@ijaap.nl"
- }
- ],
- "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
- "homepage": "http://www.phpdoc.org",
- "keywords": [
- "FQSEN",
- "phpDocumentor",
- "phpdoc",
- "reflection",
- "static analysis"
- ],
- "support": {
- "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
- "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
- },
- "time": "2020-06-27T09:03:43+00:00"
- },
- {
- "name": "phpdocumentor/reflection-docblock",
- "version": "5.6.1",
- "source": {
- "type": "git",
- "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
- "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8",
- "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8",
- "shasum": ""
- },
- "require": {
- "doctrine/deprecations": "^1.1",
- "ext-filter": "*",
- "php": "^7.4 || ^8.0",
- "phpdocumentor/reflection-common": "^2.2",
- "phpdocumentor/type-resolver": "^1.7",
- "phpstan/phpdoc-parser": "^1.7|^2.0",
- "webmozart/assert": "^1.9.1"
- },
- "require-dev": {
- "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",
- "psalm/phar": "^5.26"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "5.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "phpDocumentor\\Reflection\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Mike van Riel",
- "email": "me@mikevanriel.com"
- },
- {
- "name": "Jaap van Otterdijk",
- "email": "opensource@ijaap.nl"
- }
- ],
- "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.6.1"
- },
- "time": "2024-12-07T09:39:29+00:00"
- },
- {
- "name": "phpdocumentor/type-resolver",
- "version": "1.10.0",
- "source": {
- "type": "git",
- "url": "https://github.com/phpDocumentor/TypeResolver.git",
- "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a"
- },
- "dist": {
- "type": "zip",
- "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.18|^2.0"
- },
- "require-dev": {
- "ext-tokenizer": "*",
- "phpbench/phpbench": "^1.2",
- "phpstan/extension-installer": "^1.1",
- "phpstan/phpstan": "^1.8",
- "phpstan/phpstan-phpunit": "^1.1",
- "phpunit/phpunit": "^9.5",
- "rector/rector": "^0.13.9",
- "vimeo/psalm": "^4.25"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-1.x": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "phpDocumentor\\Reflection\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Mike van Riel",
- "email": "me@mikevanriel.com"
- }
- ],
- "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.10.0"
- },
- "time": "2024-11-09T15:12:26+00:00"
- },
- {
- "name": "phpspec/prophecy",
- "version": "v1.20.0",
- "source": {
- "type": "git",
- "url": "https://github.com/phpspec/prophecy.git",
- "reference": "a0165c648cab6a80311c74ffc708a07bb53ecc93"
- },
- "dist": {
- "type": "zip",
- "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.* || 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"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Prophecy\\": "src/Prophecy"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Konstantin Kudryashov",
- "email": "ever.zet@gmail.com",
- "homepage": "http://everzet.com"
- },
- {
- "name": "Marcello Duarte",
- "email": "marcello.duarte@gmail.com"
- }
- ],
- "description": "Highly opinionated mocking framework for PHP 5.3+",
- "homepage": "https://github.com/phpspec/prophecy",
- "keywords": [
- "Double",
- "Dummy",
- "dev",
- "fake",
- "mock",
- "spy",
- "stub"
- ],
- "support": {
- "issues": "https://github.com/phpspec/prophecy/issues",
- "source": "https://github.com/phpspec/prophecy/tree/v1.20.0"
- },
- "time": "2024-11-19T13:12:41+00:00"
- },
- {
- "name": "phpstan/phpdoc-parser",
- "version": "2.0.1",
- "source": {
- "type": "git",
- "url": "https://github.com/phpstan/phpdoc-parser.git",
- "reference": "72e51f7c32c5aef7c8b462195b8c599b11199893"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/72e51f7c32c5aef7c8b462195b8c599b11199893",
- "reference": "72e51f7c32c5aef7c8b462195b8c599b11199893",
- "shasum": ""
- },
- "require": {
- "php": "^7.4 || ^8.0"
- },
- "require-dev": {
- "doctrine/annotations": "^2.0",
- "nikic/php-parser": "^5.3.0",
- "php-parallel-lint/php-parallel-lint": "^1.2",
- "phpstan/extension-installer": "^1.0",
- "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",
- "autoload": {
- "psr-4": {
- "PHPStan\\PhpDocParser\\": [
- "src/"
- ]
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "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/2.0.1"
- },
- "time": "2025-02-13T12:25:43+00:00"
+ "time": "2025-03-12T08:01:40+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -6556,55 +5935,50 @@
},
{
"name": "phpunit/phpunit",
- "version": "9.5.20",
+ "version": "9.6.22",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba"
+ "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/12bc8879fb65aef2138b26fc633cb1e3620cffba",
- "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f80235cb4d3caa59ae09be3adf1ded27521d1a9c",
+ "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c",
"shasum": ""
},
"require": {
- "doctrine/instantiator": "^1.3.1",
+ "doctrine/instantiator": "^1.5.0 || ^2",
"ext-dom": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-xml": "*",
"ext-xmlwriter": "*",
- "myclabs/deep-copy": "^1.10.1",
- "phar-io/manifest": "^2.0.3",
- "phar-io/version": "^3.0.2",
+ "myclabs/deep-copy": "^1.12.1",
+ "phar-io/manifest": "^2.0.4",
+ "phar-io/version": "^3.2.1",
"php": ">=7.3",
- "phpspec/prophecy": "^1.12.1",
- "phpunit/php-code-coverage": "^9.2.13",
- "phpunit/php-file-iterator": "^3.0.5",
+ "phpunit/php-code-coverage": "^9.2.32",
+ "phpunit/php-file-iterator": "^3.0.6",
"phpunit/php-invoker": "^3.1.1",
- "phpunit/php-text-template": "^2.0.3",
- "phpunit/php-timer": "^5.0.2",
- "sebastian/cli-parser": "^1.0.1",
- "sebastian/code-unit": "^1.0.6",
- "sebastian/comparator": "^4.0.5",
- "sebastian/diff": "^4.0.3",
- "sebastian/environment": "^5.1.3",
- "sebastian/exporter": "^4.0.3",
- "sebastian/global-state": "^5.0.1",
- "sebastian/object-enumerator": "^4.0.3",
- "sebastian/resource-operations": "^3.0.3",
- "sebastian/type": "^3.0",
+ "phpunit/php-text-template": "^2.0.4",
+ "phpunit/php-timer": "^5.0.3",
+ "sebastian/cli-parser": "^1.0.2",
+ "sebastian/code-unit": "^1.0.8",
+ "sebastian/comparator": "^4.0.8",
+ "sebastian/diff": "^4.0.6",
+ "sebastian/environment": "^5.1.5",
+ "sebastian/exporter": "^4.0.6",
+ "sebastian/global-state": "^5.0.7",
+ "sebastian/object-enumerator": "^4.0.4",
+ "sebastian/resource-operations": "^3.0.4",
+ "sebastian/type": "^3.2.1",
"sebastian/version": "^3.0.2"
},
- "require-dev": {
- "ext-pdo": "*",
- "phpspec/prophecy-phpunit": "^2.0.1"
- },
"suggest": {
- "ext-soap": "*",
- "ext-xdebug": "*"
+ "ext-soap": "To be able to generate mocks based on WSDL files",
+ "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
},
"bin": [
"phpunit"
@@ -6612,7 +5986,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "9.5-dev"
+ "dev-master": "9.6-dev"
}
},
"autoload": {
@@ -6643,7 +6017,8 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.20"
+ "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.22"
},
"funding": [
{
@@ -6653,9 +6028,13 @@
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
+ "type": "tidelift"
}
],
- "time": "2022-04-01T12:37:26+00:00"
+ "time": "2024-12-05T13:48:26+00:00"
},
{
"name": "psr/cache",
@@ -7767,16 +7146,16 @@
},
{
"name": "symfony/console",
- "version": "v7.2.1",
+ "version": "v7.2.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3"
+ "reference": "e51498ea18570c062e7df29d05a7003585b19b88"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
- "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
+ "url": "https://api.github.com/repos/symfony/console/zipball/e51498ea18570c062e7df29d05a7003585b19b88",
+ "reference": "e51498ea18570c062e7df29d05a7003585b19b88",
"shasum": ""
},
"require": {
@@ -7840,7 +7219,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v7.2.1"
+ "source": "https://github.com/symfony/console/tree/v7.2.5"
},
"funding": [
{
@@ -7856,7 +7235,7 @@
"type": "tidelift"
}
],
- "time": "2024-12-11T03:49:26+00:00"
+ "time": "2025-03-12T08:11:12+00:00"
},
{
"name": "symfony/filesystem",
@@ -8371,16 +7750,16 @@
},
{
"name": "symfony/process",
- "version": "v7.2.0",
+ "version": "v7.2.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e"
+ "reference": "87b7c93e57df9d8e39a093d32587702380ff045d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/d34b22ba9390ec19d2dd966c40aa9e8462f27a7e",
- "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e",
+ "url": "https://api.github.com/repos/symfony/process/zipball/87b7c93e57df9d8e39a093d32587702380ff045d",
+ "reference": "87b7c93e57df9d8e39a093d32587702380ff045d",
"shasum": ""
},
"require": {
@@ -8412,7 +7791,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/process/tree/v7.2.0"
+ "source": "https://github.com/symfony/process/tree/v7.2.5"
},
"funding": [
{
@@ -8428,7 +7807,7 @@
"type": "tidelift"
}
],
- "time": "2024-11-06T14:24:19+00:00"
+ "time": "2025-03-13T12:21:46+00:00"
},
{
"name": "symfony/string",
@@ -8519,16 +7898,16 @@
},
{
"name": "textalk/websocket",
- "version": "1.5.7",
+ "version": "1.5.8",
"source": {
"type": "git",
"url": "https://github.com/Textalk/websocket-php.git",
- "reference": "1712325e99b6bf869ccbf9bf41ab749e7328ea46"
+ "reference": "d05dbaa97500176447ffb1f1800573f23085ab13"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Textalk/websocket-php/zipball/1712325e99b6bf869ccbf9bf41ab749e7328ea46",
- "reference": "1712325e99b6bf869ccbf9bf41ab749e7328ea46",
+ "url": "https://api.github.com/repos/Textalk/websocket-php/zipball/d05dbaa97500176447ffb1f1800573f23085ab13",
+ "reference": "d05dbaa97500176447ffb1f1800573f23085ab13",
"shasum": ""
},
"require": {
@@ -8562,9 +7941,9 @@
"description": "WebSocket client and server",
"support": {
"issues": "https://github.com/Textalk/websocket-php/issues",
- "source": "https://github.com/Textalk/websocket-php/tree/1.5.7"
+ "source": "https://github.com/Textalk/websocket-php/tree/1.5.8"
},
- "time": "2022-03-29T09:46:59+00:00"
+ "time": "2022-04-26T06:28:24+00:00"
},
{
"name": "theseer/tokenizer",
diff --git a/docker-compose.yml b/docker-compose.yml
index facf0e6db9..9bd55becd1 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -72,6 +72,7 @@ services:
- traefik.http.routers.appwrite_api_https.tls=true
volumes:
- appwrite-uploads:/storage/uploads:rw
+ - appwrite-imports:/storage/imports:rw
- appwrite-cache:/storage/cache:rw
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
@@ -171,6 +172,7 @@ services:
- _APP_MAINTENANCE_RETENTION_CACHE
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
+ - _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
- _APP_MAINTENANCE_RETENTION_SCHEDULES
- _APP_SMS_PROVIDER
@@ -197,11 +199,13 @@ services:
- _APP_DATABASE_SHARED_TABLES_V1
- _APP_DATABASE_SHARED_NAMESPACE
- _APP_FUNCTIONS_CREATION_ABUSE_LIMIT
+ extra_hosts:
+ - "host.docker.internal:host-gateway"
appwrite-console:
<<: *x-logging
container_name: appwrite-console
- image: appwrite/console:5.2.27
+ image: appwrite/console:5.2.56
restart: unless-stopped
networks:
- appwrite
@@ -389,6 +393,8 @@ services:
- _APP_DATABASE_SHARED_TABLES
- _APP_DATABASE_SHARED_TABLES_V1
- _APP_EMAIL_CERTIFICATES
+ - _APP_MAINTENANCE_RETENTION_AUDIT
+ - _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE
appwrite-worker-databases:
entrypoint: worker-databases
@@ -486,6 +492,8 @@ services:
- _APP_STORAGE_WASABI_REGION
- _APP_STORAGE_WASABI_BUCKET
- _APP_DATABASE_SHARED_TABLES
+ extra_hosts:
+ - "host.docker.internal:host-gateway"
appwrite-worker-certificates:
entrypoint: worker-certificates
@@ -662,6 +670,7 @@ services:
networks:
- appwrite
volumes:
+ - appwrite-imports:/storage/imports:rw
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
- ./tests:/usr/src/code/tests
@@ -721,6 +730,7 @@ services:
- _APP_MAINTENANCE_RETENTION_CACHE
- _APP_MAINTENANCE_RETENTION_ABUSE
- _APP_MAINTENANCE_RETENTION_AUDIT
+ - _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
- _APP_MAINTENANCE_RETENTION_SCHEDULES
- _APP_MAINTENANCE_DELAY
@@ -756,7 +766,7 @@ services:
- _APP_LOGGING_CONFIG
- _APP_DATABASE_SHARED_TABLES
- _APP_STATS_RESOURCES_INTERVAL
-
+
appwrite-worker-stats-resources:
entrypoint: worker-stats-resources
<<: *x-logging
@@ -787,7 +797,7 @@ services:
- _APP_LOGGING_CONFIG
- _APP_USAGE_AGGREGATION_INTERVAL
- _APP_DATABASE_SHARED_TABLES
-
+
appwrite-worker-stats-usage:
entrypoint: worker-stats-usage
<<: *x-logging
@@ -850,7 +860,7 @@ services:
- _APP_USAGE_AGGREGATION_INTERVAL
- _APP_DATABASE_SHARED_TABLES
- _APP_STATS_USAGE_DUAL_WRITING_DBS
-
+
appwrite-task-scheduler-functions:
entrypoint: schedule-functions
<<: *x-logging
@@ -1124,7 +1134,8 @@ volumes:
appwrite-redis:
appwrite-cache:
appwrite-uploads:
+ appwrite-imports:
appwrite-certificates:
appwrite-functions:
appwrite-builds:
- appwrite-config:
\ No newline at end of file
+ appwrite-config:
diff --git a/docs/examples/1.6.x/client-android/java/functions/get-deployment-download.md b/docs/examples/1.6.x/client-android/java/functions/get-deployment-download.md
deleted file mode 100644
index 200cdf2fc5..0000000000
--- a/docs/examples/1.6.x/client-android/java/functions/get-deployment-download.md
+++ /dev/null
@@ -1,23 +0,0 @@
-import io.appwrite.Client;
-import io.appwrite.coroutines.CoroutineCallback;
-import io.appwrite.services.Functions;
-
-Client client = new Client(context)
- .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- .setProject(""); // Your project ID
-
-Functions functions = new Functions(client);
-
-functions.getDeploymentDownload(
- "", // functionId
- "", // deploymentId
- new CoroutineCallback<>((result, error) -> {
- if (error != null) {
- error.printStackTrace();
- return;
- }
-
- Log.d("Appwrite", result.toString());
- })
-);
-
diff --git a/docs/examples/1.6.x/client-android/java/functions/get-template.md b/docs/examples/1.6.x/client-android/java/functions/get-template.md
deleted file mode 100644
index af9efc7b92..0000000000
--- a/docs/examples/1.6.x/client-android/java/functions/get-template.md
+++ /dev/null
@@ -1,22 +0,0 @@
-import io.appwrite.Client;
-import io.appwrite.coroutines.CoroutineCallback;
-import io.appwrite.services.Functions;
-
-Client client = new Client(context)
- .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- .setProject(""); // Your project ID
-
-Functions functions = new Functions(client);
-
-functions.getTemplate(
- "", // templateId
- new CoroutineCallback<>((result, error) -> {
- if (error != null) {
- error.printStackTrace();
- return;
- }
-
- Log.d("Appwrite", result.toString());
- })
-);
-
diff --git a/docs/examples/1.6.x/client-android/java/functions/list-templates.md b/docs/examples/1.6.x/client-android/java/functions/list-templates.md
deleted file mode 100644
index 6c051f3b04..0000000000
--- a/docs/examples/1.6.x/client-android/java/functions/list-templates.md
+++ /dev/null
@@ -1,25 +0,0 @@
-import io.appwrite.Client;
-import io.appwrite.coroutines.CoroutineCallback;
-import io.appwrite.services.Functions;
-
-Client client = new Client(context)
- .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- .setProject(""); // Your project ID
-
-Functions functions = new Functions(client);
-
-functions.listTemplates(
- listOf(), // runtimes (optional)
- listOf(), // useCases (optional)
- 1, // limit (optional)
- 0, // offset (optional)
- new CoroutineCallback<>((result, error) -> {
- if (error != null) {
- error.printStackTrace();
- return;
- }
-
- Log.d("Appwrite", result.toString());
- })
-);
-
diff --git a/docs/examples/1.6.x/client-android/kotlin/functions/get-deployment-download.md b/docs/examples/1.6.x/client-android/kotlin/functions/get-deployment-download.md
deleted file mode 100644
index 21e99cb731..0000000000
--- a/docs/examples/1.6.x/client-android/kotlin/functions/get-deployment-download.md
+++ /dev/null
@@ -1,14 +0,0 @@
-import io.appwrite.Client
-import io.appwrite.coroutines.CoroutineCallback
-import io.appwrite.services.Functions
-
-val client = Client(context)
- .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- .setProject("") // Your project ID
-
-val functions = Functions(client)
-
-val result = functions.getDeploymentDownload(
- functionId = "",
- deploymentId = "",
-)
\ No newline at end of file
diff --git a/docs/examples/1.6.x/client-android/kotlin/functions/get-template.md b/docs/examples/1.6.x/client-android/kotlin/functions/get-template.md
deleted file mode 100644
index 4af83bda3e..0000000000
--- a/docs/examples/1.6.x/client-android/kotlin/functions/get-template.md
+++ /dev/null
@@ -1,13 +0,0 @@
-import io.appwrite.Client
-import io.appwrite.coroutines.CoroutineCallback
-import io.appwrite.services.Functions
-
-val client = Client(context)
- .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- .setProject("") // Your project ID
-
-val functions = Functions(client)
-
-val result = functions.getTemplate(
- templateId = "",
-)
\ No newline at end of file
diff --git a/docs/examples/1.6.x/client-android/kotlin/functions/list-templates.md b/docs/examples/1.6.x/client-android/kotlin/functions/list-templates.md
deleted file mode 100644
index a1a59d9438..0000000000
--- a/docs/examples/1.6.x/client-android/kotlin/functions/list-templates.md
+++ /dev/null
@@ -1,16 +0,0 @@
-import io.appwrite.Client
-import io.appwrite.coroutines.CoroutineCallback
-import io.appwrite.services.Functions
-
-val client = Client(context)
- .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- .setProject("") // Your project ID
-
-val functions = Functions(client)
-
-val result = functions.listTemplates(
- runtimes = listOf(), // (optional)
- useCases = listOf(), // (optional)
- limit = 1, // (optional)
- offset = 0, // (optional)
-)
\ No newline at end of file
diff --git a/docs/examples/1.6.x/client-apple/examples/account/update-mfa-challenge.md b/docs/examples/1.6.x/client-apple/examples/account/update-mfa-challenge.md
index a237537ae3..1c5874f784 100644
--- a/docs/examples/1.6.x/client-apple/examples/account/update-mfa-challenge.md
+++ b/docs/examples/1.6.x/client-apple/examples/account/update-mfa-challenge.md
@@ -6,7 +6,7 @@ let client = Client()
let account = Account(client)
-let result = try await account.updateMfaChallenge(
+let session = try await account.updateMfaChallenge(
challengeId: "",
otp: ""
)
diff --git a/docs/examples/1.6.x/client-apple/examples/functions/get-deployment-download.md b/docs/examples/1.6.x/client-apple/examples/functions/get-deployment-download.md
deleted file mode 100644
index 0e6659969c..0000000000
--- a/docs/examples/1.6.x/client-apple/examples/functions/get-deployment-download.md
+++ /dev/null
@@ -1,13 +0,0 @@
-import Appwrite
-
-let client = Client()
- .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- .setProject("") // Your project ID
-
-let functions = Functions(client)
-
-let bytes = try await functions.getDeploymentDownload(
- functionId: "",
- deploymentId: ""
-)
-
diff --git a/docs/examples/1.6.x/client-apple/examples/functions/get-template.md b/docs/examples/1.6.x/client-apple/examples/functions/get-template.md
deleted file mode 100644
index bc7a9a3aef..0000000000
--- a/docs/examples/1.6.x/client-apple/examples/functions/get-template.md
+++ /dev/null
@@ -1,12 +0,0 @@
-import Appwrite
-
-let client = Client()
- .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- .setProject("") // Your project ID
-
-let functions = Functions(client)
-
-let templateFunction = try await functions.getTemplate(
- templateId: ""
-)
-
diff --git a/docs/examples/1.6.x/client-apple/examples/functions/list-templates.md b/docs/examples/1.6.x/client-apple/examples/functions/list-templates.md
deleted file mode 100644
index d0090ab803..0000000000
--- a/docs/examples/1.6.x/client-apple/examples/functions/list-templates.md
+++ /dev/null
@@ -1,15 +0,0 @@
-import Appwrite
-
-let client = Client()
- .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- .setProject("") // Your project ID
-
-let functions = Functions(client)
-
-let templateFunctionList = try await functions.listTemplates(
- runtimes: [], // optional
- useCases: [], // optional
- limit: 1, // optional
- offset: 0 // optional
-)
-
diff --git a/docs/examples/1.6.x/client-flutter/examples/account/update-mfa-challenge.md b/docs/examples/1.6.x/client-flutter/examples/account/update-mfa-challenge.md
index c8e1af7e90..bbe7c03470 100644
--- a/docs/examples/1.6.x/client-flutter/examples/account/update-mfa-challenge.md
+++ b/docs/examples/1.6.x/client-flutter/examples/account/update-mfa-challenge.md
@@ -6,7 +6,7 @@ Client client = Client()
Account account = Account(client);
- result = await account.updateMfaChallenge(
+Session result = await account.updateMfaChallenge(
challengeId: '',
otp: '',
);
diff --git a/docs/examples/1.6.x/client-flutter/examples/functions/get-deployment-download.md b/docs/examples/1.6.x/client-flutter/examples/functions/get-deployment-download.md
deleted file mode 100644
index bb0ee903df..0000000000
--- a/docs/examples/1.6.x/client-flutter/examples/functions/get-deployment-download.md
+++ /dev/null
@@ -1,29 +0,0 @@
-import 'package:appwrite/appwrite.dart';
-
-Client client = Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject(''); // Your project ID
-
-Functions functions = Functions(client);
-
-// Downloading file
-UInt8List bytes = await functions.getDeploymentDownload(
- functionId: '',
- deploymentId: '',
-)
-
-final file = File('path_to_file/filename.ext');
-file.writeAsBytesSync(bytes);
-
-// Displaying image preview
-FutureBuilder(
- future: functions.getDeploymentDownload(
- functionId:'' ,
- deploymentId:'' ,
-), // Works for both public file and private file, for private files you need to be logged in
- builder: (context, snapshot) {
- return snapshot.hasData && snapshot.data != null
- ? Image.memory(snapshot.data)
- : CircularProgressIndicator();
- }
-);
diff --git a/docs/examples/1.6.x/client-flutter/examples/functions/get-template.md b/docs/examples/1.6.x/client-flutter/examples/functions/get-template.md
deleted file mode 100644
index 560a4d94ed..0000000000
--- a/docs/examples/1.6.x/client-flutter/examples/functions/get-template.md
+++ /dev/null
@@ -1,11 +0,0 @@
-import 'package:appwrite/appwrite.dart';
-
-Client client = Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject(''); // Your project ID
-
-Functions functions = Functions(client);
-
-TemplateFunction result = await functions.getTemplate(
- templateId: '',
-);
diff --git a/docs/examples/1.6.x/client-flutter/examples/functions/list-templates.md b/docs/examples/1.6.x/client-flutter/examples/functions/list-templates.md
deleted file mode 100644
index d3d8c7aa4c..0000000000
--- a/docs/examples/1.6.x/client-flutter/examples/functions/list-templates.md
+++ /dev/null
@@ -1,14 +0,0 @@
-import 'package:appwrite/appwrite.dart';
-
-Client client = Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject(''); // Your project ID
-
-Functions functions = Functions(client);
-
-TemplateFunctionList result = await functions.listTemplates(
- runtimes: [], // optional
- useCases: [], // optional
- limit: 1, // optional
- offset: 0, // optional
-);
diff --git a/docs/examples/1.6.x/client-graphql/examples/account/update-mfa-challenge.md b/docs/examples/1.6.x/client-graphql/examples/account/update-mfa-challenge.md
index 8237431bcf..0bcec2157f 100644
--- a/docs/examples/1.6.x/client-graphql/examples/account/update-mfa-challenge.md
+++ b/docs/examples/1.6.x/client-graphql/examples/account/update-mfa-challenge.md
@@ -3,6 +3,34 @@ mutation {
challengeId: "",
otp: ""
) {
- status
+ _id
+ _createdAt
+ _updatedAt
+ userId
+ expire
+ provider
+ providerUid
+ providerAccessToken
+ providerAccessTokenExpiry
+ providerRefreshToken
+ ip
+ osCode
+ osName
+ osVersion
+ clientType
+ clientCode
+ clientName
+ clientVersion
+ clientEngine
+ clientEngineVersion
+ deviceName
+ deviceBrand
+ deviceModel
+ countryCode
+ countryName
+ current
+ factors
+ secret
+ mfaUpdatedAt
}
}
diff --git a/docs/examples/1.6.x/client-graphql/examples/functions/get-deployment-download.md b/docs/examples/1.6.x/client-graphql/examples/functions/get-deployment-download.md
deleted file mode 100644
index 396047bb94..0000000000
--- a/docs/examples/1.6.x/client-graphql/examples/functions/get-deployment-download.md
+++ /dev/null
@@ -1,8 +0,0 @@
-query {
- functionsGetDeploymentDownload(
- functionId: "",
- deploymentId: ""
- ) {
- status
- }
-}
diff --git a/docs/examples/1.6.x/client-graphql/examples/functions/get-template.md b/docs/examples/1.6.x/client-graphql/examples/functions/get-template.md
deleted file mode 100644
index 44a466de27..0000000000
--- a/docs/examples/1.6.x/client-graphql/examples/functions/get-template.md
+++ /dev/null
@@ -1,35 +0,0 @@
-query {
- functionsGetTemplate(
- templateId: ""
- ) {
- icon
- id
- name
- tagline
- permissions
- events
- cron
- timeout
- useCases
- runtimes {
- name
- commands
- entrypoint
- providerRootDirectory
- }
- instructions
- vcsProvider
- providerRepositoryId
- providerOwner
- providerVersion
- variables {
- name
- description
- value
- placeholder
- required
- type
- }
- scopes
- }
-}
diff --git a/docs/examples/1.6.x/client-graphql/examples/functions/list-templates.md b/docs/examples/1.6.x/client-graphql/examples/functions/list-templates.md
deleted file mode 100644
index cb289ddabb..0000000000
--- a/docs/examples/1.6.x/client-graphql/examples/functions/list-templates.md
+++ /dev/null
@@ -1,41 +0,0 @@
-query {
- functionsListTemplates(
- runtimes: [],
- useCases: [],
- limit: 1,
- offset: 0
- ) {
- total
- templates {
- icon
- id
- name
- tagline
- permissions
- events
- cron
- timeout
- useCases
- runtimes {
- name
- commands
- entrypoint
- providerRootDirectory
- }
- instructions
- vcsProvider
- providerRepositoryId
- providerOwner
- providerVersion
- variables {
- name
- description
- value
- placeholder
- required
- type
- }
- scopes
- }
- }
-}
diff --git a/docs/examples/1.6.x/client-react-native/examples/functions/get-deployment-download.md b/docs/examples/1.6.x/client-react-native/examples/functions/get-deployment-download.md
deleted file mode 100644
index 7eac2b753f..0000000000
--- a/docs/examples/1.6.x/client-react-native/examples/functions/get-deployment-download.md
+++ /dev/null
@@ -1,14 +0,0 @@
-import { Client, Functions } from "react-native-appwrite";
-
-const client = new Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject(''); // Your project ID
-
-const functions = new Functions(client);
-
-const result = functions.getDeploymentDownload(
- '', // functionId
- '' // deploymentId
-);
-
-console.log(result);
diff --git a/docs/examples/1.6.x/client-react-native/examples/functions/get-template.md b/docs/examples/1.6.x/client-react-native/examples/functions/get-template.md
deleted file mode 100644
index ea756323dc..0000000000
--- a/docs/examples/1.6.x/client-react-native/examples/functions/get-template.md
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Client, Functions } from "react-native-appwrite";
-
-const client = new Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject(''); // Your project ID
-
-const functions = new Functions(client);
-
-const result = await functions.getTemplate(
- '' // templateId
-);
-
-console.log(result);
diff --git a/docs/examples/1.6.x/client-react-native/examples/functions/list-templates.md b/docs/examples/1.6.x/client-react-native/examples/functions/list-templates.md
deleted file mode 100644
index 3811dd8e40..0000000000
--- a/docs/examples/1.6.x/client-react-native/examples/functions/list-templates.md
+++ /dev/null
@@ -1,16 +0,0 @@
-import { Client, Functions } from "react-native-appwrite";
-
-const client = new Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject(''); // Your project ID
-
-const functions = new Functions(client);
-
-const result = await functions.listTemplates(
- [], // runtimes (optional)
- [], // useCases (optional)
- 1, // limit (optional)
- 0 // offset (optional)
-);
-
-console.log(result);
diff --git a/docs/examples/1.6.x/client-rest/examples/functions/get-deployment-download.md b/docs/examples/1.6.x/client-rest/examples/functions/get-deployment-download.md
deleted file mode 100644
index 0f6166b77f..0000000000
--- a/docs/examples/1.6.x/client-rest/examples/functions/get-deployment-download.md
+++ /dev/null
@@ -1,8 +0,0 @@
-GET /v1/functions/{functionId}/deployments/{deploymentId}/download HTTP/1.1
-Host: cloud.appwrite.io
-Content-Type: application/json
-X-Appwrite-Response-Format: 1.6.0
-X-Appwrite-Project:
-X-Appwrite-Session:
-X-Appwrite-JWT:
-
diff --git a/docs/examples/1.6.x/client-rest/examples/functions/get-template.md b/docs/examples/1.6.x/client-rest/examples/functions/get-template.md
deleted file mode 100644
index d4e1f95cf5..0000000000
--- a/docs/examples/1.6.x/client-rest/examples/functions/get-template.md
+++ /dev/null
@@ -1,6 +0,0 @@
-GET /v1/functions/templates/{templateId} HTTP/1.1
-Host: cloud.appwrite.io
-Content-Type: application/json
-X-Appwrite-Response-Format: 1.6.0
-X-Appwrite-Project:
-
diff --git a/docs/examples/1.6.x/client-rest/examples/functions/list-templates.md b/docs/examples/1.6.x/client-rest/examples/functions/list-templates.md
deleted file mode 100644
index b671bedebf..0000000000
--- a/docs/examples/1.6.x/client-rest/examples/functions/list-templates.md
+++ /dev/null
@@ -1,6 +0,0 @@
-GET /v1/functions/templates HTTP/1.1
-Host: cloud.appwrite.io
-Content-Type: application/json
-X-Appwrite-Response-Format: 1.6.0
-X-Appwrite-Project:
-
diff --git a/docs/examples/1.6.x/client-web/examples/functions/get-deployment-download.md b/docs/examples/1.6.x/client-web/examples/functions/get-deployment-download.md
deleted file mode 100644
index 62e3ee6551..0000000000
--- a/docs/examples/1.6.x/client-web/examples/functions/get-deployment-download.md
+++ /dev/null
@@ -1,14 +0,0 @@
-import { Client, Functions } from "appwrite";
-
-const client = new Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject(''); // Your project ID
-
-const functions = new Functions(client);
-
-const result = functions.getDeploymentDownload(
- '', // functionId
- '' // deploymentId
-);
-
-console.log(result);
diff --git a/docs/examples/1.6.x/client-web/examples/functions/get-template.md b/docs/examples/1.6.x/client-web/examples/functions/get-template.md
deleted file mode 100644
index dd2d20284e..0000000000
--- a/docs/examples/1.6.x/client-web/examples/functions/get-template.md
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Client, Functions } from "appwrite";
-
-const client = new Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject(''); // Your project ID
-
-const functions = new Functions(client);
-
-const result = await functions.getTemplate(
- '' // templateId
-);
-
-console.log(result);
diff --git a/docs/examples/1.6.x/client-web/examples/functions/list-templates.md b/docs/examples/1.6.x/client-web/examples/functions/list-templates.md
deleted file mode 100644
index 141c56322b..0000000000
--- a/docs/examples/1.6.x/client-web/examples/functions/list-templates.md
+++ /dev/null
@@ -1,16 +0,0 @@
-import { Client, Functions } from "appwrite";
-
-const client = new Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject(''); // Your project ID
-
-const functions = new Functions(client);
-
-const result = await functions.listTemplates(
- [], // runtimes (optional)
- [], // useCases (optional)
- 1, // limit (optional)
- 0 // offset (optional)
-);
-
-console.log(result);
diff --git a/docs/examples/1.6.x/console-cli/examples/functions/download-deployment.md b/docs/examples/1.6.x/console-cli/examples/functions/download-deployment.md
deleted file mode 100644
index aa440b5145..0000000000
--- a/docs/examples/1.6.x/console-cli/examples/functions/download-deployment.md
+++ /dev/null
@@ -1,3 +0,0 @@
-appwrite functions downloadDeployment \
- --functionId \
- --deploymentId
diff --git a/docs/examples/1.6.x/console-cli/examples/functions/get-specifications.md b/docs/examples/1.6.x/console-cli/examples/functions/get-specifications.md
deleted file mode 100644
index 4e612280e7..0000000000
--- a/docs/examples/1.6.x/console-cli/examples/functions/get-specifications.md
+++ /dev/null
@@ -1 +0,0 @@
-appwrite functions getSpecifications
diff --git a/docs/examples/1.6.x/console-cli/examples/migrations/create-firebase-o-auth-migration.md b/docs/examples/1.6.x/console-cli/examples/migrations/create-firebase-o-auth-migration.md
deleted file mode 100644
index 207becba03..0000000000
--- a/docs/examples/1.6.x/console-cli/examples/migrations/create-firebase-o-auth-migration.md
+++ /dev/null
@@ -1,3 +0,0 @@
-appwrite migrations createFirebaseOAuthMigration \
- --resources one two three \
- --projectId
diff --git a/docs/examples/1.6.x/console-cli/examples/migrations/delete-firebase-auth.md b/docs/examples/1.6.x/console-cli/examples/migrations/delete-firebase-auth.md
deleted file mode 100644
index d58abd9878..0000000000
--- a/docs/examples/1.6.x/console-cli/examples/migrations/delete-firebase-auth.md
+++ /dev/null
@@ -1 +0,0 @@
-appwrite migrations deleteFirebaseAuth
diff --git a/docs/examples/1.6.x/console-cli/examples/migrations/get-firebase-report-o-auth.md b/docs/examples/1.6.x/console-cli/examples/migrations/get-firebase-report-o-auth.md
deleted file mode 100644
index 0bdbf3e435..0000000000
--- a/docs/examples/1.6.x/console-cli/examples/migrations/get-firebase-report-o-auth.md
+++ /dev/null
@@ -1,3 +0,0 @@
-appwrite migrations getFirebaseReportOAuth \
- --resources one two three \
- --projectId
diff --git a/docs/examples/1.6.x/console-cli/examples/migrations/list-firebase-projects.md b/docs/examples/1.6.x/console-cli/examples/migrations/list-firebase-projects.md
deleted file mode 100644
index a0e59713eb..0000000000
--- a/docs/examples/1.6.x/console-cli/examples/migrations/list-firebase-projects.md
+++ /dev/null
@@ -1 +0,0 @@
-appwrite migrations listFirebaseProjects
diff --git a/docs/examples/1.6.x/console-web/examples/functions/download-deployment.md b/docs/examples/1.6.x/console-web/examples/functions/download-deployment.md
deleted file mode 100644
index 8115c7e130..0000000000
--- a/docs/examples/1.6.x/console-web/examples/functions/download-deployment.md
+++ /dev/null
@@ -1,14 +0,0 @@
-import { Client, Functions } from "@appwrite.io/console";
-
-const client = new Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject(''); // Your project ID
-
-const functions = new Functions(client);
-
-const result = functions.downloadDeployment(
- '', // functionId
- '' // deploymentId
-);
-
-console.log(result);
diff --git a/docs/examples/1.6.x/console-web/examples/functions/get-specifications.md b/docs/examples/1.6.x/console-web/examples/functions/get-specifications.md
deleted file mode 100644
index c5cb3fc043..0000000000
--- a/docs/examples/1.6.x/console-web/examples/functions/get-specifications.md
+++ /dev/null
@@ -1,11 +0,0 @@
-import { Client, Functions } from "@appwrite.io/console";
-
-const client = new Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject(''); // Your project ID
-
-const functions = new Functions(client);
-
-const result = await functions.getSpecifications();
-
-console.log(result);
diff --git a/docs/examples/1.6.x/console-web/examples/migrations/create-firebase-o-auth-migration.md b/docs/examples/1.6.x/console-web/examples/migrations/create-firebase-o-auth-migration.md
deleted file mode 100644
index bf57d427e9..0000000000
--- a/docs/examples/1.6.x/console-web/examples/migrations/create-firebase-o-auth-migration.md
+++ /dev/null
@@ -1,14 +0,0 @@
-import { Client, Migrations } from "@appwrite.io/console";
-
-const client = new Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject(''); // Your project ID
-
-const migrations = new Migrations(client);
-
-const result = await migrations.createFirebaseOAuthMigration(
- [], // resources
- '' // projectId
-);
-
-console.log(result);
diff --git a/docs/examples/1.6.x/console-web/examples/migrations/delete-firebase-auth.md b/docs/examples/1.6.x/console-web/examples/migrations/delete-firebase-auth.md
deleted file mode 100644
index 8c7683b46a..0000000000
--- a/docs/examples/1.6.x/console-web/examples/migrations/delete-firebase-auth.md
+++ /dev/null
@@ -1,11 +0,0 @@
-import { Client, Migrations } from "@appwrite.io/console";
-
-const client = new Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject(''); // Your project ID
-
-const migrations = new Migrations(client);
-
-const result = await migrations.deleteFirebaseAuth();
-
-console.log(result);
diff --git a/docs/examples/1.6.x/console-web/examples/migrations/get-firebase-report-o-auth.md b/docs/examples/1.6.x/console-web/examples/migrations/get-firebase-report-o-auth.md
deleted file mode 100644
index 055ddfba7c..0000000000
--- a/docs/examples/1.6.x/console-web/examples/migrations/get-firebase-report-o-auth.md
+++ /dev/null
@@ -1,14 +0,0 @@
-import { Client, Migrations } from "@appwrite.io/console";
-
-const client = new Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject(''); // Your project ID
-
-const migrations = new Migrations(client);
-
-const result = await migrations.getFirebaseReportOAuth(
- [], // resources
- '' // projectId
-);
-
-console.log(result);
diff --git a/docs/examples/1.6.x/console-web/examples/migrations/list-firebase-projects.md b/docs/examples/1.6.x/console-web/examples/migrations/list-firebase-projects.md
deleted file mode 100644
index 833c05fe24..0000000000
--- a/docs/examples/1.6.x/console-web/examples/migrations/list-firebase-projects.md
+++ /dev/null
@@ -1,11 +0,0 @@
-import { Client, Migrations } from "@appwrite.io/console";
-
-const client = new Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject(''); // Your project ID
-
-const migrations = new Migrations(client);
-
-const result = await migrations.listFirebaseProjects();
-
-console.log(result);
diff --git a/docs/examples/1.6.x/server-dart/examples/account/update-mfa-challenge.md b/docs/examples/1.6.x/server-dart/examples/account/update-mfa-challenge.md
index fd64c61cf9..2843d2f1b4 100644
--- a/docs/examples/1.6.x/server-dart/examples/account/update-mfa-challenge.md
+++ b/docs/examples/1.6.x/server-dart/examples/account/update-mfa-challenge.md
@@ -7,7 +7,7 @@ Client client = Client()
Account account = Account(client);
- result = await account.updateMfaChallenge(
+Session result = await account.updateMfaChallenge(
challengeId: '',
otp: '',
);
diff --git a/docs/examples/1.6.x/server-dart/examples/functions/download-deployment.md b/docs/examples/1.6.x/server-dart/examples/functions/download-deployment.md
deleted file mode 100644
index 981de76756..0000000000
--- a/docs/examples/1.6.x/server-dart/examples/functions/download-deployment.md
+++ /dev/null
@@ -1,13 +0,0 @@
-import 'package:dart_appwrite/dart_appwrite.dart';
-
-Client client = Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject('') // Your project ID
- .setKey(''); // Your secret API key
-
-Functions functions = Functions(client);
-
-UInt8List result = await functions.downloadDeployment(
- functionId: '',
- deploymentId: '',
-);
diff --git a/docs/examples/1.6.x/server-dart/examples/functions/get-template.md b/docs/examples/1.6.x/server-dart/examples/functions/get-template.md
deleted file mode 100644
index 881dac6782..0000000000
--- a/docs/examples/1.6.x/server-dart/examples/functions/get-template.md
+++ /dev/null
@@ -1,11 +0,0 @@
-import 'package:dart_appwrite/dart_appwrite.dart';
-
-Client client = Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject(''); // Your project ID
-
-Functions functions = Functions(client);
-
-TemplateFunction result = await functions.getTemplate(
- templateId: '',
-);
diff --git a/docs/examples/1.6.x/server-dart/examples/functions/list-templates.md b/docs/examples/1.6.x/server-dart/examples/functions/list-templates.md
deleted file mode 100644
index 7bef5106ed..0000000000
--- a/docs/examples/1.6.x/server-dart/examples/functions/list-templates.md
+++ /dev/null
@@ -1,14 +0,0 @@
-import 'package:dart_appwrite/dart_appwrite.dart';
-
-Client client = Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject(''); // Your project ID
-
-Functions functions = Functions(client);
-
-TemplateFunctionList result = await functions.listTemplates(
- runtimes: [], // (optional)
- useCases: [], // (optional)
- limit: 1, // (optional)
- offset: 0, // (optional)
-);
diff --git a/docs/examples/1.6.x/server-deno/examples/functions/download-deployment.md b/docs/examples/1.6.x/server-deno/examples/functions/download-deployment.md
deleted file mode 100644
index bb0a4e71b4..0000000000
--- a/docs/examples/1.6.x/server-deno/examples/functions/download-deployment.md
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Client, Functions } from "https://deno.land/x/appwrite/mod.ts";
-
-const client = new Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject('') // Your project ID
- .setKey(''); // Your secret API key
-
-const functions = new Functions(client);
-
-const result = functions.downloadDeployment(
- '', // functionId
- '' // deploymentId
-);
diff --git a/docs/examples/1.6.x/server-deno/examples/functions/get-template.md b/docs/examples/1.6.x/server-deno/examples/functions/get-template.md
deleted file mode 100644
index 7a58c95a34..0000000000
--- a/docs/examples/1.6.x/server-deno/examples/functions/get-template.md
+++ /dev/null
@@ -1,11 +0,0 @@
-import { Client, Functions } from "https://deno.land/x/appwrite/mod.ts";
-
-const client = new Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject(''); // Your project ID
-
-const functions = new Functions(client);
-
-const response = await functions.getTemplate(
- '' // templateId
-);
diff --git a/docs/examples/1.6.x/server-deno/examples/functions/list-templates.md b/docs/examples/1.6.x/server-deno/examples/functions/list-templates.md
deleted file mode 100644
index 06203e404b..0000000000
--- a/docs/examples/1.6.x/server-deno/examples/functions/list-templates.md
+++ /dev/null
@@ -1,14 +0,0 @@
-import { Client, Functions } from "https://deno.land/x/appwrite/mod.ts";
-
-const client = new Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject(''); // Your project ID
-
-const functions = new Functions(client);
-
-const response = await functions.listTemplates(
- [], // runtimes (optional)
- [], // useCases (optional)
- 1, // limit (optional)
- 0 // offset (optional)
-);
diff --git a/docs/examples/1.6.x/server-dotnet/examples/account/update-mfa-challenge.md b/docs/examples/1.6.x/server-dotnet/examples/account/update-mfa-challenge.md
index edf863a36e..ec32f8c900 100644
--- a/docs/examples/1.6.x/server-dotnet/examples/account/update-mfa-challenge.md
+++ b/docs/examples/1.6.x/server-dotnet/examples/account/update-mfa-challenge.md
@@ -9,7 +9,7 @@ Client client = new Client()
Account account = new Account(client);
- result = await account.UpdateMfaChallenge(
+Session result = await account.UpdateMfaChallenge(
challengeId: "",
otp: ""
);
\ No newline at end of file
diff --git a/docs/examples/1.6.x/server-dotnet/examples/functions/download-deployment.md b/docs/examples/1.6.x/server-dotnet/examples/functions/download-deployment.md
deleted file mode 100644
index 83f56a8e14..0000000000
--- a/docs/examples/1.6.x/server-dotnet/examples/functions/download-deployment.md
+++ /dev/null
@@ -1,15 +0,0 @@
-using Appwrite;
-using Appwrite.Models;
-using Appwrite.Services;
-
-Client client = new Client()
- .SetEndPoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- .SetProject("") // Your project ID
- .SetKey(""); // Your secret API key
-
-Functions functions = new Functions(client);
-
-byte[] result = await functions.DownloadDeployment(
- functionId: "",
- deploymentId: ""
-);
\ No newline at end of file
diff --git a/docs/examples/1.6.x/server-dotnet/examples/functions/get-template.md b/docs/examples/1.6.x/server-dotnet/examples/functions/get-template.md
deleted file mode 100644
index f57d61a024..0000000000
--- a/docs/examples/1.6.x/server-dotnet/examples/functions/get-template.md
+++ /dev/null
@@ -1,13 +0,0 @@
-using Appwrite;
-using Appwrite.Models;
-using Appwrite.Services;
-
-Client client = new Client()
- .SetEndPoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- .SetProject(""); // Your project ID
-
-Functions functions = new Functions(client);
-
-TemplateFunction result = await functions.GetTemplate(
- templateId: ""
-);
\ No newline at end of file
diff --git a/docs/examples/1.6.x/server-dotnet/examples/functions/list-templates.md b/docs/examples/1.6.x/server-dotnet/examples/functions/list-templates.md
deleted file mode 100644
index f76bd5fa9c..0000000000
--- a/docs/examples/1.6.x/server-dotnet/examples/functions/list-templates.md
+++ /dev/null
@@ -1,16 +0,0 @@
-using Appwrite;
-using Appwrite.Models;
-using Appwrite.Services;
-
-Client client = new Client()
- .SetEndPoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- .SetProject(""); // Your project ID
-
-Functions functions = new Functions(client);
-
-TemplateFunctionList result = await functions.ListTemplates(
- runtimes: new List(), // optional
- useCases: new List(), // optional
- limit: 1, // optional
- offset: 0 // optional
-);
\ No newline at end of file
diff --git a/docs/examples/1.6.x/server-go/examples/functions/download-deployment.md b/docs/examples/1.6.x/server-go/examples/functions/download-deployment.md
deleted file mode 100644
index 37d2149541..0000000000
--- a/docs/examples/1.6.x/server-go/examples/functions/download-deployment.md
+++ /dev/null
@@ -1,27 +0,0 @@
-package main
-
-import (
- "fmt"
- "github.com/appwrite/sdk-for-go/client"
- "github.com/appwrite/sdk-for-go/functions"
-)
-
-func main() {
- client := client.NewClient()
-
- client.SetEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- client.SetProject("") // Your project ID
- client.SetKey("") // Your secret API key
-
- service := functions.NewFunctions(client)
- response, error := service.DownloadDeployment(
- "",
- "",
- )
-
- if error != nil {
- panic(error)
- }
-
- fmt.Println(response)
-}
diff --git a/docs/examples/1.6.x/server-go/examples/functions/get-template.md b/docs/examples/1.6.x/server-go/examples/functions/get-template.md
deleted file mode 100644
index fe4e1debb9..0000000000
--- a/docs/examples/1.6.x/server-go/examples/functions/get-template.md
+++ /dev/null
@@ -1,25 +0,0 @@
-package main
-
-import (
- "fmt"
- "github.com/appwrite/sdk-for-go/client"
- "github.com/appwrite/sdk-for-go/functions"
-)
-
-func main() {
- client := client.NewClient()
-
- client.SetEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- client.SetProject("") // Your project ID
-
- service := functions.NewFunctions(client)
- response, error := service.GetTemplate(
- "",
- )
-
- if error != nil {
- panic(error)
- }
-
- fmt.Println(response)
-}
diff --git a/docs/examples/1.6.x/server-go/examples/functions/list-templates.md b/docs/examples/1.6.x/server-go/examples/functions/list-templates.md
deleted file mode 100644
index 19edf6d360..0000000000
--- a/docs/examples/1.6.x/server-go/examples/functions/list-templates.md
+++ /dev/null
@@ -1,28 +0,0 @@
-package main
-
-import (
- "fmt"
- "github.com/appwrite/sdk-for-go/client"
- "github.com/appwrite/sdk-for-go/functions"
-)
-
-func main() {
- client := client.NewClient()
-
- client.SetEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- client.SetProject("") // Your project ID
-
- service := functions.NewFunctions(client)
- response, error := service.ListTemplates(
- functions.WithListTemplatesRuntimes([]interface{}{}),
- functions.WithListTemplatesUseCases([]interface{}{}),
- functions.WithListTemplatesLimit(1),
- functions.WithListTemplatesOffset(0),
- )
-
- if error != nil {
- panic(error)
- }
-
- fmt.Println(response)
-}
diff --git a/docs/examples/1.6.x/server-graphql/examples/account/update-mfa-challenge.md b/docs/examples/1.6.x/server-graphql/examples/account/update-mfa-challenge.md
index 8237431bcf..0bcec2157f 100644
--- a/docs/examples/1.6.x/server-graphql/examples/account/update-mfa-challenge.md
+++ b/docs/examples/1.6.x/server-graphql/examples/account/update-mfa-challenge.md
@@ -3,6 +3,34 @@ mutation {
challengeId: "",
otp: ""
) {
- status
+ _id
+ _createdAt
+ _updatedAt
+ userId
+ expire
+ provider
+ providerUid
+ providerAccessToken
+ providerAccessTokenExpiry
+ providerRefreshToken
+ ip
+ osCode
+ osName
+ osVersion
+ clientType
+ clientCode
+ clientName
+ clientVersion
+ clientEngine
+ clientEngineVersion
+ deviceName
+ deviceBrand
+ deviceModel
+ countryCode
+ countryName
+ current
+ factors
+ secret
+ mfaUpdatedAt
}
}
diff --git a/docs/examples/1.6.x/server-graphql/examples/functions/download-deployment.md b/docs/examples/1.6.x/server-graphql/examples/functions/download-deployment.md
deleted file mode 100644
index f791338765..0000000000
--- a/docs/examples/1.6.x/server-graphql/examples/functions/download-deployment.md
+++ /dev/null
@@ -1,8 +0,0 @@
-query {
- functionsDownloadDeployment(
- functionId: "",
- deploymentId: ""
- ) {
- status
- }
-}
diff --git a/docs/examples/1.6.x/server-graphql/examples/functions/get-template.md b/docs/examples/1.6.x/server-graphql/examples/functions/get-template.md
deleted file mode 100644
index 44a466de27..0000000000
--- a/docs/examples/1.6.x/server-graphql/examples/functions/get-template.md
+++ /dev/null
@@ -1,35 +0,0 @@
-query {
- functionsGetTemplate(
- templateId: ""
- ) {
- icon
- id
- name
- tagline
- permissions
- events
- cron
- timeout
- useCases
- runtimes {
- name
- commands
- entrypoint
- providerRootDirectory
- }
- instructions
- vcsProvider
- providerRepositoryId
- providerOwner
- providerVersion
- variables {
- name
- description
- value
- placeholder
- required
- type
- }
- scopes
- }
-}
diff --git a/docs/examples/1.6.x/server-graphql/examples/functions/list-templates.md b/docs/examples/1.6.x/server-graphql/examples/functions/list-templates.md
deleted file mode 100644
index cb289ddabb..0000000000
--- a/docs/examples/1.6.x/server-graphql/examples/functions/list-templates.md
+++ /dev/null
@@ -1,41 +0,0 @@
-query {
- functionsListTemplates(
- runtimes: [],
- useCases: [],
- limit: 1,
- offset: 0
- ) {
- total
- templates {
- icon
- id
- name
- tagline
- permissions
- events
- cron
- timeout
- useCases
- runtimes {
- name
- commands
- entrypoint
- providerRootDirectory
- }
- instructions
- vcsProvider
- providerRepositoryId
- providerOwner
- providerVersion
- variables {
- name
- description
- value
- placeholder
- required
- type
- }
- scopes
- }
- }
-}
diff --git a/docs/examples/1.6.x/server-graphql/examples/users/delete-mfa-authenticator.md b/docs/examples/1.6.x/server-graphql/examples/users/delete-mfa-authenticator.md
index 26c9594a53..43f73404f0 100644
--- a/docs/examples/1.6.x/server-graphql/examples/users/delete-mfa-authenticator.md
+++ b/docs/examples/1.6.x/server-graphql/examples/users/delete-mfa-authenticator.md
@@ -3,36 +3,6 @@ mutation {
userId: "",
type: "totp"
) {
- _id
- _createdAt
- _updatedAt
- name
- password
- hash
- hashOptions
- registration
status
- labels
- passwordUpdate
- email
- phone
- emailVerification
- phoneVerification
- mfa
- prefs {
- data
- }
- targets {
- _id
- _createdAt
- _updatedAt
- name
- userId
- providerId
- providerType
- identifier
- expired
- }
- accessedAt
}
}
diff --git a/docs/examples/1.6.x/server-kotlin/java/functions/download-deployment.md b/docs/examples/1.6.x/server-kotlin/java/functions/download-deployment.md
deleted file mode 100644
index 77e4809379..0000000000
--- a/docs/examples/1.6.x/server-kotlin/java/functions/download-deployment.md
+++ /dev/null
@@ -1,24 +0,0 @@
-import io.appwrite.Client;
-import io.appwrite.coroutines.CoroutineCallback;
-import io.appwrite.services.Functions;
-
-Client client = new Client()
- .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- .setProject("") // Your project ID
- .setKey(""); // Your secret API key
-
-Functions functions = new Functions(client);
-
-functions.downloadDeployment(
- "", // functionId
- "", // deploymentId
- new CoroutineCallback<>((result, error) -> {
- if (error != null) {
- error.printStackTrace();
- return;
- }
-
- System.out.println(result);
- })
-);
-
diff --git a/docs/examples/1.6.x/server-kotlin/java/functions/get-template.md b/docs/examples/1.6.x/server-kotlin/java/functions/get-template.md
deleted file mode 100644
index 1521fa47c1..0000000000
--- a/docs/examples/1.6.x/server-kotlin/java/functions/get-template.md
+++ /dev/null
@@ -1,22 +0,0 @@
-import io.appwrite.Client;
-import io.appwrite.coroutines.CoroutineCallback;
-import io.appwrite.services.Functions;
-
-Client client = new Client()
- .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- .setProject(""); // Your project ID
-
-Functions functions = new Functions(client);
-
-functions.getTemplate(
- "", // templateId
- new CoroutineCallback<>((result, error) -> {
- if (error != null) {
- error.printStackTrace();
- return;
- }
-
- System.out.println(result);
- })
-);
-
diff --git a/docs/examples/1.6.x/server-kotlin/java/functions/list-templates.md b/docs/examples/1.6.x/server-kotlin/java/functions/list-templates.md
deleted file mode 100644
index e88e19124f..0000000000
--- a/docs/examples/1.6.x/server-kotlin/java/functions/list-templates.md
+++ /dev/null
@@ -1,25 +0,0 @@
-import io.appwrite.Client;
-import io.appwrite.coroutines.CoroutineCallback;
-import io.appwrite.services.Functions;
-
-Client client = new Client()
- .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- .setProject(""); // Your project ID
-
-Functions functions = new Functions(client);
-
-functions.listTemplates(
- listOf(), // runtimes (optional)
- listOf(), // useCases (optional)
- 1, // limit (optional)
- 0, // offset (optional)
- new CoroutineCallback<>((result, error) -> {
- if (error != null) {
- error.printStackTrace();
- return;
- }
-
- System.out.println(result);
- })
-);
-
diff --git a/docs/examples/1.6.x/server-kotlin/kotlin/functions/download-deployment.md b/docs/examples/1.6.x/server-kotlin/kotlin/functions/download-deployment.md
deleted file mode 100644
index 0ae36b314f..0000000000
--- a/docs/examples/1.6.x/server-kotlin/kotlin/functions/download-deployment.md
+++ /dev/null
@@ -1,15 +0,0 @@
-import io.appwrite.Client
-import io.appwrite.coroutines.CoroutineCallback
-import io.appwrite.services.Functions
-
-val client = Client()
- .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- .setProject("") // Your project ID
- .setKey("") // Your secret API key
-
-val functions = Functions(client)
-
-val result = functions.downloadDeployment(
- functionId = "",
- deploymentId = ""
-)
diff --git a/docs/examples/1.6.x/server-kotlin/kotlin/functions/get-template.md b/docs/examples/1.6.x/server-kotlin/kotlin/functions/get-template.md
deleted file mode 100644
index 53d838f7e6..0000000000
--- a/docs/examples/1.6.x/server-kotlin/kotlin/functions/get-template.md
+++ /dev/null
@@ -1,13 +0,0 @@
-import io.appwrite.Client
-import io.appwrite.coroutines.CoroutineCallback
-import io.appwrite.services.Functions
-
-val client = Client()
- .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- .setProject("") // Your project ID
-
-val functions = Functions(client)
-
-val response = functions.getTemplate(
- templateId = ""
-)
diff --git a/docs/examples/1.6.x/server-kotlin/kotlin/functions/list-templates.md b/docs/examples/1.6.x/server-kotlin/kotlin/functions/list-templates.md
deleted file mode 100644
index 37dc89ce89..0000000000
--- a/docs/examples/1.6.x/server-kotlin/kotlin/functions/list-templates.md
+++ /dev/null
@@ -1,16 +0,0 @@
-import io.appwrite.Client
-import io.appwrite.coroutines.CoroutineCallback
-import io.appwrite.services.Functions
-
-val client = Client()
- .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- .setProject("") // Your project ID
-
-val functions = Functions(client)
-
-val response = functions.listTemplates(
- runtimes = listOf(), // optional
- useCases = listOf(), // optional
- limit = 1, // optional
- offset = 0 // optional
-)
diff --git a/docs/examples/1.6.x/server-nodejs/examples/functions/download-deployment.md b/docs/examples/1.6.x/server-nodejs/examples/functions/download-deployment.md
deleted file mode 100644
index e0962db0ed..0000000000
--- a/docs/examples/1.6.x/server-nodejs/examples/functions/download-deployment.md
+++ /dev/null
@@ -1,13 +0,0 @@
-const sdk = require('node-appwrite');
-
-const client = new sdk.Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject('') // Your project ID
- .setKey(''); // Your secret API key
-
-const functions = new sdk.Functions(client);
-
-const result = await functions.downloadDeployment(
- '', // functionId
- '' // deploymentId
-);
diff --git a/docs/examples/1.6.x/server-nodejs/examples/functions/get-template.md b/docs/examples/1.6.x/server-nodejs/examples/functions/get-template.md
deleted file mode 100644
index 36195db7f3..0000000000
--- a/docs/examples/1.6.x/server-nodejs/examples/functions/get-template.md
+++ /dev/null
@@ -1,11 +0,0 @@
-const sdk = require('node-appwrite');
-
-const client = new sdk.Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject(''); // Your project ID
-
-const functions = new sdk.Functions(client);
-
-const result = await functions.getTemplate(
- '' // templateId
-);
diff --git a/docs/examples/1.6.x/server-nodejs/examples/functions/list-templates.md b/docs/examples/1.6.x/server-nodejs/examples/functions/list-templates.md
deleted file mode 100644
index 6f896cac97..0000000000
--- a/docs/examples/1.6.x/server-nodejs/examples/functions/list-templates.md
+++ /dev/null
@@ -1,14 +0,0 @@
-const sdk = require('node-appwrite');
-
-const client = new sdk.Client()
- .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- .setProject(''); // Your project ID
-
-const functions = new sdk.Functions(client);
-
-const result = await functions.listTemplates(
- [], // runtimes (optional)
- [], // useCases (optional)
- 1, // limit (optional)
- 0 // offset (optional)
-);
diff --git a/docs/examples/1.6.x/server-php/examples/functions/download-deployment.md b/docs/examples/1.6.x/server-php/examples/functions/download-deployment.md
deleted file mode 100644
index 83c8bb756f..0000000000
--- a/docs/examples/1.6.x/server-php/examples/functions/download-deployment.md
+++ /dev/null
@@ -1,16 +0,0 @@
-setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- ->setProject('') // Your project ID
- ->setKey(''); // Your secret API key
-
-$functions = new Functions($client);
-
-$result = $functions->downloadDeployment(
- functionId: '',
- deploymentId: ''
-);
\ No newline at end of file
diff --git a/docs/examples/1.6.x/server-php/examples/functions/get-template.md b/docs/examples/1.6.x/server-php/examples/functions/get-template.md
deleted file mode 100644
index 421557dcd2..0000000000
--- a/docs/examples/1.6.x/server-php/examples/functions/get-template.md
+++ /dev/null
@@ -1,14 +0,0 @@
-setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- ->setProject(''); // Your project ID
-
-$functions = new Functions($client);
-
-$result = $functions->getTemplate(
- templateId: ''
-);
\ No newline at end of file
diff --git a/docs/examples/1.6.x/server-php/examples/functions/list-templates.md b/docs/examples/1.6.x/server-php/examples/functions/list-templates.md
deleted file mode 100644
index a661903306..0000000000
--- a/docs/examples/1.6.x/server-php/examples/functions/list-templates.md
+++ /dev/null
@@ -1,17 +0,0 @@
-setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
- ->setProject(''); // Your project ID
-
-$functions = new Functions($client);
-
-$result = $functions->listTemplates(
- runtimes: [], // optional
- useCases: [], // optional
- limit: 1, // optional
- offset: 0 // optional
-);
\ No newline at end of file
diff --git a/docs/examples/1.6.x/server-python/examples/functions/download-deployment.md b/docs/examples/1.6.x/server-python/examples/functions/download-deployment.md
deleted file mode 100644
index f9d395451e..0000000000
--- a/docs/examples/1.6.x/server-python/examples/functions/download-deployment.md
+++ /dev/null
@@ -1,13 +0,0 @@
-from appwrite.client import Client
-
-client = Client()
-client.set_endpoint('https://cloud.appwrite.io/v1') # Your API Endpoint
-client.set_project('') # Your project ID
-client.set_key('') # Your secret API key
-
-functions = Functions(client)
-
-result = functions.download_deployment(
- function_id = '',
- deployment_id = ''
-)
diff --git a/docs/examples/1.6.x/server-python/examples/functions/get-template.md b/docs/examples/1.6.x/server-python/examples/functions/get-template.md
deleted file mode 100644
index bea5b6ab03..0000000000
--- a/docs/examples/1.6.x/server-python/examples/functions/get-template.md
+++ /dev/null
@@ -1,11 +0,0 @@
-from appwrite.client import Client
-
-client = Client()
-client.set_endpoint('https://cloud.appwrite.io/v1') # Your API Endpoint
-client.set_project('') # Your project ID
-
-functions = Functions(client)
-
-result = functions.get_template(
- template_id = ''
-)
diff --git a/docs/examples/1.6.x/server-python/examples/functions/list-templates.md b/docs/examples/1.6.x/server-python/examples/functions/list-templates.md
deleted file mode 100644
index b3ee38aef7..0000000000
--- a/docs/examples/1.6.x/server-python/examples/functions/list-templates.md
+++ /dev/null
@@ -1,14 +0,0 @@
-from appwrite.client import Client
-
-client = Client()
-client.set_endpoint('https://cloud.appwrite.io/v1') # Your API Endpoint
-client.set_project('') # Your project ID
-
-functions = Functions(client)
-
-result = functions.list_templates(
- runtimes = [], # optional
- use_cases = [], # optional
- limit = 1, # optional
- offset = 0 # optional
-)
diff --git a/docs/examples/1.6.x/server-rest/examples/functions/download-deployment.md b/docs/examples/1.6.x/server-rest/examples/functions/download-deployment.md
deleted file mode 100644
index ccd37283c9..0000000000
--- a/docs/examples/1.6.x/server-rest/examples/functions/download-deployment.md
+++ /dev/null
@@ -1,7 +0,0 @@
-GET /v1/functions/{functionId}/deployments/{deploymentId}/download HTTP/1.1
-Host: cloud.appwrite.io
-Content-Type: application/json
-X-Appwrite-Response-Format: 1.5.0
-X-Appwrite-Project:
-X-Appwrite-Key:
-
diff --git a/docs/examples/1.6.x/server-rest/examples/functions/get-template.md b/docs/examples/1.6.x/server-rest/examples/functions/get-template.md
deleted file mode 100644
index d4e1f95cf5..0000000000
--- a/docs/examples/1.6.x/server-rest/examples/functions/get-template.md
+++ /dev/null
@@ -1,6 +0,0 @@
-GET /v1/functions/templates/{templateId} HTTP/1.1
-Host: cloud.appwrite.io
-Content-Type: application/json
-X-Appwrite-Response-Format: 1.6.0
-X-Appwrite-Project:
-
diff --git a/docs/examples/1.6.x/server-rest/examples/functions/list-templates.md b/docs/examples/1.6.x/server-rest/examples/functions/list-templates.md
deleted file mode 100644
index b671bedebf..0000000000
--- a/docs/examples/1.6.x/server-rest/examples/functions/list-templates.md
+++ /dev/null
@@ -1,6 +0,0 @@
-GET /v1/functions/templates HTTP/1.1
-Host: cloud.appwrite.io
-Content-Type: application/json
-X-Appwrite-Response-Format: 1.6.0
-X-Appwrite-Project:
-
diff --git a/docs/examples/1.6.x/server-ruby/examples/functions/download-deployment.md b/docs/examples/1.6.x/server-ruby/examples/functions/download-deployment.md
deleted file mode 100644
index 748d0c9d01..0000000000
--- a/docs/examples/1.6.x/server-ruby/examples/functions/download-deployment.md
+++ /dev/null
@@ -1,15 +0,0 @@
-require 'appwrite'
-
-include Appwrite
-
-client = Client.new
- .set_endpoint('https://cloud.appwrite.io/v1') # Your API Endpoint
- .set_project('') # Your project ID
- .set_key('') # Your secret API key
-
-functions = Functions.new(client)
-
-result = functions.download_deployment(
- function_id: '',
- deployment_id: ''
-)
diff --git a/docs/examples/1.6.x/server-ruby/examples/functions/get-template.md b/docs/examples/1.6.x/server-ruby/examples/functions/get-template.md
deleted file mode 100644
index 5274880f67..0000000000
--- a/docs/examples/1.6.x/server-ruby/examples/functions/get-template.md
+++ /dev/null
@@ -1,13 +0,0 @@
-require 'appwrite'
-
-include Appwrite
-
-client = Client.new
- .set_endpoint('https://cloud.appwrite.io/v1') # Your API Endpoint
- .set_project('') # Your project ID
-
-functions = Functions.new(client)
-
-result = functions.get_template(
- template_id: ''
-)
diff --git a/docs/examples/1.6.x/server-ruby/examples/functions/list-templates.md b/docs/examples/1.6.x/server-ruby/examples/functions/list-templates.md
deleted file mode 100644
index 8bee6b0187..0000000000
--- a/docs/examples/1.6.x/server-ruby/examples/functions/list-templates.md
+++ /dev/null
@@ -1,16 +0,0 @@
-require 'appwrite'
-
-include Appwrite
-
-client = Client.new
- .set_endpoint('https://cloud.appwrite.io/v1') # Your API Endpoint
- .set_project('') # Your project ID
-
-functions = Functions.new(client)
-
-result = functions.list_templates(
- runtimes: [], # optional
- use_cases: [], # optional
- limit: 1, # optional
- offset: 0 # optional
-)
diff --git a/docs/examples/1.6.x/server-swift/examples/account/update-mfa-challenge.md b/docs/examples/1.6.x/server-swift/examples/account/update-mfa-challenge.md
index eed3bfade7..fee76bf0dd 100644
--- a/docs/examples/1.6.x/server-swift/examples/account/update-mfa-challenge.md
+++ b/docs/examples/1.6.x/server-swift/examples/account/update-mfa-challenge.md
@@ -7,7 +7,7 @@ let client = Client()
let account = Account(client)
-let result = try await account.updateMfaChallenge(
+let session = try await account.updateMfaChallenge(
challengeId: "",
otp: ""
)
diff --git a/docs/examples/1.6.x/server-swift/examples/functions/download-deployment.md b/docs/examples/1.6.x/server-swift/examples/functions/download-deployment.md
deleted file mode 100644
index 753d7058e6..0000000000
--- a/docs/examples/1.6.x/server-swift/examples/functions/download-deployment.md
+++ /dev/null
@@ -1,14 +0,0 @@
-import Appwrite
-
-let client = Client()
- .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- .setProject("") // Your project ID
- .setKey("") // Your secret API key
-
-let functions = Functions(client)
-
-let bytes = try await functions.downloadDeployment(
- functionId: "",
- deploymentId: ""
-)
-
diff --git a/docs/examples/1.6.x/server-swift/examples/functions/get-template.md b/docs/examples/1.6.x/server-swift/examples/functions/get-template.md
deleted file mode 100644
index bc7a9a3aef..0000000000
--- a/docs/examples/1.6.x/server-swift/examples/functions/get-template.md
+++ /dev/null
@@ -1,12 +0,0 @@
-import Appwrite
-
-let client = Client()
- .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- .setProject("") // Your project ID
-
-let functions = Functions(client)
-
-let templateFunction = try await functions.getTemplate(
- templateId: ""
-)
-
diff --git a/docs/examples/1.6.x/server-swift/examples/functions/list-templates.md b/docs/examples/1.6.x/server-swift/examples/functions/list-templates.md
deleted file mode 100644
index d0090ab803..0000000000
--- a/docs/examples/1.6.x/server-swift/examples/functions/list-templates.md
+++ /dev/null
@@ -1,15 +0,0 @@
-import Appwrite
-
-let client = Client()
- .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
- .setProject("") // Your project ID
-
-let functions = Functions(client)
-
-let templateFunctionList = try await functions.listTemplates(
- runtimes: [], // optional
- useCases: [], // optional
- limit: 1, // optional
- offset: 0 // optional
-)
-
diff --git a/docs/examples/1.6.x/server-swift/examples/users/delete-mfa-authenticator.md b/docs/examples/1.6.x/server-swift/examples/users/delete-mfa-authenticator.md
index 902d0c904c..5f1d6a0eeb 100644
--- a/docs/examples/1.6.x/server-swift/examples/users/delete-mfa-authenticator.md
+++ b/docs/examples/1.6.x/server-swift/examples/users/delete-mfa-authenticator.md
@@ -8,7 +8,7 @@ let client = Client()
let users = Users(client)
-let user = try await users.deleteMfaAuthenticator(
+let result = try await users.deleteMfaAuthenticator(
userId: "",
type: .totp
)
diff --git a/docs/references/migrations/migration-csv.md b/docs/references/migrations/migration-csv.md
new file mode 100644
index 0000000000..7a32d5ff6e
--- /dev/null
+++ b/docs/references/migrations/migration-csv.md
@@ -0,0 +1 @@
+Import documents from a CSV file into your Appwrite database. This endpoint allows you to import documents from a CSV file uploaded to Appwrite Storage bucket.
\ No newline at end of file
diff --git a/docs/sdks/cli/GETTING_STARTED.md b/docs/sdks/cli/GETTING_STARTED.md
index 564fb4d5f9..1cadb1bbda 100644
--- a/docs/sdks/cli/GETTING_STARTED.md
+++ b/docs/sdks/cli/GETTING_STARTED.md
@@ -59,7 +59,7 @@ My Awesome Function
You can now deploy this function using
```sh
-$ appwrite deploy function
+$ appwrite push function
? Which functions would you like to deploy? My Awesome Function (61d1a4c81dfcd95bc834)
ℹ Info Deploying function My Awesome Function ( 61d1a4c81dfcd95bc834 )
@@ -73,7 +73,7 @@ Your function has now been deployed on your Appwrite server! As soon as the buil
Similarly, you can deploy all your collections to your Appwrite server using
```sh
-appwrite deploy collections
+appwrite push collections
```
> ### Note
@@ -98,7 +98,7 @@ $ appwrite users list
To create a document you can use the following command
```sh
-$ appwrite database createDocument --collectionId --documentId 'unique()' --data '{ "Name": "Iron Man" }' --permissions 'read("any")' 'read("team:abc")'
+$ appwrite databases create-document --database-id --collection-id --document-id "unique()" --data '{"name": "Walter O Brein"}' --permissions 'read("any")' 'read("team:abc")'
```
### Some Gotchas
@@ -140,4 +140,4 @@ The Appwrite CLI can also work in a CI environment. The initialisation of the CL
```sh
appwrite client --endpoint http://localhost/v1 --projectId --key
-```
\ No newline at end of file
+```
diff --git a/docs/sdks/dart/CHANGELOG.md b/docs/sdks/dart/CHANGELOG.md
index a46f1dcf0f..c41413b9ec 100644
--- a/docs/sdks/dart/CHANGELOG.md
+++ b/docs/sdks/dart/CHANGELOG.md
@@ -1,3 +1,15 @@
+## 14.0.0
+
+* Breaking changes:
+ * Changed the typing of `AppwriteException`'s response parameter from a `dynamic` object to an optional string (`?String`).
+
+## 13.0.0
+
+* Fixed realtime pong response.
+* Fixed issues with `chunkedUpload` method.
+* Fixed type mismatch bug where `List` was incorrectly causing runtime type errors.
+* Updated return type of `updateMfaChallenge()` from raw data to properly typed `models.Session` object.
+
## 12.0.0
* Support for Appwrite 1.6
diff --git a/docs/sdks/flutter/CHANGELOG.md b/docs/sdks/flutter/CHANGELOG.md
index abb8967f84..110b6d08c3 100644
--- a/docs/sdks/flutter/CHANGELOG.md
+++ b/docs/sdks/flutter/CHANGELOG.md
@@ -1,3 +1,13 @@
+## 15.0.0
+
+* Breaking changes:
+ * Changed the typing of `AppwriteException`'s response parameter from a `dynamic` object to an optional string (`?String`).
+
+## 14.0.0
+
+* Fixed realtime pong response.
+* Fixed issues with `chunkedUpload` method.
+
## 13.0.0
* Fixed realtime reconnection issues
diff --git a/phpunit.xml b/phpunit.xml
index 4c4e55ea4e..598b730908 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -6,7 +6,7 @@
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
- stopOnFailure="false"
+ stopOnFailure="true"
>
diff --git a/src/Appwrite/Event/Realtime.php b/src/Appwrite/Event/Realtime.php
index f4f00b59d4..28a1bb6a6d 100644
--- a/src/Appwrite/Event/Realtime.php
+++ b/src/Appwrite/Event/Realtime.php
@@ -7,10 +7,17 @@ use Utopia\Database\Document;
class Realtime extends Event
{
+ protected array $subscribers = [];
+
public function __construct()
{
}
+ /**
+ * Get Realtime payload for this event.
+ *
+ * @return array
+ */
public function getRealtimePayload(): array
{
$payload = [];
@@ -24,6 +31,28 @@ class Realtime extends Event
return $payload;
}
+ /**
+ * Set subscribers for this realtime event.
+ *
+ * @param array $subscribers
+ * @return array
+ */
+ public function setSubscribers(array $subscribers): self
+ {
+ $this->subscribers = $subscribers;
+ return $this;
+ }
+
+ /**
+ * Get subscribers for this realtime event.
+ *
+ * @return array
+ */
+ public function getSubscribers(): array
+ {
+ return $this->subscribers;
+ }
+
/**
* Execute Event.
*
@@ -53,17 +82,23 @@ class Realtime extends Event
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')
- ]
- );
+ $projectIds = !empty($this->getSubscribers())
+ ? $this->getSubscribers()
+ : [$target['projectId'] ?? $this->getProject()->getId()];
+
+ foreach ($projectIds as $projectId) {
+ RealtimeAdapter::send(
+ projectId: $projectId,
+ 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
deleted file mode 100644
index c70cea5c73..0000000000
--- a/src/Appwrite/Event/Usage.php
+++ /dev/null
@@ -1,78 +0,0 @@
-setQueue(Event::USAGE_QUEUE_NAME)
- ->setClass(Event::USAGE_CLASS_NAME);
- }
-
- /**
- * Add reduce.
- *
- * @param Document $document
- * @return self
- */
- public function addReduce(Document $document): self
- {
- $this->reduce[] = $document;
-
- return $this;
- }
-
- /**
- * Add metric.
- *
- * @param string $key
- * @param int $value
- * @return self
- */
- public function addMetric(string $key, int $value): self
- {
-
- $this->metrics[] = [
- 'key' => $key,
- 'value' => $value,
- ];
-
- return $this;
- }
-
- /**
- * Prepare the payload for the usage event.
- *
- * @return array
- */
- protected function preparePayload(): array
- {
- return [
- 'project' => $this->project,
- 'reduce' => $this->reduce,
- 'metrics' => $this->metrics,
- ];
- }
-
- /**
- * Sends metrics to the usage worker.
- *
- * @return string|bool
- */
- public function trigger(): string|bool
- {
- parent::trigger();
- $this->metrics = [];
- return true;
- }
-}
diff --git a/src/Appwrite/Event/UsageDump.php b/src/Appwrite/Event/UsageDump.php
deleted file mode 100644
index a70716e94f..0000000000
--- a/src/Appwrite/Event/UsageDump.php
+++ /dev/null
@@ -1,44 +0,0 @@
-setQueue(Event::USAGE_DUMP_QUEUE_NAME)
- ->setClass(Event::USAGE_DUMP_CLASS_NAME);
- }
-
- /**
- * Add Stats.
- *
- * @param array $stats
- * @return self
- */
- public function setStats(array $stats): self
- {
- $this->stats = $stats;
-
- return $this;
- }
-
- /**
- * Prepare the payload for the usage dump event.
- *
- * @return array
- */
- protected function preparePayload(): array
- {
- return [
- 'stats' => $this->stats,
- ];
- }
-}
diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php
index 56016f1057..17e93f43f5 100644
--- a/src/Appwrite/Migration/Migration.php
+++ b/src/Appwrite/Migration/Migration.php
@@ -93,6 +93,7 @@ abstract class Migration
'1.6.0' => 'V21',
'1.6.1' => 'V21',
'1.6.2' => 'V22',
+ '1.7.0' => 'V23',
];
/**
diff --git a/src/Appwrite/Migration/Version/V19.php b/src/Appwrite/Migration/Version/V19.php
index 18234ebdc4..4415003bfd 100644
--- a/src/Appwrite/Migration/Version/V19.php
+++ b/src/Appwrite/Migration/Version/V19.php
@@ -10,7 +10,6 @@ use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception;
use Utopia\Database\Query;
-use Utopia\System\System;
class V19 extends Migration
{
@@ -731,7 +730,7 @@ class V19 extends Migration
if (empty($document->getAttribute('scheduleId', null))) {
$schedule = $this->consoleDB->createDocument('schedules', new Document([
- 'region' => System::getEnv('_APP_REGION', 'default'), // Todo replace with projects region
+ 'region' => $project->getAttribute('region'),
'resourceType' => 'function',
'resourceId' => $document->getId(),
'resourceInternalId' => $document->getInternalId(),
diff --git a/src/Appwrite/Migration/Version/V23.php b/src/Appwrite/Migration/Version/V23.php
new file mode 100644
index 0000000000..dec7e8e9d3
--- /dev/null
+++ b/src/Appwrite/Migration/Version/V23.php
@@ -0,0 +1,69 @@
+ null,
+ fn () => []
+ );
+ }
+
+ Console::info('Migrating Collections');
+ $this->migrateCollections();
+ }
+
+ /**
+ * Migrate Collections.
+ *
+ * @return void
+ * @throws Exception|Throwable
+ */
+ private function migrateCollections(): void
+ {
+ $internalProjectId = $this->project->getInternalId();
+ $collectionType = match ($internalProjectId) {
+ 'console' => 'console',
+ default => 'projects',
+ };
+
+ $collections = $this->collections[$collectionType];
+ foreach ($collections as $collection) {
+ $id = $collection['$id'];
+
+ Console::log("Migrating Collection \"{$id}\"");
+
+ $this->projectDB->setNamespace("_$internalProjectId");
+
+ switch ($id) {
+ case 'memberships':
+ // Create roles index
+ try {
+ $this->createIndexFromCollection($this->projectDB, $id, '_key_roles');
+ } catch (Throwable $th) {
+ Console::warning("'_key_roles' from {$id}: {$th->getMessage()}");
+ }
+ break;
+ }
+
+ usleep(50000);
+ }
+ }
+}
diff --git a/src/Appwrite/Platform/Services/Workers.php b/src/Appwrite/Platform/Services/Workers.php
index e121ee35f7..4f4095aca4 100644
--- a/src/Appwrite/Platform/Services/Workers.php
+++ b/src/Appwrite/Platform/Services/Workers.php
@@ -14,10 +14,6 @@ use Appwrite\Platform\Workers\Migrations;
use Appwrite\Platform\Workers\StatsResources;
use Appwrite\Platform\Workers\StatsUsage;
use Appwrite\Platform\Workers\StatsUsageDump;
-/** remove */
-use Appwrite\Platform\Workers\Usage;
-use Appwrite\Platform\Workers\UsageDump;
-/** /remove */
use Appwrite\Platform\Workers\Webhooks;
use Utopia\Platform\Service;
@@ -40,10 +36,6 @@ class Workers extends Service
->addAction(StatsUsage::getName(), new StatsUsage())
->addAction(Migrations::getName(), new Migrations())
->addAction(StatsResources::getName(), new StatsResources())
- /** Remove */
- ->addAction(UsageDump::getName(), new UsageDump())
- ->addAction(Usage::getName(), new Usage())
- /** /remove */
;
}
}
diff --git a/src/Appwrite/Platform/Tasks/Maintenance.php b/src/Appwrite/Platform/Tasks/Maintenance.php
index 2d37bdbf70..b9e312a3fb 100644
--- a/src/Appwrite/Platform/Tasks/Maintenance.php
+++ b/src/Appwrite/Platform/Tasks/Maintenance.php
@@ -24,12 +24,13 @@ class Maintenance extends Action
$this
->desc('Schedules maintenance tasks and publishes them to our queues')
->inject('dbForPlatform')
+ ->inject('console')
->inject('queueForCertificates')
->inject('queueForDeletes')
- ->callback(fn (Database $dbForPlatform, Certificate $queueForCertificates, Delete $queueForDeletes) => $this->action($dbForPlatform, $queueForCertificates, $queueForDeletes));
+ ->callback(fn (Database $dbForPlatform, Document $console, Certificate $queueForCertificates, Delete $queueForDeletes) => $this->action($dbForPlatform, $console, $queueForCertificates, $queueForDeletes));
}
- public function action(Database $dbForPlatform, Certificate $queueForCertificates, Delete $queueForDeletes): void
+ public function action(Database $dbForPlatform, Document $console, Certificate $queueForCertificates, Delete $queueForDeletes): void
{
Console::title('Maintenance V1');
Console::success(APP_NAME . ' maintenance process v1 has started');
@@ -41,19 +42,26 @@ 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, $dbForPlatform, $queueForDeletes, $queueForCertificates) {
+ Console::loop(function () use ($interval, $cacheRetention, $schedulesDeletionRetention, $usageStatsRetentionHourly, $dbForPlatform, $console, $queueForDeletes, $queueForCertificates) {
$time = DateTime::now();
Console::info("[{$time}] Notifying workers with maintenance tasks every {$interval} seconds");
- $this->foreachProject($dbForPlatform, function (Document $project) use ($queueForDeletes, $usageStatsRetentionHourly) {
+ $dbForPlatform->foreach('projects', function (Document $project) use ($queueForDeletes, $usageStatsRetentionHourly) {
$queueForDeletes
->setType(DELETE_TYPE_MAINTENANCE)
->setProject($project)
->setUsageRetentionHourlyDateTime(DateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly))
->trigger();
+ }, [
+ Query::limit(100),
+ ]);
- });
+ $queueForDeletes
+ ->setType(DELETE_TYPE_MAINTENANCE)
+ ->setProject($console)
+ ->setUsageRetentionHourlyDateTime(DateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly))
+ ->trigger();
$this->notifyDeleteConnections($queueForDeletes);
$this->renewCertificates($dbForPlatform, $queueForCertificates);
@@ -62,33 +70,6 @@ class Maintenance extends Action
}, $interval, $delay);
}
- 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;
- $chunk = 0;
- $limit = 50;
- $sum = $limit;
- $executionStart = \microtime(true);
-
- while ($sum === $limit) {
- $projects = $dbForPlatform->find('projects', [Query::limit($limit), Query::offset($chunk * $limit)]);
-
- $chunk++;
-
- /** @var string[] $projectIds */
- $sum = count($projects);
-
- foreach ($projects as $project) {
- $callback($project);
- $count++;
- }
- }
-
- $executionEnd = \microtime(true);
- Console::info("Found {$count} projects " . ($executionEnd - $executionStart) . " seconds");
- }
-
private function notifyDeleteConnections(Delete $queueForDeletes): void
{
$queueForDeletes
diff --git a/src/Appwrite/Platform/Tasks/ScheduleBase.php b/src/Appwrite/Platform/Tasks/ScheduleBase.php
index dad2db0d9a..a3c36cb96e 100644
--- a/src/Appwrite/Platform/Tasks/ScheduleBase.php
+++ b/src/Appwrite/Platform/Tasks/ScheduleBase.php
@@ -103,8 +103,15 @@ abstract class ScheduleBase extends Action
$paginationQueries[] = Query::cursorAfter($latestDocument);
}
+ // Temporarly accepting both 'fra' and 'default'
+ // When all migrated, only use _APP_REGION with 'default' as default value
+ $regions = [System::getEnv('_APP_REGION', 'default')];
+ if (!in_array('default', $regions)) {
+ $regions[] = 'default';
+ }
+
$results = $dbForPlatform->find('schedules', \array_merge($paginationQueries, [
- Query::equal('region', [System::getEnv('_APP_REGION', 'default')]),
+ Query::equal('region', $regions),
Query::equal('resourceType', [static::getSupportedResource()]),
Query::equal('active', [true]),
]));
@@ -153,8 +160,15 @@ abstract class ScheduleBase extends Action
$paginationQueries[] = Query::cursorAfter($latestDocument);
}
+ // Temporarly accepting both 'fra' and 'default'
+ // When all migrated, only use _APP_REGION with 'default' as default value
+ $regions = [System::getEnv('_APP_REGION', 'default')];
+ if (!in_array('default', $regions)) {
+ $regions[] = 'default';
+ }
+
$results = $dbForPlatform->find('schedules', \array_merge($paginationQueries, [
- Query::equal('region', [System::getEnv('_APP_REGION', 'default')]),
+ Query::equal('region', $regions),
Query::equal('resourceType', [static::getSupportedResource()]),
Query::greaterThanEqual('resourceUpdatedAt', $lastSyncUpdate),
]));
diff --git a/src/Appwrite/Platform/Workers/Audits.php b/src/Appwrite/Platform/Workers/Audits.php
index f1ae46eea7..76309145b8 100644
--- a/src/Appwrite/Platform/Workers/Audits.php
+++ b/src/Appwrite/Platform/Workers/Audits.php
@@ -7,8 +7,6 @@ use Exception;
use Throwable;
use Utopia\Audit\Audit;
use Utopia\CLI\Console;
-use Utopia\Database\Database;
-use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Authorization;
use Utopia\Database\Exception\Structure;
@@ -18,15 +16,16 @@ use Utopia\System\System;
class Audits extends Action
{
- private const BATCH_SIZE_DEVELOPMENT = 1; // smaller batch size for development
- private const BATCH_SIZE_PRODUCTION = 5_000;
- private const BATCH_AGGREGATION_INTERVAL = 60; // in seconds
+ protected const BATCH_SIZE_DEVELOPMENT = 1; // smaller batch size for development
+ protected const BATCH_SIZE_PRODUCTION = 5_000;
+ protected const BATCH_AGGREGATION_INTERVAL = 60; // in seconds
private int $lastTriggeredTime = 0;
private array $logs = [];
- private function getBatchSize(): int
+
+ protected function getBatchSize(): int
{
return System::getEnv('_APP_ENV', 'development') === 'development'
? self::BATCH_SIZE_DEVELOPMENT
@@ -46,8 +45,9 @@ class Audits extends Action
$this
->desc('Audits worker')
->inject('message')
- ->inject('dbForProject')
- ->callback(fn ($message, $dbForProject) => $this->action($message, $dbForProject));
+ ->inject('getProjectDB')
+ ->inject('project')
+ ->callback([$this, 'action']);
$this->lastTriggeredTime = time();
}
@@ -55,14 +55,15 @@ class Audits extends Action
/**
* @param Message $message
- * @param Database $dbForProject
+ * @param callable $getProjectDB
+ * @param Document $project
* @return void
* @throws Throwable
* @throws \Utopia\Database\Exception
* @throws Authorization
* @throws Structure
*/
- public function action(Message $message, Database $dbForProject): void
+ public function action(Message $message, callable $getProjectDB, Document $project): void
{
$payload = $message->getPayload() ?? [];
@@ -73,7 +74,11 @@ class Audits extends Action
Console::info('Aggregating audit logs');
$event = $payload['event'] ?? '';
- $auditPayload = $payload['payload'] ?? '';
+
+ $auditPayload = '';
+ if ($project->getId() === 'console') {
+ $auditPayload = $payload['payload'] ?? '';
+ }
$mode = $payload['mode'] ?? '';
$resource = $payload['resource'] ?? '';
$userAgent = $payload['userAgent'] ?? '';
@@ -100,30 +105,45 @@ class Audits extends Action
'mode' => $mode,
'data' => $auditPayload,
],
- 'timestamp' => DateTime::formatTz(DateTime::now())
+ 'timestamp' => date("Y-m-d H:i:s", $message->getTimestamp()),
];
- $this->logs[] = $eventData;
+ if (isset($this->logs[$project->getInternalId()])) {
+ $this->logs[$project->getInternalId()]['logs'][] = $eventData;
+ } else {
+ $this->logs[$project->getInternalId()] = [
+ 'project' => new Document([
+ '$id' => $project->getId(),
+ '$internalId' => $project->getInternalId(),
+ 'database' => $project->getAttribute('database'),
+ ]),
+ 'logs' => [$eventData]
+ ];
+ }
// Check if we should process the batch by checking both for the batch size and the elapsed time
$batchSize = $this->getBatchSize();
- $shouldProcessBatch = count($this->logs) >= $batchSize;
- if (!$shouldProcessBatch && count($this->logs) > 0) {
- $shouldProcessBatch = (time() - $this->lastTriggeredTime) >= self::BATCH_AGGREGATION_INTERVAL;
+ $shouldProcessBatch = \count($this->logs) >= $batchSize;
+ if (!$shouldProcessBatch && \count($this->logs) > 0) {
+ $shouldProcessBatch = (\time() - $this->lastTriggeredTime) >= self::BATCH_AGGREGATION_INTERVAL;
}
if ($shouldProcessBatch) {
- Console::log('Processing batch with ' . count($this->logs) . ' events');
- $audit = new Audit($dbForProject);
-
try {
- $audit->logBatch($this->logs);
- Console::success('Audit logs processed successfully');
+ foreach ($this->logs as $internalId => $projectLogs) {
+ $dbForProject = $getProjectDB($projectLogs['project']);
+
+ Console::log('Processing batch with ' . count($projectLogs['logs']) . ' events');
+ $audit = new Audit($dbForProject);
+
+ $audit->logBatch($projectLogs['logs']);
+ Console::success('Audit logs processed successfully');
+
+ unset($this->logs[$internalId]);
+ }
} catch (Throwable $e) {
Console::error('Error processing audit logs: ' . $e->getMessage());
} finally {
- // Clear the pending events after successful batch processing
- $this->logs = [];
$this->lastTriggeredTime = time();
}
}
diff --git a/src/Appwrite/Platform/Workers/Builds.php b/src/Appwrite/Platform/Workers/Builds.php
index c9833adcfb..4057d4b190 100644
--- a/src/Appwrite/Platform/Workers/Builds.php
+++ b/src/Appwrite/Platform/Workers/Builds.php
@@ -5,8 +5,9 @@ namespace Appwrite\Platform\Workers;
use Ahc\Jwt\JWT;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
+use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage;
-use Appwrite\Messaging\Adapter\Realtime;
+use Appwrite\Event\Webhook;
use Appwrite\Utopia\Response\Model\Deployment;
use Appwrite\Vcs\Comment;
use Exception;
@@ -49,15 +50,18 @@ class Builds extends Action
->inject('project')
->inject('dbForPlatform')
->inject('queueForEvents')
+ ->inject('queueForWebhooks')
->inject('queueForFunctions')
+ ->inject('queueForRealtime')
->inject('queueForStatsUsage')
->inject('cache')
->inject('dbForProject')
->inject('deviceForFunctions')
->inject('isResourceBlocked')
->inject('log')
- ->callback(fn ($message, Document $project, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions, StatsUsage $usage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log) =>
- $this->action($message, $project, $dbForPlatform, $queueForEvents, $queueForFunctions, $usage, $cache, $dbForProject, $deviceForFunctions, $isResourceBlocked, $log));
+ ->inject('executor')
+ ->callback(fn ($message, Document $project, Database $dbForPlatform, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, StatsUsage $usage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log, Executor $executor) =>
+ $this->action($message, $project, $dbForPlatform, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $usage, $cache, $dbForProject, $deviceForFunctions, $isResourceBlocked, $log, $executor));
}
/**
@@ -65,16 +69,19 @@ class Builds extends Action
* @param Document $project
* @param Database $dbForPlatform
* @param Event $queueForEvents
+ * @param Webhook $queueForWebhooks
* @param Func $queueForFunctions
+ * @param Realtime $queueForRealtime
* @param StatsUsage $queueForStatsUsage
* @param Cache $cache
* @param Database $dbForProject
* @param Device $deviceForFunctions
* @param Log $log
+ * @param Executor $executor
* @return void
* @throws \Utopia\Database\Exception
*/
- public function action(Message $message, Document $project, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions, StatsUsage $queueForStatsUsage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log): void
+ public function action(Message $message, Document $project, Database $dbForPlatform, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, StatsUsage $queueForStatsUsage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log, Executor $executor): void
{
$payload = $message->getPayload() ?? [];
@@ -95,7 +102,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, $queueForStatsUsage, $dbForPlatform, $dbForProject, $github, $project, $resource, $deployment, $template, $isResourceBlocked, $log);
+ $this->buildDeployment($deviceForFunctions, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $queueForEvents, $queueForStatsUsage, $dbForPlatform, $dbForProject, $github, $project, $resource, $deployment, $template, $isResourceBlocked, $log, $executor);
break;
default:
@@ -105,7 +112,9 @@ class Builds extends Action
/**
* @param Device $deviceForFunctions
+ * @param Webhook $queueForWebhooks
* @param Func $queueForFunctions
+ * @param Realtime $queueForRealtime
* @param Event $queueForEvents
* @param StatsUsage $queueForStatsUsage
* @param Database $dbForPlatform
@@ -116,14 +125,13 @@ class Builds extends Action
* @param Document $deployment
* @param Document $template
* @param Log $log
+ * @param Executor $executor
* @return void
* @throws \Utopia\Database\Exception
* @throws Exception
*/
- protected function buildDeployment(Device $deviceForFunctions, Func $queueForFunctions, Event $queueForEvents, StatsUsage $queueForStatsUsage, Database $dbForPlatform, Database $dbForProject, GitHub $github, Document $project, Document $function, Document $deployment, Document $template, callable $isResourceBlocked, Log $log): void
+ protected function buildDeployment(Device $deviceForFunctions, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Event $queueForEvents, StatsUsage $queueForStatsUsage, Database $dbForPlatform, Database $dbForProject, GitHub $github, Document $project, Document $function, Document $deployment, Document $template, callable $isResourceBlocked, Log $log, Executor $executor): void
{
- $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST'));
-
$functionId = $function->getId();
$log->addTag('functionId', $function->getId());
@@ -158,10 +166,7 @@ class Builds extends Action
}
// Realtime preparation
- $allEvents = Event::generateEvents('functions.[functionId].deployments.[deploymentId].update', [
- 'functionId' => $function->getId(),
- 'deploymentId' => $deployment->getId()
- ]);
+ $event = "functions.[functionId].deployments.[deploymentId].update";
$startTime = DateTime::now();
$durationStart = \microtime(true);
@@ -375,21 +380,16 @@ class Builds extends Action
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
/**
- * Send realtime Event
+ * Trigger Realtime Event
*/
- $target = Realtime::fromPayload(
- // Pass first, most verbose event pattern
- event: $allEvents[0],
- payload: $build,
- project: $project
- );
- Realtime::send(
- projectId: 'console',
- payload: $build->getArrayCopy(),
- events: $allEvents,
- channels: $target['channels'],
- roles: $target['roles']
- );
+ $queueForRealtime
+ ->setProject($project)
+ ->setSubscribers(['console'])
+ ->setEvent($event)
+ ->setParam('functionId', $function->getId())
+ ->setParam('deploymentId', $deployment->getId())
+ ->setPayload($build->getArrayCopy())
+ ->trigger();
}
$tmpPath = '/tmp/builds/' . $buildId;
@@ -436,40 +436,34 @@ class Builds extends Action
$this->runGitAction('building', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForPlatform);
}
- /** Trigger Webhook */
$deploymentModel = new Deployment();
$deploymentUpdate =
$queueForEvents
- ->setQueue(Event::WEBHOOK_QUEUE_NAME)
- ->setClass(Event::WEBHOOK_CLASS_NAME)
->setProject($project)
->setEvent('functions.[functionId].deployments.[deploymentId].update')
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId())
->setPayload($deployment->getArrayCopy(array_keys($deploymentModel->getRules())));
- $deploymentUpdate->trigger();
+ /** Trigger Webhook */
+ $queueForWebhooks
+ ->from($deploymentUpdate)
+ ->trigger();
/** Trigger Functions */
$queueForFunctions
->from($deploymentUpdate)
->trigger();
- /** Trigger Realtime */
- $target = Realtime::fromPayload(
- // Pass first, most verbose event pattern
- event: $allEvents[0],
- payload: $build,
- project: $project
- );
-
- Realtime::send(
- projectId: 'console',
- payload: $build->getArrayCopy(),
- events: $allEvents,
- channels: $target['channels'],
- roles: $target['roles']
- );
+ /** Trigger Realtime Event */
+ $queueForRealtime
+ ->setProject($project)
+ ->setSubscribers(['console'])
+ ->setEvent($event)
+ ->setParam('functionId', $function->getId())
+ ->setParam('deploymentId', $deployment->getId())
+ ->setPayload($build->getArrayCopy())
+ ->trigger();
$vars = [];
@@ -562,12 +556,12 @@ class Builds extends Action
$err = $error;
}
}),
- Co\go(function () use ($executor, $project, $deployment, &$response, &$build, $dbForProject, $allEvents, &$err, &$isCanceled) {
+ Co\go(function () use ($executor, $project, $function, $deployment, &$response, &$build, $dbForProject, $event, &$err, $queueForRealtime, &$isCanceled) {
try {
$executor->getLogs(
deploymentId: $deployment->getId(),
projectId: $project->getId(),
- callback: function ($logs) use (&$response, &$err, &$build, $dbForProject, $allEvents, $project, &$isCanceled) {
+ callback: function ($logs) use (&$response, &$err, &$build, $dbForProject, $event, $project, $function, $deployment, $queueForRealtime, &$isCanceled) {
if ($isCanceled) {
return;
}
@@ -592,21 +586,16 @@ class Builds extends Action
$build = $dbForProject->updateDocument('builds', $build->getId(), $build);
/**
- * Send realtime Event
+ * Trigger Realtime Event
*/
- $target = Realtime::fromPayload(
- // Pass first, most verbose event pattern
- event: $allEvents[0],
- payload: $build,
- project: $project
- );
- Realtime::send(
- projectId: 'console',
- payload: $build->getArrayCopy(),
- events: $allEvents,
- channels: $target['channels'],
- roles: $target['roles']
- );
+ $queueForRealtime
+ ->setProject($project)
+ ->setSubscribers(['console'])
+ ->setEvent($event)
+ ->setParam('functionId', $function->getId())
+ ->setParam('deploymentId', $deployment->getId())
+ ->setPayload($build->getArrayCopy())
+ ->trigger();
}
}
);
@@ -694,21 +683,16 @@ class Builds extends Action
}
} finally {
/**
- * Send realtime Event
+ * Trigger Realtime Event
*/
- $target = Realtime::fromPayload(
- // Pass first, most verbose event pattern
- event: $allEvents[0],
- payload: $build,
- project: $project
- );
- Realtime::send(
- projectId: 'console',
- payload: $build->getArrayCopy(),
- events: $allEvents,
- channels: $target['channels'],
- roles: $target['roles']
- );
+ $queueForRealtime
+ ->setProject($project)
+ ->setSubscribers(['console'])
+ ->setEvent($event)
+ ->setParam('functionId', $function->getId())
+ ->setParam('deploymentId', $deployment->getId())
+ ->setPayload($build->getArrayCopy())
+ ->trigger();
/** Trigger usage queue */
if ($build->getAttribute('status') === 'ready') {
diff --git a/src/Appwrite/Platform/Workers/Certificates.php b/src/Appwrite/Platform/Workers/Certificates.php
index 7e220b2734..093e6fda5a 100644
--- a/src/Appwrite/Platform/Workers/Certificates.php
+++ b/src/Appwrite/Platform/Workers/Certificates.php
@@ -6,7 +6,8 @@ use Appwrite\Certificates\Adapter as CertificatesAdapter;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Mail;
-use Appwrite\Messaging\Adapter\Realtime;
+use Appwrite\Event\Realtime;
+use Appwrite\Event\Webhook;
use Appwrite\Network\Validator\CNAME;
use Appwrite\Template\Template;
use Appwrite\Utopia\Response\Model\Rule;
@@ -46,12 +47,14 @@ class Certificates extends Action
->inject('dbForPlatform')
->inject('queueForMails')
->inject('queueForEvents')
+ ->inject('queueForWebhooks')
->inject('queueForFunctions')
+ ->inject('queueForRealtime')
->inject('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)
+ fn (Message $message, Database $dbForPlatform, Mail $queueForMails, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Log $log, CertificatesAdapter $certificates) =>
+ $this->action($message, $dbForPlatform, $queueForMails, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $log, $certificates)
);
}
@@ -60,14 +63,16 @@ class Certificates extends Action
* @param Database $dbForPlatform
* @param Mail $queueForMails
* @param Event $queueForEvents
+ * @param Webhook $queueForWebhooks
* @param Func $queueForFunctions
+ * @param Realtime $queueForRealtime
* @param Log $log
* @param CertificatesAdapter $certificates
* @return void
* @throws Throwable
* @throws \Utopia\Database\Exception
*/
- public function action(Message $message, Database $dbForPlatform, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, Log $log, CertificatesAdapter $certificates): void
+ public function action(Message $message, Database $dbForPlatform, Mail $queueForMails, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Log $log, CertificatesAdapter $certificates): void
{
$payload = $message->getPayload() ?? [];
@@ -81,7 +86,7 @@ class Certificates extends Action
$log->addTag('domain', $domain->get());
- $this->execute($domain, $dbForPlatform, $queueForMails, $queueForEvents, $queueForFunctions, $log, $certificates, $skipRenewCheck);
+ $this->execute($domain, $dbForPlatform, $queueForMails, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $log, $certificates, $skipRenewCheck);
}
/**
@@ -90,13 +95,14 @@ class Certificates extends Action
* @param Mail $queueForMails
* @param Event $queueForEvents
* @param Func $queueForFunctions
+ * @param Realtime $queueForRealtime
* @param CertificatesAdapter $certificates
* @param bool $skipRenewCheck
* @return void
* @throws Throwable
* @throws \Utopia\Database\Exception
*/
- private function execute(Domain $domain, Database $dbForPlatform, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, Log $log, CertificatesAdapter $certificates, bool $skipRenewCheck = false): void
+ private function execute(Domain $domain, Database $dbForPlatform, Mail $queueForMails, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Log $log, CertificatesAdapter $certificates, bool $skipRenewCheck = false): void
{
/**
* 1. Read arguments and validate domain
@@ -186,7 +192,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, $dbForPlatform, $queueForEvents, $queueForFunctions);
+ $this->saveCertificateDocument($domain->get(), $certificate, $success, $dbForPlatform, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime);
}
}
@@ -199,13 +205,14 @@ class Certificates extends Action
* @param Database $dbForPlatform Database connection for console
* @param Event $queueForEvents
* @param Func $queueForFunctions
+ * @param Realtime $queueForRealtime
* @return void
* @throws \Utopia\Database\Exception
* @throws Authorization
* @throws Conflict
* @throws Structure
*/
- private function saveCertificateDocument(string $domain, Document $certificate, bool $success, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions): void
+ private function saveCertificateDocument(string $domain, Document $certificate, bool $success, Database $dbForPlatform, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime): void
{
// Check if update or insert required
$certificateDocument = $dbForPlatform->findOne('certificates', [Query::equal('domain', [$domain])]);
@@ -219,7 +226,7 @@ class Certificates extends Action
}
$certificateId = $certificate->getId();
- $this->updateDomainDocuments($certificateId, $domain, $success, $dbForPlatform, $queueForEvents, $queueForFunctions);
+ $this->updateDomainDocuments($certificateId, $domain, $success, $dbForPlatform, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime);
}
/**
@@ -338,7 +345,7 @@ class Certificates extends Action
*
* @return void
*/
- private function updateDomainDocuments(string $certificateId, string $domain, bool $success, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions): void
+ private function updateDomainDocuments(string $certificateId, string $domain, bool $success, Database $dbForPlatform, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime): void
{
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
@@ -367,50 +374,28 @@ class Certificates extends Action
return;
}
- /** Trigger Webhook */
$ruleModel = new Rule();
$queueForEvents
- ->setQueue(Event::WEBHOOK_QUEUE_NAME)
- ->setClass(Event::WEBHOOK_CLASS_NAME)
->setProject($project)
->setEvent('rules.[ruleId].update')
->setParam('ruleId', $rule->getId())
- ->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())))
- ->trigger();
+ ->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())));
+ /** Trigger Webhook */
+ $queueForWebhooks
+ ->from($queueForEvents)
+ ->trigger();
/** Trigger Functions */
$queueForFunctions
- ->setProject($project)
- ->setEvent('rules.[ruleId].update')
- ->setParam('ruleId', $rule->getId())
- ->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())))
+ ->from($queueForEvents)
->trigger();
- /** Trigger realtime event */
- $allEvents = Event::generateEvents('rules.[ruleId].update', [
- 'ruleId' => $rule->getId(),
- ]);
- $target = Realtime::fromPayload(
- // Pass first, most verbose event pattern
- event: $allEvents[0],
- payload: $rule,
- project: $project
- );
- Realtime::send(
- projectId: 'console',
- payload: $rule->getArrayCopy(),
- events: $allEvents,
- channels: $target['channels'],
- roles: $target['roles']
- );
- Realtime::send(
- projectId: $project->getId(),
- payload: $rule->getArrayCopy(),
- events: $allEvents,
- channels: $target['channels'],
- roles: $target['roles']
- );
+ /** Trigger Realtime Events */
+ $queueForRealtime
+ ->from($queueForEvents)
+ ->setSubscribers(['console', $projectId])
+ ->trigger();
}
}
}
diff --git a/src/Appwrite/Platform/Workers/Databases.php b/src/Appwrite/Platform/Workers/Databases.php
index 0f400e0107..44738b557c 100644
--- a/src/Appwrite/Platform/Workers/Databases.php
+++ b/src/Appwrite/Platform/Workers/Databases.php
@@ -2,8 +2,7 @@
namespace Appwrite\Platform\Workers;
-use Appwrite\Event\Event;
-use Appwrite\Messaging\Adapter\Realtime;
+use Appwrite\Event\Realtime;
use Exception;
use Utopia\CLI\Console;
use Utopia\Database\Database;
@@ -37,8 +36,9 @@ class Databases extends Action
->inject('project')
->inject('dbForPlatform')
->inject('dbForProject')
+ ->inject('queueForRealtime')
->inject('log')
- ->callback(fn (Message $message, Document $project, Database $dbForPlatform, Database $dbForProject, Log $log) => $this->action($message, $project, $dbForPlatform, $dbForProject, $log));
+ ->callback(fn (Message $message, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime, Log $log) => $this->action($message, $project, $dbForPlatform, $dbForProject, $queueForRealtime, $log));
}
/**
@@ -46,16 +46,17 @@ class Databases extends Action
* @param Document $project
* @param Database $dbForPlatform
* @param Database $dbForProject
+ * @param Realtime $queueForRealtime
* @param Log $log
* @return void
* @throws \Exception
*/
- public function action(Message $message, Document $project, Database $dbForPlatform, Database $dbForProject, Log $log): void
+ public function action(Message $message, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime, Log $log): void
{
$payload = $message->getPayload() ?? [];
if (empty($payload)) {
- throw new \Exception('Missing payload');
+ throw new Exception('Missing payload');
}
$type = $payload['type'];
@@ -75,11 +76,11 @@ 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, $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)),
+ DATABASE_TYPE_CREATE_ATTRIBUTE => $this->createAttribute($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $queueForRealtime),
+ DATABASE_TYPE_DELETE_ATTRIBUTE => $this->deleteAttribute($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $queueForRealtime),
+ DATABASE_TYPE_CREATE_INDEX => $this->createIndex($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $queueForRealtime),
+ DATABASE_TYPE_DELETE_INDEX => $this->deleteIndex($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $queueForRealtime),
+ default => throw new Exception('No database operation for type: ' . \strval($type)),
};
}
@@ -90,13 +91,14 @@ class Databases extends Action
* @param Document $project
* @param Database $dbForPlatform
* @param Database $dbForProject
+ * @param Realtime $queueForRealtime
* @return void
* @throws Authorization
* @throws Conflict
* @throws \Exception
* @throws \Throwable
*/
- private function createAttribute(Document $database, Document $collection, Document $attribute, Document $project, Database $dbForPlatform, Database $dbForProject): void
+ private function createAttribute(Document $database, Document $collection, Document $attribute, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime): void
{
if ($collection->isEmpty()) {
throw new Exception('Missing collection');
@@ -106,12 +108,7 @@ class Databases extends Action
}
$projectId = $project->getId();
-
- $events = Event::generateEvents('databases.[databaseId].collections.[collectionId].attributes.[attributeId].update', [
- 'databaseId' => $database->getId(),
- 'collectionId' => $collection->getId(),
- 'attributeId' => $attribute->getId()
- ]);
+ $event = "databases.[databaseId].collections.[collectionId].attributes.[attributeId].update";
/**
* TODO @christyjacob4 verify if this is still the case
* Fetch attribute from the database, since with Resque float values are loosing informations.
@@ -169,7 +166,7 @@ class Databases extends Action
break;
default:
if (!$dbForProject->createAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key, $type, $size, $required, $default, $signed, $array, $format, $formatOptions, $filters)) {
- throw new \Exception('Failed to create Attribute');
+ throw new Exception('Failed to create Attribute');
}
}
@@ -200,7 +197,7 @@ class Databases extends Action
throw $e;
} finally {
- $this->trigger($database, $collection, $attribute, $project, $projectId, $events);
+ $this->trigger($database, $collection, $project, $event, $queueForRealtime, $attribute);
if (! $relatedCollection->isEmpty()) {
$dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId());
@@ -217,13 +214,14 @@ class Databases extends Action
* @param Document $project
* @param Database $dbForPlatform
* @param Database $dbForProject
+ * @param Realtime $queueForRealtime
* @return void
* @throws Authorization
* @throws Conflict
* @throws \Exception
* @throws \Throwable
**/
- private function deleteAttribute(Document $database, Document $collection, Document $attribute, Document $project, Database $dbForPlatform, Database $dbForProject): void
+ private function deleteAttribute(Document $database, Document $collection, Document $attribute, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime): void
{
if ($collection->isEmpty()) {
throw new Exception('Missing collection');
@@ -233,15 +231,9 @@ class Databases extends Action
}
$projectId = $project->getId();
-
- $events = Event::generateEvents('databases.[databaseId].collections.[collectionId].attributes.[attributeId].delete', [
- 'databaseId' => $database->getId(),
- 'collectionId' => $collection->getId(),
- 'attributeId' => $attribute->getId()
- ]);
+ $event = 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].delete';
$collectionId = $collection->getId();
$key = $attribute->getAttribute('key', '');
- $status = $attribute->getAttribute('status', '');
$type = $attribute->getAttribute('type', '');
$project = $dbForPlatform->getDocument('projects', $projectId);
$options = $attribute->getAttribute('options', []);
@@ -312,7 +304,7 @@ class Databases extends Action
throw $e;
} finally {
- $this->trigger($database, $collection, $attribute, $project, $projectId, $events);
+ $this->trigger($database, $collection, $project, $event, $queueForRealtime, $attribute);
}
// The underlying database removes/rebuilds indexes when attribute is removed
@@ -358,7 +350,7 @@ class Databases extends Action
}
if ($exists) { // Delete the duplicate if created, else update in db
- $this->deleteIndex($database, $collection, $index, $project, $dbForPlatform, $dbForProject);
+ $this->deleteIndex($database, $collection, $index, $project, $dbForPlatform, $dbForProject, $queueForRealtime);
} else {
$dbForProject->updateDocument('indexes', $index->getId(), $index);
}
@@ -381,6 +373,7 @@ class Databases extends Action
* @param Document $project
* @param Database $dbForPlatform
* @param Database $dbForProject
+ * @param Realtime $queueForRealtime
* @return void
* @throws Authorization
* @throws Conflict
@@ -388,7 +381,7 @@ class Databases extends Action
* @throws DatabaseException
* @throws \Throwable
*/
- private function createIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject): void
+ private function createIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime): void
{
if ($collection->isEmpty()) {
throw new Exception('Missing collection');
@@ -398,12 +391,7 @@ class Databases extends Action
}
$projectId = $project->getId();
-
- $events = Event::generateEvents('databases.[databaseId].collections.[collectionId].indexes.[indexId].update', [
- 'databaseId' => $database->getId(),
- 'collectionId' => $collection->getId(),
- 'indexId' => $index->getId()
- ]);
+ $event = 'databases.[databaseId].collections.[collectionId].indexes.[indexId].update';
$collectionId = $collection->getId();
$key = $index->getAttribute('key', '');
$type = $index->getAttribute('type', '');
@@ -430,7 +418,7 @@ class Databases extends Action
throw $e;
} finally {
- $this->trigger($database, $collection, $index, $project, $projectId, $events);
+ $this->trigger($database, $collection, $project, $event, $queueForRealtime, null, $index);
$dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collectionId);
}
}
@@ -442,6 +430,7 @@ class Databases extends Action
* @param Document $project
* @param Database $dbForPlatform
* @param Database $dbForProject
+ * @param Realtime $queueForRealtime
* @return void
* @throws Authorization
* @throws Conflict
@@ -449,7 +438,7 @@ class Databases extends Action
* @throws DatabaseException
* @throws \Throwable
*/
- private function deleteIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject): void
+ private function deleteIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime): void
{
if ($collection->isEmpty()) {
throw new Exception('Missing collection');
@@ -459,12 +448,7 @@ class Databases extends Action
}
$projectId = $project->getId();
-
- $events = Event::generateEvents('databases.[databaseId].collections.[collectionId].indexes.[indexId].delete', [
- 'databaseId' => $database->getId(),
- 'collectionId' => $collection->getId(),
- 'indexId' => $index->getId()
- ]);
+ $event = 'databases.[databaseId].collections.[collectionId].indexes.[indexId].delete';
$key = $index->getAttribute('key');
$status = $index->getAttribute('status', '');
$project = $dbForPlatform->getDocument('projects', $projectId);
@@ -490,7 +474,7 @@ class Databases extends Action
throw $e;
} finally {
- $this->trigger($database, $collection, $index, $project, $projectId, $events);
+ $this->trigger($database, $collection, $project, $event, $queueForRealtime, null, $index);
$dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collection->getId());
}
}
@@ -532,7 +516,6 @@ class Databases extends Action
$collectionId = $collection->getId();
$collectionInternalId = $collection->getInternalId();
- $databaseId = $database->getId();
$databaseInternalId = $database->getInternalId();
$dbForProject->deleteCollection('database_' . $databaseInternalId . '_collection_' . $collection->getInternalId());
@@ -568,21 +551,22 @@ class Databases extends Action
/**
- * @param string $collection collectionID
+ * @param string $collectionId
* @param array $queries
* @param Database $database
* @param callable|null $callback
* @return void
* @throws Exception
*/
- protected function deleteByGroup(string $collection, array $queries, Database $database, callable $callback = null): void
+ protected function deleteByGroup(string $collectionId, array $queries, Database $database, callable $callback = null): void
{
$start = \microtime(true);
try {
- $documents = $database->deleteDocuments($collection, $queries);
+ $documents = $database->deleteDocuments($collectionId, $queries);
} catch (\Throwable $th) {
- Console::error('Failed to delete documents for collection ' . $collection . ': ' . $th->getMessage());
+ $tenant = $database->getSharedTables() ? 'Tenant:'.$database->getTenant() : '';
+ Console::error("Failed to delete documents for collection:{$database->getNamespace()}_{$collectionId} {$tenant} :{$th->getMessage()}");
return;
}
@@ -598,31 +582,42 @@ class Databases extends Action
Console::info("Deleted {$count} documents by group in " . ($end - $start) . " seconds");
}
+ /**
+ * @param Document $database
+ * @param Document $collection
+ * @param Document $project
+ * @param Realtime $queueForRealtime
+ * @param Document|null $attribute
+ * @param Document|null $index
+ * @return void
+ */
protected function trigger(
Document $database,
Document $collection,
- Document $attribute,
Document $project,
- string $projectId,
- array $events
+ string $event,
+ Realtime $queueForRealtime,
+ Document|null $attribute = null,
+ Document|null $index = null,
): void {
- $target = Realtime::fromPayload(
- // Pass first, most verbose event pattern
- event: $events[0],
- payload: $attribute,
- project: $project,
- );
- Realtime::send(
- projectId: 'console',
- payload: $attribute->getArrayCopy(),
- events: $events,
- channels: $target['channels'],
- roles: $target['roles'],
- options: [
- 'projectId' => $projectId,
- 'databaseId' => $database->getId(),
- 'collectionId' => $collection->getId()
- ]
- );
+ $queueForRealtime
+ ->setProject($project)
+ ->setSubscribers(['console'])
+ ->setEvent($event)
+ ->setParam('databaseId', $database->getId())
+ ->setParam('collectionId', $collection->getId());
+
+ if ($attribute !== null && !empty($attribute)) {
+ $queueForRealtime
+ ->setParam('attributeId', $attribute->getId())
+ ->setPayload($attribute->getArrayCopy());
+ }
+ if ($index !== null && !empty($index)) {
+ $queueForRealtime
+ ->setParam('indexId', $index->getId())
+ ->setPayload($index->getArrayCopy());
+ }
+
+ $queueForRealtime->trigger();
}
}
diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php
index e11181d199..5092599d67 100644
--- a/src/Appwrite/Platform/Workers/Deletes.php
+++ b/src/Appwrite/Platform/Workers/Deletes.php
@@ -31,6 +31,8 @@ use Utopia\System\System;
class Deletes extends Action
{
+ protected array $selects = ['$internalId', '$id', '$collection', '$permissions', '$updatedAt'];
+
public static function getName(): string
{
return 'deletes';
@@ -47,18 +49,19 @@ class Deletes extends Action
->inject('project')
->inject('dbForPlatform')
->inject('getProjectDB')
- ->inject('timelimit')
+ ->inject('getLogsDB')
->inject('deviceForFiles')
->inject('deviceForFunctions')
->inject('deviceForBuilds')
->inject('deviceForCache')
->inject('certificates')
+ ->inject('executor')
->inject('executionRetention')
->inject('auditRetention')
->inject('log')
->callback(
- fn ($message, Document $project, Database $dbForPlatform, callable $getProjectDB, callable $timelimit, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, string $executionRetention, string $auditRetention, Log $log) =>
- $this->action($message, $project, $dbForPlatform, $getProjectDB, $timelimit, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $executionRetention, $auditRetention, $log)
+ fn ($message, Document $project, Database $dbForPlatform, callable $getProjectDB, callable $getLogsDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, Executor $executor, string $executionRetention, string $auditRetention, Log $log) =>
+ $this->action($message, $project, $dbForPlatform, $getProjectDB, $getLogsDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $executor, $executionRetention, $auditRetention, $log)
);
}
@@ -66,7 +69,7 @@ class Deletes extends Action
* @throws Exception
* @throws Throwable
*/
- public function action(Message $message, Document $project, Database $dbForPlatform, callable $getProjectDB, callable $timelimit, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, string $executionRetention, string $auditRetention, Log $log): void
+ public function action(Message $message, Document $project, Database $dbForPlatform, callable $getProjectDB, callable $getLogsDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, Executor $executor, string $executionRetention, string $auditRetention, Log $log): void
{
$payload = $message->getPayload() ?? [];
@@ -91,10 +94,10 @@ class Deletes extends Action
$this->deleteProject($dbForPlatform, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $document);
break;
case DELETE_TYPE_FUNCTIONS:
- $this->deleteFunction($dbForPlatform, $getProjectDB, $deviceForFunctions, $deviceForBuilds, $certificates, $document, $project);
+ $this->deleteFunction($dbForPlatform, $getProjectDB, $deviceForFunctions, $deviceForBuilds, $certificates, $document, $project, $executor);
break;
case DELETE_TYPE_DEPLOYMENTS:
- $this->deleteDeployment($getProjectDB, $deviceForFunctions, $deviceForBuilds, $document, $project);
+ $this->deleteDeployment($getProjectDB, $deviceForFunctions, $deviceForBuilds, $document, $project, $executor);
break;
case DELETE_TYPE_USERS:
$this->deleteUser($getProjectDB, $document, $project);
@@ -131,7 +134,7 @@ class Deletes extends Action
$this->deleteExpiredSessions($project, $getProjectDB);
break;
case DELETE_TYPE_USAGE:
- $this->deleteUsageStats($project, $getProjectDB, $hourlyUsageRetentionDatetime);
+ $this->deleteUsageStats($project, $getProjectDB, $getLogsDB, $hourlyUsageRetentionDatetime);
break;
case DELETE_TYPE_CACHE_BY_RESOURCE:
$this->deleteCacheByResource($project, $getProjectDB, $resource, $resourceType);
@@ -158,7 +161,7 @@ class Deletes extends Action
$this->deleteExpiredTargets($project, $getProjectDB);
$this->deleteExecutionLogs($project, $getProjectDB, $executionRetention);
$this->deleteAuditLogs($project, $getProjectDB, $auditRetention);
- $this->deleteUsageStats($project, $getProjectDB, $hourlyUsageRetentionDatetime);
+ $this->deleteUsageStats($project, $getProjectDB, $getLogsDB, $hourlyUsageRetentionDatetime);
$this->deleteExpiredSessions($project, $getProjectDB);
break;
default:
@@ -180,10 +183,17 @@ class Deletes extends Action
*/
private function deleteSchedules(Database $dbForPlatform, callable $getProjectDB, string $datetime): void
{
+ // Temporarly accepting both 'fra' and 'default'
+ // When all migrated, only use _APP_REGION with 'default' as default value
+ $regions = [System::getEnv('_APP_REGION', 'default')];
+ if (!in_array('default', $regions)) {
+ $regions[] = 'default';
+ }
+
$this->listByGroup(
'schedules',
[
- Query::equal('region', [System::getEnv('_APP_REGION', 'default')]),
+ Query::equal('region', $regions),
Query::lessThanEqual('resourceUpdatedAt', $datetime),
Query::equal('active', [false]),
],
@@ -248,7 +258,8 @@ class Deletes extends Action
$this->deleteByGroup(
'subscribers',
[
- Query::equal('topicInternalId', [$topic->getInternalId()])
+ Query::equal('topicInternalId', [$topic->getInternalId()]),
+ Query::orderAsc(),
],
$getProjectDB($project)
);
@@ -269,7 +280,8 @@ class Deletes extends Action
$this->deleteByGroup(
'subscribers',
[
- Query::equal('targetInternalId', [$target->getInternalId()])
+ Query::equal('targetInternalId', [$target->getInternalId()]),
+ Query::orderAsc(),
],
$dbForProject,
function (Document $subscriber) use ($dbForProject, $target) {
@@ -306,7 +318,8 @@ class Deletes extends Action
$this->deleteByGroup(
'targets',
[
- Query::equal('expired', [true])
+ Query::equal('expired', [true]),
+ Query::orderAsc(),
],
$getProjectDB($project),
function (Document $target) use ($getProjectDB, $project) {
@@ -320,7 +333,8 @@ class Deletes extends Action
$this->deleteByGroup(
'targets',
[
- Query::equal('sessionInternalId', [$session->getInternalId()])
+ Query::equal('sessionInternalId', [$session->getInternalId()]),
+ Query::orderAsc(),
],
$getProjectDB($project),
function (Document $target) use ($getProjectDB, $project) {
@@ -347,14 +361,20 @@ class Deletes extends Action
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId)
);
- $query[] = Query::equal('resource', [$resource]);
+ $queries = [
+ Query::equal('resource', [$resource])
+ ];
+
if (!empty($resourceType)) {
- $query[] = Query::equal('resourceType', [$resourceType]);
+ $queries[] = Query::equal('resourceType', [$resourceType]);
}
+ $queries[] = Query::select($this->selects);
+ $queries[] = Query::orderAsc();
+
$this->deleteByGroup(
'cache',
- $query,
+ $queries,
$dbForProject,
function (Document $document) use ($cache, $projectId) {
$path = APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId . DIRECTORY_SEPARATOR . $document->getId();
@@ -385,13 +405,16 @@ class Deletes extends Action
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId)
);
- $query = [
+ $queries = [
+ Query::select([...$this->selects, 'accessedAt']),
Query::lessThan('accessedAt', $datetime),
+ Query::orderDesc('accessedAt'),
+ Query::orderDesc(),
];
$this->deleteByGroup(
'cache',
- $query,
+ $queries,
$dbForProject,
function (Document $document) use ($cache, $projectId) {
$path = APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId . DIRECTORY_SEPARATOR . $document->getId();
@@ -412,14 +435,35 @@ class Deletes extends Action
* @return void
* @throws Exception
*/
- private function deleteUsageStats(Document $project, callable $getProjectDB, string $hourlyUsageRetentionDatetime): void
+ private function deleteUsageStats(Document $project, callable $getProjectDB, callable $getLogsDB, string $hourlyUsageRetentionDatetime): void
{
+ /** @var Database $dbForProject*/
$dbForProject = $getProjectDB($project);
- // Delete Usage stats
+
+ $selects = [...$this->selects, 'time'];
+
+ // Delete Usage stats from projectDB
$this->deleteByGroup('stats', [
- Query::lessThan('time', $hourlyUsageRetentionDatetime),
+ Query::select($selects),
Query::equal('period', ['1h']),
+ Query::lessThan('time', $hourlyUsageRetentionDatetime),
+ Query::orderDesc('time'),
+ Query::orderDesc(),
], $dbForProject);
+
+ if ($project->getId() !== 'console') {
+ /** @var Database $dbForLogs*/
+ $dbForLogs = call_user_func($getLogsDB, $project);
+
+ // Delete Usage stats from logsDB
+ $this->deleteByGroup('stats', [
+ Query::select($selects),
+ Query::equal('period', ['1h']),
+ Query::lessThan('time', $hourlyUsageRetentionDatetime),
+ Query::orderDesc('time'),
+ Query::orderDesc(),
+ ], $dbForLogs);
+ }
}
/**
@@ -438,7 +482,8 @@ class Deletes extends Action
$this->deleteByGroup(
'memberships',
[
- Query::equal('teamInternalId', [$teamInternalId])
+ Query::equal('teamInternalId', [$teamInternalId]),
+ Query::orderAsc()
],
$dbForProject,
function (Document $membership) use ($dbForProject) {
@@ -528,7 +573,13 @@ class Deletes extends Action
if ($projectTables || !\in_array($collection->getId(), $projectCollectionIds)) {
$dbForProject->deleteCollection($collection->getId());
} else {
- $this->deleteByGroup($collection->getId(), [], database: $dbForProject);
+ $this->deleteByGroup(
+ $collection->getId(),
+ [
+ Query::orderAsc()
+ ],
+ database: $dbForProject
+ );
}
} catch (Throwable $e) {
Console::error('Error deleting '.$collection->getId().' '.$e->getMessage());
@@ -548,58 +599,78 @@ class Deletes extends Action
// Delete Platforms
$this->deleteByGroup('platforms', [
- Query::equal('projectInternalId', [$projectInternalId])
+ Query::equal('projectInternalId', [$projectInternalId]),
+ Query::orderAsc()
], $dbForPlatform);
// Delete project and function rules
$this->deleteByGroup('rules', [
- Query::equal('projectInternalId', [$projectInternalId])
+ Query::equal('projectInternalId', [$projectInternalId]),
+ Query::orderAsc()
], $dbForPlatform, function (Document $document) use ($dbForPlatform, $certificates) {
$this->deleteRule($dbForPlatform, $document, $certificates);
});
// Delete Keys
$this->deleteByGroup('keys', [
- Query::equal('projectInternalId', [$projectInternalId])
+ Query::equal('projectInternalId', [$projectInternalId]),
+ Query::orderAsc()
], $dbForPlatform);
// Delete Webhooks
$this->deleteByGroup('webhooks', [
- Query::equal('projectInternalId', [$projectInternalId])
+ Query::equal('projectInternalId', [$projectInternalId]),
+ Query::orderAsc()
], $dbForPlatform);
// Delete VCS Installations
$this->deleteByGroup('installations', [
- Query::equal('projectInternalId', [$projectInternalId])
+ Query::equal('projectInternalId', [$projectInternalId]),
+ Query::orderAsc()
], $dbForPlatform);
// Delete VCS Repositories
$this->deleteByGroup('repositories', [
Query::equal('projectInternalId', [$projectInternalId]),
+ Query::orderAsc()
], $dbForPlatform);
// Delete VCS comments
$this->deleteByGroup('vcsComments', [
Query::equal('projectInternalId', [$projectInternalId]),
+ Query::orderAsc()
], $dbForPlatform);
- // Delete Schedules (No projectInternalId in this collection)
+ // Delete Schedules
$this->deleteByGroup('schedules', [
Query::equal('projectId', [$projectId]),
+ Query::orderAsc()
], $dbForPlatform);
// Delete metadata table
if ($projectTables) {
$dbForProject->deleteCollection(Database::METADATA);
} elseif ($sharedTablesV1) {
- $this->deleteByGroup(Database::METADATA, [], $dbForProject);
+ $this->deleteByGroup(
+ Database::METADATA,
+ [
+ Query::orderAsc()
+ ],
+ $dbForProject
+ );
} elseif ($sharedTablesV2) {
$queries = \array_map(
fn ($id) => Query::notEqual('$id', $id),
$projectCollectionIds
);
- $this->deleteByGroup(Database::METADATA, $queries, $dbForProject);
+ $queries[] = Query::orderAsc();
+
+ $this->deleteByGroup(
+ Database::METADATA,
+ $queries,
+ $dbForProject
+ );
}
// Delete all storage directories
@@ -624,14 +695,16 @@ class Deletes extends Action
// Delete all sessions of this user from the sessions table and update the sessions field of the user record
$this->deleteByGroup('sessions', [
- Query::equal('userInternalId', [$userInternalId])
+ Query::equal('userInternalId', [$userInternalId]),
+ Query::orderAsc()
], $dbForProject);
$dbForProject->purgeCachedDocument('users', $userId);
// Delete Memberships and decrement team membership counts
$this->deleteByGroup('memberships', [
- Query::equal('userInternalId', [$userInternalId])
+ Query::equal('userInternalId', [$userInternalId]),
+ Query::orderAsc()
], $dbForProject, function (Document $document) use ($dbForProject) {
if ($document->getAttribute('confirm')) { // Count only confirmed members
$teamId = $document->getAttribute('teamId');
@@ -644,19 +717,22 @@ class Deletes extends Action
// Delete tokens
$this->deleteByGroup('tokens', [
- Query::equal('userInternalId', [$userInternalId])
+ Query::equal('userInternalId', [$userInternalId]),
+ Query::orderAsc()
], $dbForProject);
// Delete identities
$this->deleteByGroup('identities', [
- Query::equal('userInternalId', [$userInternalId])
+ Query::equal('userInternalId', [$userInternalId]),
+ Query::orderAsc()
], $dbForProject);
// Delete targets
$this->deleteByGroup(
'targets',
[
- Query::equal('userInternalId', [$userInternalId])
+ Query::equal('userInternalId', [$userInternalId]),
+ Query::orderAsc()
],
$dbForProject,
function (Document $target) use ($getProjectDB, $project) {
@@ -675,9 +751,13 @@ class Deletes extends Action
private function deleteExecutionLogs(Document $project, callable $getProjectDB, string $datetime): void
{
$dbForProject = $getProjectDB($project);
+
// Delete Executions
$this->deleteByGroup('executions', [
- Query::lessThan('$createdAt', $datetime)
+ Query::select([...$this->selects, '$createdAt']),
+ Query::lessThan('$createdAt', $datetime),
+ Query::orderDesc('$createdAt'),
+ Query::orderDesc(),
], $dbForProject);
}
@@ -695,7 +775,10 @@ class Deletes extends Action
// Delete Sessions
$this->deleteByGroup('sessions', [
- Query::lessThan('$createdAt', $expired)
+ Query::select([...$this->selects, '$createdAt']),
+ Query::lessThan('$createdAt', $expired),
+ Query::orderDesc('$createdAt'),
+ Query::orderDesc(),
], $dbForProject);
}
@@ -709,14 +792,16 @@ class Deletes extends Action
{
// Delete Dead Realtime Logs
$this->deleteByGroup('realtime', [
- Query::lessThan('timestamp', $datetime)
+ Query::lessThan('timestamp', $datetime),
+ Query::orderDesc('timestamp'),
+ Query::orderAsc(),
], $dbForPlatform);
}
/**
* @param Database $dbForPlatform
* @param callable $getProjectDB
- * @param string $datetime
+ * @param string $auditRetention
* @return void
* @throws Exception
*/
@@ -724,10 +809,14 @@ class Deletes extends Action
{
$projectId = $project->getId();
$dbForProject = $getProjectDB($project);
- $audit = new Audit($dbForProject);
try {
- $audit->cleanup($auditRetention);
+ $this->deleteByGroup(Audit::COLLECTION, [
+ Query::select([...$this->selects, 'time']),
+ Query::lessThan('time', $auditRetention),
+ Query::orderDesc('time'),
+ Query::orderAsc(),
+ ], $dbForProject);
} catch (DatabaseException $e) {
Console::error('Failed to delete audit logs for project ' . $projectId . ': ' . $e->getMessage());
}
@@ -739,10 +828,11 @@ class Deletes extends Action
* @param Device $deviceForBuilds
* @param Document $document function document
* @param Document $project
+ * @param Executor $executor
* @return void
* @throws Exception
*/
- private function deleteFunction(Database $dbForPlatform, callable $getProjectDB, Device $deviceForFunctions, Device $deviceForBuilds, CertificatesAdapter $certificates, Document $document, Document $project): void
+ private function deleteFunction(Database $dbForPlatform, callable $getProjectDB, Device $deviceForFunctions, Device $deviceForBuilds, CertificatesAdapter $certificates, Document $document, Document $project, Executor $executor): void
{
$projectId = $project->getId();
$dbForProject = $getProjectDB($project);
@@ -754,9 +844,10 @@ class Deletes extends Action
*/
Console::info("Deleting rules for function " . $functionId);
$this->deleteByGroup('rules', [
- Query::equal('resourceType', ['function']),
+ Query::equal('projectInternalId', [$project->getInternalId()]),
Query::equal('resourceInternalId', [$functionInternalId]),
- Query::equal('projectInternalId', [$project->getInternalId()])
+ Query::equal('resourceType', ['function']),
+ Query::orderAsc()
], $dbForPlatform, function (Document $document) use ($project, $dbForPlatform, $certificates) {
$this->deleteRule($dbForPlatform, $document, $certificates);
});
@@ -766,8 +857,9 @@ class Deletes extends Action
*/
Console::info("Deleting variables for function " . $functionId);
$this->deleteByGroup('variables', [
+ Query::equal('resourceInternalId', [$functionInternalId]),
Query::equal('resourceType', ['function']),
- Query::equal('resourceInternalId', [$functionInternalId])
+ Query::orderAsc()
], $dbForProject);
/**
@@ -777,7 +869,8 @@ class Deletes extends Action
$deploymentInternalIds = [];
$this->deleteByGroup('deployments', [
- Query::equal('resourceInternalId', [$functionInternalId])
+ Query::equal('resourceInternalId', [$functionInternalId]),
+ Query::orderAsc()
], $dbForProject, function (Document $document) use ($deviceForFunctions, &$deploymentInternalIds) {
$deploymentInternalIds[] = $document->getInternalId();
$this->deleteDeploymentFiles($deviceForFunctions, $document);
@@ -790,7 +883,8 @@ class Deletes extends Action
foreach ($deploymentInternalIds as $deploymentInternalId) {
$this->deleteByGroup('builds', [
- Query::equal('deploymentInternalId', [$deploymentInternalId])
+ Query::equal('deploymentInternalId', [$deploymentInternalId]),
+ Query::orderAsc()
], $dbForProject, function (Document $document) use ($deviceForBuilds) {
$this->deleteBuildFiles($deviceForBuilds, $document);
});
@@ -801,7 +895,9 @@ class Deletes extends Action
*/
Console::info("Deleting executions for function " . $functionId);
$this->deleteByGroup('executions', [
- Query::equal('functionInternalId', [$functionInternalId])
+ Query::select($this->selects),
+ Query::equal('functionInternalId', [$functionInternalId]),
+ Query::orderAsc()
], $dbForProject);
/**
@@ -812,12 +908,15 @@ class Deletes extends Action
Query::equal('projectInternalId', [$project->getInternalId()]),
Query::equal('resourceInternalId', [$functionInternalId]),
Query::equal('resourceType', ['function']),
+ Query::orderAsc()
], $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]),
+ Query::orderAsc()
], $dbForPlatform);
});
@@ -825,7 +924,7 @@ class Deletes extends Action
* Request executor to delete all deployment containers
*/
Console::info("Requesting executor to delete all deployment containers for function " . $functionId);
- $this->deleteRuntimes($getProjectDB, $document, $project);
+ $this->deleteRuntimes($getProjectDB, $document, $project, $executor);
}
/**
@@ -851,7 +950,7 @@ class Deletes extends Action
} else {
Console::error('Failed to delete deployment files: ' . $deploymentPath);
}
- } catch (\Throwable $th) {
+ } catch (Throwable $th) {
Console::error('Failed to delete deployment files: ' . $deploymentPath);
Console::error('[Error] Type: ' . get_class($th));
Console::error('[Error] Message: ' . $th->getMessage());
@@ -881,7 +980,7 @@ class Deletes extends Action
} else {
Console::error('Failed to delete build files: ' . $buildPath);
}
- } catch (\Throwable $th) {
+ } catch (Throwable $th) {
Console::error('Failed to delete deployment files: ' . $buildPath);
Console::error('[Error] Type: ' . get_class($th));
Console::error('[Error] Message: ' . $th->getMessage());
@@ -896,10 +995,11 @@ class Deletes extends Action
* @param Device $deviceForBuilds
* @param Document $document
* @param Document $project
+ * @param Executor $executor
* @return void
* @throws Exception
*/
- private function deleteDeployment(callable $getProjectDB, Device $deviceForFunctions, Device $deviceForBuilds, Document $document, Document $project): void
+ private function deleteDeployment(callable $getProjectDB, Device $deviceForFunctions, Device $deviceForBuilds, Document $document, Document $project, Executor $executor): void
{
$projectId = $project->getId();
$dbForProject = $getProjectDB($project);
@@ -917,7 +1017,8 @@ class Deletes extends Action
Console::info("Deleting builds for deployment " . $deploymentId);
$this->deleteByGroup('builds', [
- Query::equal('deploymentInternalId', [$deploymentInternalId])
+ Query::equal('deploymentInternalId', [$deploymentInternalId]),
+ Query::orderAsc()
], $dbForProject, function (Document $document) use ($deviceForBuilds) {
$this->deleteBuildFiles($deviceForBuilds, $document);
});
@@ -926,7 +1027,7 @@ class Deletes extends Action
* Request executor to delete all deployment containers
*/
Console::info("Requesting executor to delete deployment container for deployment " . $deploymentId);
- $this->deleteRuntimes($getProjectDB, $document, $project);
+ $this->deleteRuntimes($getProjectDB, $document, $project, $executor);
}
/**
@@ -935,7 +1036,7 @@ class Deletes extends Action
* @param Database $database
* @param ?callable $callback
* @return void
- * @throws Exception
+ * @throws DatabaseException
*/
protected function deleteByGroup(
string $collection,
@@ -945,10 +1046,15 @@ class Deletes extends Action
): void {
$start = \microtime(true);
+ /**
+ * deleteDocuments uses a cursor, we need to add a unique order by field or use default
+ */
+
try {
$documents = $database->deleteDocuments($collection, $queries);
- } catch (\Throwable $th) {
- Console::error('Failed to delete documents for collection ' . $collection . ': ' . $th->getMessage());
+ } catch (Throwable $th) {
+ $tenant = $database->getSharedTables() ? 'Tenant:'.$database->getTenant() : '';
+ Console::error("Failed to delete documents for collection:{$database->getNamespace()}_{$collection} {$tenant} :{$th->getMessage()}");
return;
}
@@ -1077,13 +1183,12 @@ class Deletes extends Action
* @param callable $getProjectDB
* @param ?Document $function
* @param Document $project
+ * @param Executor $executor
* @return void
* @throws Exception
*/
- private function deleteRuntimes(callable $getProjectDB, ?Document $function, Document $project): void
+ private function deleteRuntimes(callable $getProjectDB, ?Document $function, Document $project, Executor $executor): void
{
- $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST'));
-
$deleteByFunction = function (Document $function) use ($getProjectDB, $project, $executor) {
$this->listByGroup(
'deployments',
diff --git a/src/Appwrite/Platform/Workers/Functions.php b/src/Appwrite/Platform/Workers/Functions.php
index 0a7c39c02f..4e1794c085 100644
--- a/src/Appwrite/Platform/Workers/Functions.php
+++ b/src/Appwrite/Platform/Workers/Functions.php
@@ -5,8 +5,9 @@ namespace Appwrite\Platform\Workers;
use Ahc\Jwt\JWT;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
+use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage;
-use Appwrite\Messaging\Adapter\Realtime;
+use Appwrite\Event\Webhook;
use Appwrite\Utopia\Response\Model\Execution;
use Exception;
use Executor\Executor;
@@ -44,15 +45,18 @@ class Functions extends Action
->inject('project')
->inject('message')
->inject('dbForProject')
+ ->inject('queueForWebhooks')
->inject('queueForFunctions')
+ ->inject('queueForRealtime')
->inject('queueForEvents')
->inject('queueForStatsUsage')
->inject('log')
+ ->inject('executor')
->inject('isResourceBlocked')
- ->callback(fn (Document $project, Message $message, Database $dbForProject, Func $queueForFunctions, Event $queueForEvents, StatsUsage $queueForStatsUsage, Log $log, callable $isResourceBlocked) => $this->action($project, $message, $dbForProject, $queueForFunctions, $queueForEvents, $queueForStatsUsage, $log, $isResourceBlocked));
+ ->callback(fn (Document $project, Message $message, Database $dbForProject, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Event $queueForEvents, StatsUsage $queueForStatsUsage, Log $log, Executor $executor, callable $isResourceBlocked) => $this->action($project, $message, $dbForProject, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $queueForEvents, $queueForStatsUsage, $log, $executor, $isResourceBlocked));
}
- public function action(Document $project, Message $message, Database $dbForProject, Func $queueForFunctions, Event $queueForEvents, StatsUsage $queueForStatsUsage, Log $log, callable $isResourceBlocked): void
+ public function action(Document $project, Message $message, Database $dbForProject, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Event $queueForEvents, StatsUsage $queueForStatsUsage, Log $log, Executor $executor, callable $isResourceBlocked): void
{
$payload = $message->getPayload() ?? [];
@@ -136,11 +140,14 @@ class Functions extends Action
$this->execute(
log: $log,
dbForProject: $dbForProject,
+ queueForWebhooks: $queueForWebhooks,
queueForFunctions: $queueForFunctions,
+ queueForRealtime: $queueForRealtime,
queueForStatsUsage: $queueForStatsUsage,
queueForEvents: $queueForEvents,
project: $project,
function: $function,
+ executor: $executor,
trigger: 'event',
path: '/',
method: 'POST',
@@ -176,11 +183,14 @@ class Functions extends Action
$this->execute(
log: $log,
dbForProject: $dbForProject,
+ queueForWebhooks: $queueForWebhooks,
queueForFunctions: $queueForFunctions,
+ queueForRealtime: $queueForRealtime,
queueForStatsUsage: $queueForStatsUsage,
queueForEvents: $queueForEvents,
project: $project,
function: $function,
+ executor: $executor,
trigger: 'http',
path: $path,
method: $method,
@@ -198,11 +208,14 @@ class Functions extends Action
$this->execute(
log: $log,
dbForProject: $dbForProject,
+ queueForWebhooks: $queueForWebhooks,
queueForFunctions: $queueForFunctions,
+ queueForRealtime: $queueForRealtime,
queueForStatsUsage: $queueForStatsUsage,
queueForEvents: $queueForEvents,
project: $project,
function: $function,
+ executor: $executor,
trigger: 'schedule',
path: $path,
method: $method,
@@ -284,10 +297,12 @@ class Functions extends Action
* @param Log $log
* @param Database $dbForProject
* @param Func $queueForFunctions
+ * @param Realtime $queueForRealtime
* @param StatsUsage $queueForStatsUsage
* @param Event $queueForEvents
* @param Document $project
* @param Document $function
+ * @param Executor $executor
* @param string $trigger
* @param string $path
* @param string $method
@@ -307,11 +322,14 @@ class Functions extends Action
private function execute(
Log $log,
Database $dbForProject,
+ Webhook $queueForWebhooks,
Func $queueForFunctions,
+ Realtime $queueForRealtime,
StatsUsage $queueForStatsUsage,
Event $queueForEvents,
Document $project,
Document $function,
+ Executor $executor,
string $trigger,
string $path,
string $method,
@@ -502,7 +520,6 @@ class Functions extends Action
try {
$version = $function->getAttribute('version', 'v2');
$command = $runtime['startCommand'];
- $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST'));
$command = $version === 'v2' ? '' : 'cp /tmp/code.tar.gz /mnt/code/code.tar.gz && nohup helpers/start.sh "' . $command . '"';
$executionResponse = $executor->createExecution(
projectId: $project->getId(),
@@ -564,20 +581,20 @@ class Functions extends Action
;
}
-
$execution = $dbForProject->updateDocument('executions', $executionId, $execution);
- /** Trigger Webhook */
$executionModel = new Execution();
$queueForEvents
- ->setQueue(Event::WEBHOOK_QUEUE_NAME)
- ->setClass(Event::WEBHOOK_CLASS_NAME)
->setProject($project)
->setUser($user)
->setEvent('functions.[functionId].executions.[executionId].update')
->setParam('functionId', $function->getId())
->setParam('executionId', $execution->getId())
- ->setPayload($execution->getArrayCopy(array_keys($executionModel->getRules())))
+ ->setPayload($execution->getArrayCopy(array_keys($executionModel->getRules())));
+
+ /** Trigger Webhook */
+ $queueForWebhooks
+ ->from($queueForEvents)
->trigger();
/** Trigger Functions */
@@ -585,31 +602,11 @@ class Functions extends Action
->from($queueForEvents)
->trigger();
- /** Trigger realtime event */
- $allEvents = Event::generateEvents('functions.[functionId].executions.[executionId].update', [
- 'functionId' => $function->getId(),
- 'executionId' => $execution->getId()
- ]);
- $target = Realtime::fromPayload(
- // Pass first, most verbose event pattern
- event: $allEvents[0],
- payload: $execution,
- project: $project
- );
- Realtime::send(
- projectId: 'console',
- payload: $execution->getArrayCopy(),
- events: $allEvents,
- channels: $target['channels'],
- roles: $target['roles']
- );
- Realtime::send(
- projectId: $project->getId(),
- payload: $execution->getArrayCopy(),
- events: $allEvents,
- channels: $target['channels'],
- roles: $target['roles']
- );
+ /** Trigger Realtime Events */
+ $queueForRealtime
+ ->from($queueForEvents)
+ ->setSubscribers(['console', $project->getId()])
+ ->trigger();
if (!empty($error)) {
throw new Exception($error, $errorCode);
diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php
index ed58f59418..51c32fa768 100644
--- a/src/Appwrite/Platform/Workers/Migrations.php
+++ b/src/Appwrite/Platform/Workers/Migrations.php
@@ -3,8 +3,7 @@
namespace Appwrite\Platform\Workers;
use Ahc\Jwt\JWT;
-use Appwrite\Event\Event;
-use Appwrite\Messaging\Adapter\Realtime;
+use Appwrite\Event\Realtime;
use Exception;
use Utopia\CLI\Console;
use Utopia\Config\Config;
@@ -19,12 +18,14 @@ use Utopia\Migration\Destinations\Appwrite as DestinationAppwrite;
use Utopia\Migration\Exception as MigrationException;
use Utopia\Migration\Source;
use Utopia\Migration\Sources\Appwrite as SourceAppwrite;
+use Utopia\Migration\Sources\CSV;
use Utopia\Migration\Sources\Firebase;
use Utopia\Migration\Sources\NHost;
use Utopia\Migration\Sources\Supabase;
use Utopia\Migration\Transfer;
use Utopia\Platform\Action;
use Utopia\Queue\Message;
+use Utopia\Storage\Device;
use Utopia\System\System;
class Migrations extends Action
@@ -33,8 +34,13 @@ class Migrations extends Action
protected Database $dbForPlatform;
+ protected Device $deviceForImports;
+
protected Document $project;
+ /**
+ * @var callable
+ */
protected $logError;
public static function getName(): string
@@ -54,15 +60,18 @@ class Migrations extends Action
->inject('dbForProject')
->inject('dbForPlatform')
->inject('logError')
- ->callback(fn (Message $message, Document $project, Database $dbForProject, Database $dbForPlatform, callable $logError) => $this->action($message, $project, $dbForProject, $dbForPlatform, $logError));
+ ->inject('queueForRealtime')
+ ->inject('deviceForImports')
+ ->callback(fn (Message $message, Document $project, Database $dbForProject, Database $dbForPlatform, callable $logError, Realtime $queueForRealtime, Device $deviceForImports) => $this->action($message, $project, $dbForProject, $dbForPlatform, $logError, $queueForRealtime, $deviceForImports));
}
/**
* @throws Exception
*/
- public function action(Message $message, Document $project, Database $dbForProject, Database $dbForPlatform, callable $logError): void
+ public function action(Message $message, Document $project, Database $dbForProject, Database $dbForPlatform, callable $logError, Realtime $queueForRealtime, Device $deviceForImports): void
{
$payload = $message->getPayload() ?? [];
+ $this->deviceForImports = $deviceForImports;
if (empty($payload)) {
throw new Exception('Missing payload');
@@ -87,7 +96,7 @@ class Migrations extends Action
return;
}
- $this->processMigration($migration);
+ $this->processMigration($migration, $queueForRealtime);
}
/**
@@ -96,7 +105,9 @@ class Migrations extends Action
protected function processSource(Document $migration): Source
{
$source = $migration->getAttribute('source');
+ $resourceId = $migration->getAttribute('resourceId');
$credentials = $migration->getAttribute('credentials');
+ $migrationOptions = $migration->getAttribute('options');
return match ($source) {
Firebase::getName() => new Firebase(
@@ -125,6 +136,12 @@ class Migrations extends Action
$credentials['endpoint'] === 'http://localhost/v1' ? 'http://appwrite/v1' : $credentials['endpoint'],
$credentials['apiKey'],
),
+ CSV::getName() => new CSV(
+ $resourceId,
+ $migrationOptions['path'],
+ $this->deviceForImports,
+ $this->dbForProject
+ ),
default => throw new \Exception('Invalid source type'),
};
}
@@ -155,34 +172,16 @@ class Migrations extends Action
* @throws \Utopia\Database\Exception
* @throws Exception
*/
- protected function updateMigrationDocument(Document $migration, Document $project): Document
+ protected function updateMigrationDocument(Document $migration, Document $project, Realtime $queueForRealtime): Document
{
- /** Trigger Realtime */
- $allEvents = Event::generateEvents('migrations.[migrationId].update', [
- 'migrationId' => $migration->getId(),
- ]);
-
- $target = Realtime::fromPayload(
- event: $allEvents[0],
- payload: $migration,
- project: $project
- );
-
- Realtime::send(
- projectId: 'console',
- payload: $migration->getArrayCopy(),
- events: $allEvents,
- channels: $target['channels'],
- roles: $target['roles'],
- );
-
- Realtime::send(
- projectId: $project->getId(),
- payload: $migration->getArrayCopy(),
- events: $allEvents,
- channels: $target['channels'],
- roles: $target['roles'],
- );
+ /** Trigger Realtime Events */
+ $queueForRealtime
+ ->setProject($project)
+ ->setSubscribers(['console', $project->getId()])
+ ->setEvent('migrations.[migrationId].update')
+ ->setParam('migrationId', $migration->getId())
+ ->setPayload($migration->getArrayCopy())
+ ->trigger();
return $this->dbForProject->updateDocument('migrations', $migration->getId(), $migration);
}
@@ -234,7 +233,7 @@ class Migrations extends Action
* @throws \Utopia\Database\Exception
* @throws Exception
*/
- protected function processMigration(Document $migration): void
+ protected function processMigration(Document $migration, Realtime $queueForRealtime): void
{
$project = $this->project;
$projectDocument = $this->dbForPlatform->getDocument('projects', $project->getId());
@@ -258,7 +257,7 @@ class Migrations extends Action
$migration->setAttribute('stage', 'processing');
$migration->setAttribute('status', 'processing');
- $this->updateMigrationDocument($migration, $projectDocument);
+ $this->updateMigrationDocument($migration, $projectDocument, $queueForRealtime);
$source = $this->processSource($migration);
$destination = $this->processDestination($migration, $tempAPIKey);
@@ -272,14 +271,14 @@ class Migrations extends Action
/** Start Transfer */
$migration->setAttribute('stage', 'migrating');
- $this->updateMigrationDocument($migration, $projectDocument);
+ $this->updateMigrationDocument($migration, $projectDocument, $queueForRealtime);
$transfer->run(
$migration->getAttribute('resources'),
- function () use ($migration, $transfer, $projectDocument) {
+ function () use ($migration, $transfer, $projectDocument, $queueForRealtime) {
$migration->setAttribute('resourceData', json_encode($transfer->getCache()));
$migration->setAttribute('statusCounters', json_encode($transfer->getStatusCounters()));
- $this->updateMigrationDocument($migration, $projectDocument);
+ $this->updateMigrationDocument($migration, $projectDocument, $queueForRealtime);
},
$migration->getAttribute('resourceId'),
$migration->getAttribute('resourceType')
@@ -316,7 +315,7 @@ class Migrations extends Action
}
$migration->setAttribute('errors', $errorMessages);
- $this->updateMigrationDocument($migration, $projectDocument);
+ $this->updateMigrationDocument($migration, $projectDocument, $queueForRealtime);
return;
}
@@ -357,7 +356,7 @@ class Migrations extends Action
$migration->setAttribute('errors', $errorMessages);
}
} finally {
- $this->updateMigrationDocument($migration, $projectDocument);
+ $this->updateMigrationDocument($migration, $projectDocument, $queueForRealtime);
if ($migration->getAttribute('status', '') === 'failed') {
Console::error('Migration('.$migration->getInternalId().':'.$migration->getId().') failed, Project('.$this->project->getInternalId().':'.$this->project->getId().')');
diff --git a/src/Appwrite/Platform/Workers/StatsResources.php b/src/Appwrite/Platform/Workers/StatsResources.php
index a6101522fb..3c0e772bd4 100644
--- a/src/Appwrite/Platform/Workers/StatsResources.php
+++ b/src/Appwrite/Platform/Workers/StatsResources.php
@@ -77,10 +77,13 @@ class StatsResources extends Action
// Reset documents for each job
$this->documents = [];
+ $startTime = microtime(true);
$this->countForProject($dbForPlatform, $getLogsDB, $getProjectDB, $project);
+ $endTime = microtime(true);
+ $executionTime = $endTime - $startTime;
+ Console::info('Project: ' . $project->getId() . '(' . $project->getInternalId() . ') aggregated in ' . $executionTime .' seconds');
}
-
protected function countForProject(Database $dbForPlatform, callable $getLogsDB, callable $getProjectDB, Document $project): void
{
Console::info('Begining count for: ' . $project->getId());
@@ -111,6 +114,13 @@ class StatsResources extends Action
$keys = $dbForPlatform->count('keys', [
Query::equal('projectInternalId', [$project->getInternalId()])
]);
+
+ $domains = $dbForPlatform->count('rules', [
+ Query::equal('projectInternalId', [$project->getInternalId()]),
+ Query::equal('owner', ['']),
+ ]);
+
+
$databases = $dbForProject->count('databases');
$buckets = $dbForProject->count('buckets');
$users = $dbForProject->count('users');
@@ -129,9 +139,20 @@ class StatsResources extends Action
]);
$teams = $dbForProject->count('teams');
$functions = $dbForProject->count('functions');
+
$messages = $dbForProject->count('messages');
$providers = $dbForProject->count('providers');
$topics = $dbForProject->count('topics');
+ $targets = $dbForProject->count('targets');
+ $emailTargets = $dbForProject->count('targets', [
+ Query::equal('providerType', [MESSAGE_TYPE_EMAIL])
+ ]);
+ $pushTargets = $dbForProject->count('targets', [
+ Query::equal('providerType', [MESSAGE_TYPE_PUSH])
+ ]);
+ $smsTargets = $dbForProject->count('targets', [
+ Query::equal('providerType', [MESSAGE_TYPE_SMS])
+ ]);
$metrics = [
METRIC_DATABASES => $databases,
@@ -148,6 +169,11 @@ class StatsResources extends Action
METRIC_PROVIDERS => $providers,
METRIC_TOPICS => $topics,
METRIC_KEYS => $keys,
+ METRIC_DOMAINS => $domains,
+ METRIC_TARGETS => $targets,
+ str_replace('{providerType}', MESSAGE_TYPE_EMAIL, METRIC_PROVIDER_TYPE_TARGETS) => $emailTargets,
+ str_replace('{providerType}', MESSAGE_TYPE_PUSH, METRIC_PROVIDER_TYPE_TARGETS) => $pushTargets,
+ str_replace('{providerType}', MESSAGE_TYPE_SMS, METRIC_PROVIDER_TYPE_TARGETS) => $smsTargets,
];
foreach ($metrics as $metric => $value) {
@@ -167,7 +193,7 @@ class StatsResources extends Action
}
try {
- $this->countForDatabase($dbForProject, $dbForLogs, $region);
+ $this->countForDatabase($dbForProject, $region);
} catch (Throwable $th) {
call_user_func_array($this->logError, [$th, "StatsResources", "count_for_database_{$project->getId()}"]);
}
@@ -227,42 +253,54 @@ class StatsResources extends Action
$this->createStatsDocuments($region, METRIC_FILES_IMAGES_TRANSFORMED, $totalImageTransformations);
}
- protected function countForDatabase(Database $dbForProject, Database $dbForLogs, string $region)
+ protected function countForDatabase(Database $dbForProject, string $region)
{
$totalCollections = 0;
$totalDocuments = 0;
- $this->foreachDocument($dbForProject, 'databases', [], function ($database) use ($dbForProject, $dbForLogs, $region, &$totalCollections, &$totalDocuments) {
+ $totalDatabaseStorage = 0;
+
+ $this->foreachDocument($dbForProject, 'databases', [], function ($database) use ($dbForProject, $region, &$totalCollections, &$totalDocuments, &$totalDatabaseStorage) {
$collections = $dbForProject->count('database_' . $database->getInternalId());
$metric = str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_COLLECTIONS);
$this->createStatsDocuments($region, $metric, $collections);
- $documents = $this->countForCollections($dbForProject, $dbForLogs, $database, $region);
+ [$documents, $storage] = $this->countForCollections($dbForProject, $database, $region);
+ $totalDatabaseStorage += $storage;
$totalDocuments += $documents;
$totalCollections += $collections;
});
$this->createStatsDocuments($region, METRIC_COLLECTIONS, $totalCollections);
$this->createStatsDocuments($region, METRIC_DOCUMENTS, $totalDocuments);
+ $this->createStatsDocuments($region, METRIC_DATABASES_STORAGE, $totalDatabaseStorage);
}
- protected function countForCollections(Database $dbForProject, Database $dbForLogs, Document $database, string $region): int
+ protected function countForCollections(Database $dbForProject, Document $database, string $region): array
{
$databaseDocuments = 0;
- $this->foreachDocument($dbForProject, 'database_' . $database->getInternalId(), [], function ($collection) use ($dbForProject, $dbForLogs, $database, $region, &$totalCollections, &$databaseDocuments) {
+ $databaseStorage = 0;
+ $this->foreachDocument($dbForProject, 'database_' . $database->getInternalId(), [], function ($collection) use ($dbForProject, $database, $region, &$databaseStorage, &$databaseDocuments) {
$documents = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
-
$metric = str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS);
$this->createStatsDocuments($region, $metric, $documents);
-
$databaseDocuments += $documents;
+
+ $collectionStorage = $dbForProject->getSizeOfCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
+ $metric = str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE);
+ $this->createStatsDocuments($region, $metric, $collectionStorage);
+ $databaseStorage += $collectionStorage;
+
});
$metric = str_replace(['{databaseInternalId}'], [$database->getInternalId()], METRIC_DATABASE_ID_DOCUMENTS);
$this->createStatsDocuments($region, $metric, $databaseDocuments);
- return $databaseDocuments;
+ $metric = str_replace(['{databaseInternalId}'], [$database->getInternalId()], METRIC_DATABASE_ID_STORAGE);
+ $this->createStatsDocuments($region, $metric, $databaseStorage);
+
+ return [$databaseDocuments, $databaseStorage];
}
protected function countForFunctions(Database $dbForProject, Database $dbForLogs, string $region)
diff --git a/src/Appwrite/Platform/Workers/StatsUsage.php b/src/Appwrite/Platform/Workers/StatsUsage.php
index c4d8b0e8d2..a755f723a0 100644
--- a/src/Appwrite/Platform/Workers/StatsUsage.php
+++ b/src/Appwrite/Platform/Workers/StatsUsage.php
@@ -17,13 +17,20 @@ class StatsUsage extends Action
private int $lastTriggeredTime = 0;
private int $keys = 0;
private const INFINITY_PERIOD = '_inf_';
- private const KEYS_THRESHOLD = 10000;
+ private const BATCH_SIZE_DEVELOPMENT = 1;
+ private const BATCH_SIZE_PRODUCTION = 10_000;
public static function getName(): string
{
return 'stats-usage';
}
+ private function getBatchSize(): int
+ {
+ return System::getEnv('_APP_ENV', 'development') === 'development'
+ ? self::BATCH_SIZE_DEVELOPMENT
+ : self::BATCH_SIZE_PRODUCTION;
+ }
/**
* @throws Exception
*/
@@ -86,7 +93,7 @@ class StatsUsage 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 ||
+ $this->keys >= $this->getBatchSize() ||
(time() - $this->lastTriggeredTime > $aggregationInterval && $this->keys > 0)
) {
Console::warning('[' . DateTime::now() . '] Aggregated ' . $this->keys . ' keys');
diff --git a/src/Appwrite/Platform/Workers/StatsUsageDump.php b/src/Appwrite/Platform/Workers/StatsUsageDump.php
index 5d7240f4b5..119a9e7288 100644
--- a/src/Appwrite/Platform/Workers/StatsUsageDump.php
+++ b/src/Appwrite/Platform/Workers/StatsUsageDump.php
@@ -48,6 +48,7 @@ class StatsUsageDump extends Action
METRIC_BUILDS => true,
METRIC_COLLECTIONS => true,
METRIC_DOCUMENTS => true,
+ METRIC_DATABASES_STORAGE => true,
];
/**
@@ -63,6 +64,7 @@ class StatsUsageDump extends Action
'.deployments.storage',
'.builds',
'.builds.storage',
+ '.databases.storage'
];
/**
@@ -117,7 +119,7 @@ class StatsUsageDump extends Action
$project = new Document($stats['project'] ?? []);
$numberOfKeys = !empty($stats['keys']) ? count($stats['keys']) : 0;
- $receivedAt = $stats['receivedAt'] ?? 'NONE';
+ $receivedAt = $stats['receivedAt'] ?? null;
if ($numberOfKeys === 0) {
continue;
}
@@ -134,7 +136,7 @@ class StatsUsageDump extends Action
if (str_contains($key, METRIC_DATABASES_STORAGE)) {
try {
- $this->handleDatabaseStorage($key, $dbForProject, $project);
+ $this->handleDatabaseStorage($key, $dbForProject, $project, $receivedAt);
} catch (\Exception $e) {
console::error('[' . DateTime::now() . '] failed to calculate database storage for key [' . $key . '] ' . $e->getMessage());
}
@@ -142,7 +144,11 @@ class StatsUsageDump extends Action
}
foreach ($this->periods as $period => $format) {
- $time = 'inf' === $period ? null : date($format, time());
+ $time = null;
+
+ if ($period !== 'inf') {
+ $time = !empty($receivedAt) ? (new \DateTime($receivedAt))->format($format) : date($format, time());
+ }
$id = \md5("{$time}_{$period}_{$key}");
$document = new Document([
@@ -171,12 +177,12 @@ class StatsUsageDump extends Action
}
}
- private function handleDatabaseStorage(string $key, Database $dbForProject, Document $project): void
+ private function handleDatabaseStorage(string $key, Database $dbForProject, Document $project, string $receivedAt): void
{
$data = explode('.', $key);
$start = microtime(true);
- $updateMetric = function (Database $dbForProject, Document $project, int $value, string $key, string $period, string|null $time) {
+ $updateMetric = function (Database $dbForProject, Document $project, int $value, string $key, string $period, string|null $time) use ($receivedAt) {
$id = \md5("{$time}_{$period}_{$key}");
$document = new Document([
@@ -197,7 +203,11 @@ class StatsUsageDump extends Action
};
foreach ($this->periods as $period => $format) {
- $time = 'inf' === $period ? null : date($format, time());
+ $time = null;
+
+ if ($period !== 'inf') {
+ $time = !empty($receivedAt) ? (new \DateTime($receivedAt))->format($format) : date($format, time());
+ }
$id = \md5("{$time}_{$period}_{$key}");
$value = 0;
@@ -320,7 +330,7 @@ class StatsUsageDump extends Action
protected function writeToLogsDB(Document $project, Document $document): void
{
- if (!System::getEnv('_APP_STATS_USAGE_DUAL_WRITING', false)) {
+ if (System::getEnv('_APP_STATS_USAGE_DUAL_WRITING', 'disabled') === 'disabled') {
Console::log('Dual Writing is disabled. Skipping...');
return;
}
diff --git a/src/Appwrite/Platform/Workers/Usage.php b/src/Appwrite/Platform/Workers/Usage.php
deleted file mode 100644
index 3687eeab67..0000000000
--- a/src/Appwrite/Platform/Workers/Usage.php
+++ /dev/null
@@ -1,292 +0,0 @@
-desc('Usage worker')
- ->inject('message')
- ->inject('project')
- ->inject('getProjectDB')
- ->inject('queueForUsageDump')
- ->callback(function (Message $message, Document $project, callable $getProjectDB, UsageDump $queueForUsageDump) {
- $this->action($message, $project, $getProjectDB, $queueForUsageDump);
- });
-
- $this->aggregationInterval = (int) System::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', '20');
- $this->lastTriggeredTime = time();
- }
-
- /**
- * @param Message $message
- * @param Document $project
- * @param callable $getProjectDB
- * @param UsageDump $queueForUsageDump
- * @return void
- * @throws \Utopia\Database\Exception
- * @throws Exception
- */
- public function action(Message $message, Document $project, callable $getProjectDB, UsageDump $queueForUsageDump): void
- {
- $payload = $message->getPayload() ?? [];
- if (empty($payload)) {
- throw new Exception('Missing payload');
- }
-
-
- if (empty($project->getAttribute('database'))) {
- var_dump($payload);
- return;
- }
-
- $projectId = $project->getInternalId();
- foreach ($payload['reduce'] ?? [] as $document) {
- if (empty($document)) {
- continue;
- }
-
- $this->reduce(
- project: $project,
- document: new Document($document),
- metrics: $payload['metrics'],
- getProjectDB: $getProjectDB
- );
- }
-
-
- $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++;
- if (!isset($this->stats[$projectId]['keys'][$metric['key']])) {
- $this->stats[$projectId]['keys'][$metric['key']] = $metric['value'];
- continue;
- }
-
- $this->stats[$projectId]['keys'][$metric['key']] += $metric['value'];
- }
-
- // 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 > $this->aggregationInterval && $this->keys > 0)
- ) {
- Console::warning('[' . DateTime::now() . '] Aggregated ' . $this->keys . ' keys');
-
- $queueForUsageDump
- ->setStats($this->stats)
- ->trigger();
-
- $this->stats = [];
- $this->keys = 0;
- $this->lastTriggeredTime = time();
- }
- }
-
- /**
- * On Documents that tied by relations like functions>deployments>build || documents>collection>database || buckets>files.
- * When we remove a parent document we need to deduct his children aggregation from the project scope.
- * @param Document $project
- * @param Document $document
- * @param array $metrics
- * @param callable $getProjectDB
- * @return void
- */
- private function reduce(Document $project, Document $document, array &$metrics, callable $getProjectDB): void
- {
- $dbForProject = $getProjectDB($project);
-
- try {
- switch (true) {
- case $document->getCollection() === 'users': // users
- $sessions = count($document->getAttribute(METRIC_SESSIONS, 0));
- if (!empty($sessions)) {
- $metrics[] = [
- 'key' => METRIC_SESSIONS,
- 'value' => ($sessions * -1),
- ];
- }
- break;
- case $document->getCollection() === 'databases': // databases
- $collections = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{databaseInternalId}', $document->getInternalId(), METRIC_DATABASE_ID_COLLECTIONS)));
- $documents = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{databaseInternalId}', $document->getInternalId(), METRIC_DATABASE_ID_DOCUMENTS)));
- if (!empty($collections['value'])) {
- $metrics[] = [
- 'key' => METRIC_COLLECTIONS,
- 'value' => ($collections['value'] * -1),
- ];
- }
-
- if (!empty($documents['value'])) {
- $metrics[] = [
- 'key' => METRIC_DOCUMENTS,
- 'value' => ($documents['value'] * -1),
- ];
- }
- break;
- case str_starts_with($document->getCollection(), 'database_') && !str_contains($document->getCollection(), 'collection'): //collections
- $parts = explode('_', $document->getCollection());
- $databaseInternalId = $parts[1] ?? 0;
- $documents = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $document->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS)));
-
- if (!empty($documents['value'])) {
- $metrics[] = [
- 'key' => METRIC_DOCUMENTS,
- 'value' => ($documents['value'] * -1),
- ];
- $metrics[] = [
- 'key' => str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_DOCUMENTS),
- 'value' => ($documents['value'] * -1),
- ];
- }
- break;
-
- case $document->getCollection() === 'buckets':
- $files = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{bucketInternalId}', $document->getInternalId(), METRIC_BUCKET_ID_FILES)));
- $storage = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{bucketInternalId}', $document->getInternalId(), METRIC_BUCKET_ID_FILES_STORAGE)));
-
- if (!empty($files['value'])) {
- $metrics[] = [
- 'key' => METRIC_FILES,
- 'value' => ($files['value'] * -1),
- ];
- }
-
- if (!empty($storage['value'])) {
- $metrics[] = [
- 'key' => METRIC_FILES_STORAGE,
- 'value' => ($storage['value'] * -1),
- ];
- }
- break;
-
- case $document->getCollection() === 'functions':
- $deployments = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace(['{resourceType}', '{resourceInternalId}'], ['functions', $document->getInternalId()], METRIC_FUNCTION_ID_DEPLOYMENTS)));
- $deploymentsStorage = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace(['{resourceType}', '{resourceInternalId}'], ['functions', $document->getInternalId()], METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE)));
- $builds = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{functionInternalId}', $document->getInternalId(), METRIC_FUNCTION_ID_BUILDS)));
- $buildsSuccess = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{functionInternalId}', $document->getInternalId(), METRIC_FUNCTION_ID_BUILDS_SUCCESS)));
- $buildsFailed = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{functionInternalId}', $document->getInternalId(), METRIC_FUNCTION_ID_BUILDS_FAILED)));
- $buildsStorage = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{functionInternalId}', $document->getInternalId(), METRIC_FUNCTION_ID_BUILDS_STORAGE)));
- $buildsCompute = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{functionInternalId}', $document->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE)));
- $buildsComputeSuccess = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{functionInternalId}', $document->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE_SUCCESS)));
- $buildsComputeFailed = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{functionInternalId}', $document->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE_FAILED)));
- $executions = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{functionInternalId}', $document->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS)));
- $executionsCompute = $dbForProject->getDocument('stats', md5(self::INFINITY_PERIOD . str_replace('{functionInternalId}', $document->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE)));
-
- if (!empty($deployments['value'])) {
- $metrics[] = [
- 'key' => METRIC_DEPLOYMENTS,
- 'value' => ($deployments['value'] * -1),
- ];
- }
-
- if (!empty($deploymentsStorage['value'])) {
- $metrics[] = [
- 'key' => METRIC_DEPLOYMENTS_STORAGE,
- 'value' => ($deploymentsStorage['value'] * -1),
- ];
- }
-
- if (!empty($builds['value'])) {
- $metrics[] = [
- 'key' => METRIC_BUILDS,
- 'value' => ($builds['value'] * -1),
- ];
- }
-
- if (!empty($buildsSuccess['value'])) {
- $metrics[] = [
- 'key' => METRIC_BUILDS_SUCCESS,
- 'value' => ($buildsSuccess['value'] * -1),
- ];
- }
-
- if (!empty($buildsFailed['value'])) {
- $metrics[] = [
- 'key' => METRIC_BUILDS_FAILED,
- 'value' => ($buildsFailed['value'] * -1),
- ];
- }
-
- if (!empty($buildsStorage['value'])) {
- $metrics[] = [
- 'key' => METRIC_BUILDS_STORAGE,
- 'value' => ($buildsStorage['value'] * -1),
- ];
- }
-
- if (!empty($buildsCompute['value'])) {
- $metrics[] = [
- 'key' => METRIC_BUILDS_COMPUTE,
- 'value' => ($buildsCompute['value'] * -1),
- ];
- }
-
- if (!empty($buildsComputeSuccess['value'])) {
- $metrics[] = [
- 'key' => METRIC_BUILDS_COMPUTE_SUCCESS,
- 'value' => ($buildsComputeSuccess['value'] * -1),
- ];
- }
-
- if (!empty($buildsComputeFailed['value'])) {
- $metrics[] = [
- 'key' => METRIC_BUILDS_COMPUTE_FAILED,
- 'value' => ($buildsComputeFailed['value'] * -1),
- ];
- }
-
- if (!empty($executions['value'])) {
- $metrics[] = [
- 'key' => METRIC_EXECUTIONS,
- 'value' => ($executions['value'] * -1),
- ];
- }
-
- if (!empty($executionsCompute['value'])) {
- $metrics[] = [
- 'key' => METRIC_EXECUTIONS_COMPUTE,
- 'value' => ($executionsCompute['value'] * -1),
- ];
- }
- break;
- default:
- break;
- }
- } catch (\Throwable $e) {
- console::error("[reducer] " . " {DateTime::now()} " . " {$project->getInternalId()} " . " {$e->getMessage()}");
- }
- }
-}
diff --git a/src/Appwrite/Platform/Workers/UsageDump.php b/src/Appwrite/Platform/Workers/UsageDump.php
deleted file mode 100644
index 2f1d13f29a..0000000000
--- a/src/Appwrite/Platform/Workers/UsageDump.php
+++ /dev/null
@@ -1,286 +0,0 @@
- 'Y-m-d H:00',
- '1d' => 'Y-m-d 00:00',
- 'inf' => '0000-00-00 00:00'
- ];
-
- public static function getName(): string
- {
- return 'usage-dump';
- }
-
- /**
- * @throws \Exception
- */
- public function __construct()
- {
- $this
- ->inject('message')
- ->inject('getProjectDB')
- ->callback(function (Message $message, callable $getProjectDB) {
- $this->action($message, $getProjectDB);
- });
- }
-
- /**
- * @param Message $message
- * @param callable $getProjectDB
- * @return void
- * @throws Exception
- * @throws \Utopia\Database\Exception
- */
- public function action(Message $message, callable $getProjectDB): void
- {
- $payload = $message->getPayload() ?? [];
- if (empty($payload)) {
- throw new Exception('Missing payload');
- }
-
-
- foreach ($payload['stats'] ?? [] as $stats) {
- $project = new Document($stats['project'] ?? []);
-
- /**
- * End temp bug fallback
- */
- $numberOfKeys = !empty($stats['keys']) ? count($stats['keys']) : 0;
- $receivedAt = $stats['receivedAt'] ?? 'NONE';
- if ($numberOfKeys === 0) {
- continue;
- }
-
- console::log('['.DateTime::now().'] Id: '.$project->getId(). ' InternalId: '.$project->getInternalId(). ' Db: '.$project->getAttribute('database').' ReceivedAt: '.$receivedAt. ' Keys: '.$numberOfKeys);
-
- try {
- $dbForProject = $getProjectDB($project);
- foreach ($stats['keys'] ?? [] as $key => $value) {
- if ($value == 0) {
- continue;
- }
-
- if (str_contains($key, METRIC_DATABASES_STORAGE)) {
- try {
- $this->handleDatabaseStorage($key, $dbForProject);
- } catch (\Exception $e) {
- console::error('[' . DateTime::now() . '] failed to calculate database storage for key [' . $key . '] ' . $e->getMessage());
- }
- continue;
- }
-
- foreach ($this->periods as $period => $format) {
- $time = 'inf' === $period ? null : date($format, time());
- $id = \md5("{$time}_{$period}_{$key}");
-
- try {
- $dbForProject->createDocument('stats', new Document([
- '$id' => $id,
- 'period' => $period,
- 'time' => $time,
- 'metric' => $key,
- 'value' => $value,
- 'region' => System::getEnv('_APP_REGION', 'default'),
- ]));
- } catch (Duplicate $th) {
- if ($value < 0) {
- $dbForProject->decreaseDocumentAttribute(
- 'stats',
- $id,
- 'value',
- abs($value)
- );
- } else {
- $dbForProject->increaseDocumentAttribute(
- 'stats',
- $id,
- 'value',
- $value
- );
- }
- }
- }
- }
- } catch (\Exception $e) {
- console::error('[' . DateTime::now() . '] project [' . $project->getInternalId() . '] database [' . $project['database'] . '] ' . ' ' . $e->getMessage());
- }
- }
- }
-
- private function handleDatabaseStorage(string $key, Database $dbForProject): void
- {
- $data = explode('.', $key);
- $start = microtime(true);
-
- $updateMetric = function (Database $dbForProject, int $value, string $key, string $period, string|null $time) {
- $id = \md5("{$time}_{$period}_{$key}");
-
- try {
- $dbForProject->createDocument('stats', new Document([
- '$id' => $id,
- 'period' => $period,
- 'time' => $time,
- 'metric' => $key,
- 'value' => $value,
- 'region' => System::getEnv('_APP_REGION', 'default'),
- ]));
- } catch (Duplicate $th) {
- if ($value < 0) {
- $dbForProject->decreaseDocumentAttribute(
- 'stats',
- $id,
- 'value',
- abs($value)
- );
- } else {
- $dbForProject->increaseDocumentAttribute(
- 'stats',
- $id,
- 'value',
- $value
- );
- }
- }
- };
-
- foreach ($this->periods as $period => $format) {
- $time = 'inf' === $period ? null : date($format, time());
- $id = \md5("{$time}_{$period}_{$key}");
-
- $value = 0;
- $previousValue = 0;
- try {
- $previousValue = ($dbForProject->getDocument('stats', $id))->getAttribute('value', 0);
- } catch (\Exception $e) {
- // No previous value
- }
-
- switch (count($data)) {
- // Collection Level
- case METRIC_COLLECTION_LEVEL_STORAGE:
- Console::log('[' . DateTime::now() . '] Collection Level Storage Calculation [' . $key . ']');
- $databaseInternalId = $data[0];
- $collectionInternalId = $data[1];
-
- try {
- $value = $dbForProject->getSizeOfCollection('database_' . $databaseInternalId . '_collection_' . $collectionInternalId);
- } catch (\Exception $e) {
- // Collection not found
- if ($e->getMessage() !== 'Collection not found') {
- throw $e;
- }
- }
-
- // Compare with previous value
- $diff = $value - $previousValue;
-
- if ($diff === 0) {
- break;
- }
-
- // Update Collection
- $updateMetric($dbForProject, $diff, $key, $period, $time);
-
- // Update Database
- $databaseKey = str_replace(['{databaseInternalId}'], [$data[0]], METRIC_DATABASE_ID_STORAGE);
- $updateMetric($dbForProject, $diff, $databaseKey, $period, $time);
-
- // Update Project
- $projectKey = METRIC_DATABASES_STORAGE;
- $updateMetric($dbForProject, $diff, $projectKey, $period, $time);
- break;
- // Database Level
- case METRIC_DATABASE_LEVEL_STORAGE:
- Console::log('[' . DateTime::now() . '] Database Level Storage Calculation [' . $key . ']');
- $databaseInternalId = $data[0];
-
- $collections = [];
- try {
- $collections = $dbForProject->find('database_' . $databaseInternalId);
- } catch (\Exception $e) {
- // Database not found
- if ($e->getMessage() !== 'Collection not found') {
- throw $e;
- }
- }
-
- foreach ($collections as $collection) {
- try {
- $value += $dbForProject->getSizeOfCollection('database_' . $databaseInternalId . '_collection_' . $collection->getInternalId());
- } catch (\Exception $e) {
- // Collection not found
- if ($e->getMessage() !== 'Collection not found') {
- throw $e;
- }
- }
- }
-
- $diff = $value - $previousValue;
-
- if ($diff === 0) {
- break;
- }
-
- // Update Database
- $databaseKey = str_replace(['{databaseInternalId}'], [$data[0]], METRIC_DATABASE_ID_STORAGE);
- $updateMetric($dbForProject, $diff, $databaseKey, $period, $time);
-
- // Update Project
- $projectKey = METRIC_DATABASES_STORAGE;
- $updateMetric($dbForProject, $diff, $projectKey, $period, $time);
- break;
- // Project Level
- case METRIC_PROJECT_LEVEL_STORAGE:
- Console::log('[' . DateTime::now() . '] Project Level Storage Calculation [' . $key . ']');
- // Get all project databases
- $databases = $dbForProject->find('database');
-
- // Recalculate all databases
- foreach ($databases as $database) {
- $collections = $dbForProject->find('database_' . $database->getInternalId());
-
- foreach ($collections as $collection) {
- try {
- $value += $dbForProject->getSizeOfCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
- } catch (\Exception $e) {
- // Collection not found
- if ($e->getMessage() !== 'Collection not found') {
- throw $e;
- }
- }
- }
- }
-
- $diff = $value - $previousValue;
-
- // Update Project
- $projectKey = METRIC_DATABASES_STORAGE;
- $updateMetric($dbForProject, $diff, $projectKey, $period, $time);
- break;
- }
- }
-
- $end = microtime(true);
-
- console::log('[' . DateTime::now() . '] DB Storage Calculation [' . $key . '] took ' . (($end - $start) * 1000) . ' milliseconds');
- }
-}
diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php
index af5d59ddfd..e8eafba5a0 100644
--- a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php
+++ b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php
@@ -24,7 +24,15 @@ class Base extends Queries
public function __construct(string $collection, array $allowedAttributes)
{
$config = Config::getParam('collections', []);
- $collections = array_merge($config['projects'], $config['buckets'], $config['databases'], $config['console']);
+
+ $collections = array_merge(
+ $config['projects'],
+ $config['buckets'],
+ $config['databases'],
+ $config['console'],
+ $config['logs']
+ );
+
$collection = $collections[$collection];
// array for constant lookup time
$allowedAttributesLookup = [];
@@ -35,6 +43,7 @@ class Base extends Queries
$attributes = [];
foreach ($collection['attributes'] as $attribute) {
$key = $attribute['$id'];
+
if (!isset($allowedAttributesLookup[$key])) {
continue;
}
@@ -62,12 +71,18 @@ class Base extends Queries
'array' => false,
]);
+ $internalId = new Document([
+ 'key' => '$internalId',
+ 'type' => Database::VAR_STRING,
+ 'array' => false,
+ ]);
+
$validators = [
new Limit(),
new Offset(),
new Cursor(),
new Filter($attributes, APP_DATABASE_QUERY_MAX_VALUES),
- new Order($attributes),
+ new Order([...$attributes, $internalId]),
];
parent::__construct($validators);
diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Memberships.php b/src/Appwrite/Utopia/Database/Validator/Queries/Memberships.php
index 5ff0098662..cef562ba2c 100644
--- a/src/Appwrite/Utopia/Database/Validator/Queries/Memberships.php
+++ b/src/Appwrite/Utopia/Database/Validator/Queries/Memberships.php
@@ -9,12 +9,12 @@ class Memberships extends Base
'teamId',
'invited',
'joined',
- 'confirm'
+ 'confirm',
+ 'roles',
];
/**
* Expression constructor
- *
*/
public function __construct()
{
diff --git a/src/Appwrite/Utopia/Response/Model/UsageBuckets.php b/src/Appwrite/Utopia/Response/Model/UsageBuckets.php
index 2f528ac9d1..ee624a29ad 100644
--- a/src/Appwrite/Utopia/Response/Model/UsageBuckets.php
+++ b/src/Appwrite/Utopia/Response/Model/UsageBuckets.php
@@ -42,6 +42,19 @@ class UsageBuckets extends Model
'example' => [],
'array' => true
])
+ ->addRule('imageTransformations', [
+ 'type' => Response::MODEL_METRIC,
+ 'description' => 'Aggregated number of files transformations per period.',
+ 'default' => [],
+ 'example' => [],
+ 'array' => true
+ ])
+ ->addRule('imageTransformationsTotal', [
+ 'type' => self::TYPE_INTEGER,
+ 'description' => 'Total aggregated number of files transformations.',
+ 'default' => 0,
+ 'example' => 0,
+ ])
;
}
diff --git a/src/Appwrite/Utopia/Response/Model/UsageProject.php b/src/Appwrite/Utopia/Response/Model/UsageProject.php
index 1006276b56..395b19b7cd 100644
--- a/src/Appwrite/Utopia/Response/Model/UsageProject.php
+++ b/src/Appwrite/Utopia/Response/Model/UsageProject.php
@@ -197,6 +197,19 @@ class UsageProject extends Model
'example' => [],
'array' => true
])
+ ->addRule('imageTransformations', [
+ 'type' => Response::MODEL_METRIC,
+ 'description' => 'An array of aggregated number of image transformations.',
+ 'default' => [],
+ 'example' => [],
+ 'array' => true
+ ])
+ ->addRule('imageTransformationsTotal', [
+ 'type' => self::TYPE_INTEGER,
+ 'description' => 'Total aggregated number of image transformations.',
+ 'default' => 0,
+ 'example' => 0,
+ ])
;
}
diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php
index c230cfb664..6bc2fe7aab 100644
--- a/src/Executor/Executor.php
+++ b/src/Executor/Executor.php
@@ -21,17 +21,19 @@ class Executor
private bool $selfSigned = false;
- private string $endpoint;
+ /**
+ * @var callable(string, string): string $endpoint
+ */
+ private $endpointSelector;
protected array $headers;
- public function __construct(string $endpoint)
+ /**
+ * @param callable(string, string): string $endpointSelector
+ */
+ public function __construct(callable $endpointSelector)
{
- if (!filter_var($endpoint, FILTER_VALIDATE_URL)) {
- throw new Exception('Unsupported endpoint');
- }
-
- $this->endpoint = $endpoint;
+ $this->endpointSelector = $endpointSelector;
$this->headers = [
'content-type' => 'application/json',
'authorization' => 'Bearer ' . System::getEnv('_APP_EXECUTOR_SECRET', ''),
@@ -92,7 +94,8 @@ class Executor
'timeout' => $timeout,
];
- $response = $this->call(self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout);
+ $endpoint = $this->selectEndpoint($projectId, $deploymentId);
+ $response = $this->call($endpoint, self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout);
$status = $response['headers']['status-code'];
if ($status >= 400) {
@@ -123,7 +126,8 @@ class Executor
'timeout' => $timeout
];
- $this->call(self::METHOD_GET, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout, $callback);
+ $endpoint = $this->selectEndpoint($projectId, $deploymentId);
+ $this->call($endpoint, self::METHOD_GET, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout, $callback);
}
/**
@@ -139,7 +143,8 @@ class Executor
$runtimeId = "$projectId-$deploymentId";
$route = "/runtimes/$runtimeId";
- $response = $this->call(self::METHOD_DELETE, $route, [
+ $endpoint = $this->selectEndpoint($projectId, $deploymentId);
+ $response = $this->call($endpoint, self::METHOD_DELETE, $route, [
'x-opr-addressing-method' => 'broadcast'
], [], true, 30);
@@ -227,7 +232,8 @@ class Executor
$requestTimeout = $timeout + 15;
}
- $response = $this->call(self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId, 'content-type' => 'multipart/form-data', 'accept' => 'multipart/form-data' ], $params, true, $requestTimeout);
+ $endpoint = $this->selectEndpoint($projectId, $deploymentId);
+ $response = $this->call($endpoint, self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId, 'content-type' => 'multipart/form-data', 'accept' => 'multipart/form-data' ], $params, true, $requestTimeout);
$status = $response['headers']['status-code'];
if ($status >= 400) {
@@ -235,7 +241,11 @@ class Executor
throw new \Exception($message, $status);
}
- $response['body']['headers'] = \json_decode($response['body']['headers'] ?? '{}', true);
+ $headers = $response['body']['headers'] ?? [];
+ if (is_string($headers)) {
+ $headers = \json_decode($headers, true);
+ }
+ $response['body']['headers'] = $headers;
$response['body']['statusCode'] = \intval($response['body']['statusCode'] ?? 500);
$response['body']['duration'] = \floatval($response['body']['duration'] ?? 0);
$response['body']['startTime'] = \floatval($response['body']['startTime'] ?? \microtime(true));
@@ -256,10 +266,10 @@ class Executor
* @return array|string
* @throws Exception
*/
- public function call(string $method, string $path = '', array $headers = [], array $params = [], bool $decode = true, int $timeout = 15, callable $callback = null)
+ private function call(string $endpoint, string $method, string $path = '', array $headers = [], array $params = [], bool $decode = true, int $timeout = 15, callable $callback = null)
{
$headers = array_merge($this->headers, $headers);
- $ch = curl_init($this->endpoint . $path . (($method == self::METHOD_GET && !empty($params)) ? '?' . http_build_query($params) : ''));
+ $ch = curl_init($endpoint . $path . (($method == self::METHOD_GET && !empty($params)) ? '?' . http_build_query($params) : ''));
$responseHeaders = [];
$responseStatus = -1;
$responseType = '';
@@ -422,4 +432,9 @@ class Executor
return $output;
}
+
+ private function selectEndpoint(string $projectId, string $deploymentId): string
+ {
+ return call_user_func($this->endpointSelector, $projectId, $deploymentId);
+ }
}
diff --git a/tests/e2e/General/UsageTest.php b/tests/e2e/General/UsageTest.php
index c0d0c80eb1..e614e2e185 100644
--- a/tests/e2e/General/UsageTest.php
+++ b/tests/e2e/General/UsageTest.php
@@ -23,7 +23,7 @@ class UsageTest extends Scope
use SideServer;
use FunctionsBase;
- private const WAIT = 35;
+ private const WAIT = 5;
private const CREATE = 20;
protected string $projectId;
@@ -134,8 +134,6 @@ class UsageTest extends Scope
#[Retry(count: 1)]
public function testUsersStats(array $data): array
{
- sleep(self::WAIT);
-
$requestsTotal = $data['requestsTotal'];
$response = $this->client->call(
@@ -150,7 +148,7 @@ class UsageTest extends Scope
);
$this->assertEquals(200, $response['headers']['status-code']);
- $this->assertEquals(29, count($response['body']));
+ $this->assertEquals(31, count($response['body']));
$this->validateDates($response['body']['network']);
$this->validateDates($response['body']['requests']);
$this->validateDates($response['body']['users']);
@@ -309,7 +307,7 @@ class UsageTest extends Scope
/**
* @depends testPrepareStorageStats
*/
- #[Retry(count: 1)]
+ #[Retry(count: 10)]
public function testStorageStats(array $data): array
{
$bucketId = $data['bucketId'];
@@ -318,8 +316,6 @@ class UsageTest extends Scope
$storageTotal = $data['storageTotal'];
$filesTotal = $data['filesTotal'];
- sleep(self::WAIT);
-
$response = $this->client->call(
Client::METHOD_GET,
'/project/usage',
@@ -331,7 +327,7 @@ class UsageTest extends Scope
]
);
- $this->assertEquals(29, count($response['body']));
+ $this->assertEquals(31, count($response['body']));
$this->assertEquals(1, count($response['body']['requests']));
$this->assertEquals($requestsTotal, $response['body']['requests'][array_key_last($response['body']['requests'])]['value']);
$this->validateDates($response['body']['requests']);
@@ -474,10 +470,10 @@ class UsageTest extends Scope
$this->assertEquals('name', $response['body']['key']);
- $requestsTotal += 1;
-
sleep(self::WAIT);
+ $requestsTotal += 1;
+
for ($i = 0; $i < self::CREATE; $i++) {
$name = uniqid() . ' collection';
@@ -552,7 +548,7 @@ class UsageTest extends Scope
]
);
- $this->assertEquals(29, count($response['body']));
+ $this->assertEquals(31, count($response['body']));
$this->assertEquals(1, count($response['body']['requests']));
$this->assertEquals(1, count($response['body']['network']));
$this->assertEquals($requestsTotal, $response['body']['requests'][array_key_last($response['body']['requests'])]['value']);
@@ -709,8 +705,6 @@ class UsageTest extends Scope
// $this->assertEquals(201, $response['headers']['status-code']);
// }
- // sleep(self::WAIT);
-
// for ($i = 0; $i < 3; $i++) {
// try {
// $newProjectMetrics = $this->client->call(
@@ -752,7 +746,6 @@ class UsageTest extends Scope
// if ($i === 2) {
// throw $e;
// }
- // sleep(self::WAIT);
// continue;
// }
// }
@@ -792,8 +785,6 @@ class UsageTest extends Scope
// $this->assertEquals(204, $response['headers']['status-code']);
// }
- // sleep(self::WAIT);
-
// for ($i = 0; $i < 3; $i++) {
// try {
// $newProjectMetrics = $this->client->call(
@@ -835,7 +826,6 @@ class UsageTest extends Scope
// if ($i === 2) {
// throw $e;
// }
- // sleep(self::WAIT);
// continue;
// }
// }
@@ -1027,8 +1017,6 @@ class UsageTest extends Scope
$executionTime = $data['executionTime'];
$executions = $data['executions'];
- sleep(self::WAIT);
-
$response = $this->client->call(
Client::METHOD_GET,
'/functions/' . $functionId . '/usage?range=30d',
@@ -1152,52 +1140,40 @@ class UsageTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
- sleep(self::WAIT + 20);
$tries = 0;
- while (true) {
- try {
- // Compare new values with old values
- $response = $this->client->call(
- Client::METHOD_GET,
- '/functions/' . $functionId . '/usage?range=30d',
- $this->getConsoleHeaders()
- );
+ $this->assertEventually(function () use ($functionId, $functionsMetrics, $projectMetrics) {
+ // Compare new values with old values
+ $response = $this->client->call(
+ Client::METHOD_GET,
+ '/functions/' . $functionId . '/usage?range=30d',
+ $this->getConsoleHeaders()
+ );
- $this->assertEquals(200, $response['headers']['status-code']);
- $this->assertEquals(19, count($response['body']));
- $this->assertEquals('30d', $response['body']['range']);
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertEquals(19, count($response['body']));
+ $this->assertEquals('30d', $response['body']['range']);
- // Check if the new values are greater than the old values
- $this->assertEquals($functionsMetrics['executionsTotal'] + 1, $response['body']['executionsTotal']);
- $this->assertGreaterThan($functionsMetrics['executionsTimeTotal'], $response['body']['executionsTimeTotal']);
- $this->assertGreaterThan($functionsMetrics['executionsMbSecondsTotal'], $response['body']['executionsMbSecondsTotal']);
+ // Check if the new values are greater than the old values
+ $this->assertEquals($functionsMetrics['executionsTotal'] + 1, $response['body']['executionsTotal']);
+ $this->assertGreaterThan($functionsMetrics['executionsTimeTotal'], $response['body']['executionsTimeTotal']);
+ $this->assertGreaterThan($functionsMetrics['executionsMbSecondsTotal'], $response['body']['executionsMbSecondsTotal']);
- $response = $this->client->call(
- Client::METHOD_GET,
- '/project/usage',
- $this->getConsoleHeaders(),
- [
- 'period' => '1h',
- 'startDate' => self::getToday(),
- 'endDate' => self::getTomorrow(),
- ]
- );
+ $response = $this->client->call(
+ Client::METHOD_GET,
+ '/project/usage',
+ $this->getConsoleHeaders(),
+ [
+ 'period' => '1h',
+ 'startDate' => self::getToday(),
+ 'endDate' => self::getTomorrow(),
+ ]
+ );
- $this->assertEquals(200, $response['headers']['status-code']);
- $this->assertEquals($projectMetrics['executionsTotal'] + 1, $response['body']['executionsTotal']);
- $this->assertGreaterThan($projectMetrics['executionsMbSecondsTotal'], $response['body']['executionsMbSecondsTotal']);
-
- break;
- } catch (ExpectationFailedException $th) {
- if ($tries >= 5) {
- throw $th;
- } else {
- $tries++;
- sleep(5);
- }
- }
- }
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertEquals($projectMetrics['executionsTotal'] + 1, $response['body']['executionsTotal']);
+ $this->assertGreaterThan($projectMetrics['executionsMbSecondsTotal'], $response['body']['executionsMbSecondsTotal']);
+ });
}
public function tearDown(): void
diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php
index 2b4073a1d2..aee6ae973a 100644
--- a/tests/e2e/Scopes/ProjectCustom.php
+++ b/tests/e2e/Scopes/ProjectCustom.php
@@ -4,6 +4,7 @@ namespace Tests\E2E\Scopes;
use Tests\E2E\Client;
use Utopia\Database\Helpers\ID;
+use Utopia\System\System;
trait ProjectCustom
{
@@ -42,7 +43,7 @@ trait ProjectCustom
'x-appwrite-project' => 'console',
], [
'projectId' => ID::unique(),
- 'region' => 'default',
+ 'region' => System::getEnv('_APP_REGION', 'default'),
'name' => 'Demo Project',
'teamId' => $team['body']['$id'],
'description' => 'Demo Project Description',
diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php
index 788f949fb3..daa5bcbff8 100644
--- a/tests/e2e/Services/Account/AccountCustomClientTest.php
+++ b/tests/e2e/Services/Account/AccountCustomClientTest.php
@@ -277,6 +277,7 @@ class AccountCustomClientTest extends Scope
{
sleep(5);
$session = $data['session'] ?? '';
+
/**
* Test for SUCCESS
*/
@@ -2418,6 +2419,33 @@ class AccountCustomClientTest extends Scope
$message = $smsRequest['data']['message'];
$token = substr($message, 0, 6);
+ /**
+ * Test for FAILURE
+ */
+
+ // disable phone sessions
+ $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/auth/phone', array_merge([
+ 'origin' => 'http://localhost',
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => 'console',
+ 'cookie' => 'a_session_console=' . $this->getRoot()['session'],
+ ]), [
+ 'status' => false,
+ ]);
+
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertEquals(false, $response['body']['authPhone']);
+
+ $response = $this->client->call(Client::METHOD_POST, '/account/verification/phone', array_merge([
+ 'origin' => 'http://localhost',
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
+ ]));
+
+ $this->assertEquals(501, $response['headers']['status-code']);
+ $this->assertEquals("Phone authentication is disabled for this project", $response['body']['message']);
+
return \array_merge($data, [
'token' => \substr($smsRequest['data']['message'], 0, 6)
]);
diff --git a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php
index b501e2119e..57e0b93634 100644
--- a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php
+++ b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php
@@ -2309,6 +2309,30 @@ class DatabasesCustomServerTest extends Scope
$this->assertEquals(100, $new['body']['min']);
$this->assertEquals(2000, $new['body']['max']);
+ $update = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/integer/' . $key, array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'x-appwrite-key' => $this->getProject()['apiKey']
+ ]), [
+ 'required' => false,
+ 'default' => 100,
+ 'min' => 0,
+ ]);
+
+ $this->assertEquals(200, $update['headers']['status-code']);
+
+ $update = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/integer/' . $key, array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'x-appwrite-key' => $this->getProject()['apiKey']
+ ]), [
+ 'required' => false,
+ 'default' => 10,
+ 'max' => 100,
+ ]);
+
+ $this->assertEquals(200, $update['headers']['status-code']);
+
/**
* Test against failure
*/
@@ -2368,32 +2392,6 @@ class DatabasesCustomServerTest extends Scope
$this->assertEquals(400, $update['headers']['status-code']);
$this->assertEquals(AppwriteException::GENERAL_ARGUMENT_INVALID, $update['body']['type']);
- $update = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/integer/' . $key, array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey']
- ]), [
- 'required' => false,
- 'default' => 100,
- 'min' => 0,
- ]);
-
- $this->assertEquals(400, $update['headers']['status-code']);
- $this->assertEquals(AppwriteException::GENERAL_ARGUMENT_INVALID, $update['body']['type']);
-
- $update = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/integer/' . $key, array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey']
- ]), [
- 'required' => false,
- 'default' => 100,
- 'max' => 0,
- ]);
-
- $this->assertEquals(400, $update['headers']['status-code']);
- $this->assertEquals(AppwriteException::GENERAL_ARGUMENT_INVALID, $update['body']['type']);
-
$update = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/integer/' . $key, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -2572,6 +2570,30 @@ class DatabasesCustomServerTest extends Scope
$this->assertEquals(123.456, $new['body']['min']);
$this->assertEquals(2000, $new['body']['max']);
+ $update = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/float/' . $key, array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'x-appwrite-key' => $this->getProject()['apiKey']
+ ]), [
+ 'required' => false,
+ 'default' => 123.456,
+ 'min' => 0.0,
+ ]);
+
+ $this->assertEquals(200, $update['headers']['status-code']);
+
+ $update = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/float/' . $key, array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'x-appwrite-key' => $this->getProject()['apiKey']
+ ]), [
+ 'required' => false,
+ 'default' => 23.456,
+ 'max' => 100.0,
+ ]);
+
+ $this->assertEquals(200, $update['headers']['status-code']);
+
/**
* Test against failure
*/
@@ -2631,32 +2653,6 @@ class DatabasesCustomServerTest extends Scope
$this->assertEquals(400, $update['headers']['status-code']);
$this->assertEquals(AppwriteException::GENERAL_ARGUMENT_INVALID, $update['body']['type']);
- $update = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/float/' . $key, array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey']
- ]), [
- 'required' => false,
- 'default' => 123.456,
- 'min' => 0.0,
- ]);
-
- $this->assertEquals(400, $update['headers']['status-code']);
- $this->assertEquals(AppwriteException::GENERAL_ARGUMENT_INVALID, $update['body']['type']);
-
- $update = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/float/' . $key, array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey']
- ]), [
- 'required' => false,
- 'default' => 123.456,
- 'max' => 0.0,
- ]);
-
- $this->assertEquals(400, $update['headers']['status-code']);
- $this->assertEquals(AppwriteException::GENERAL_ARGUMENT_INVALID, $update['body']['type']);
-
$update = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/float/' . $key, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php
index 914a255663..55db081720 100644
--- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php
+++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php
@@ -8,6 +8,7 @@ use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideClient;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Role;
+use Utopia\System\System;
class FunctionsCustomClientTest extends Scope
{
@@ -111,7 +112,7 @@ class FunctionsCustomClientTest extends Scope
$this->assertEquals('PHP', $output['APPWRITE_FUNCTION_RUNTIME_NAME']);
$this->assertEquals('8.0', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
$this->assertEquals(APP_VERSION_STABLE, $output['APPWRITE_VERSION']);
- $this->assertEquals('default', $output['APPWRITE_REGION']);
+ $this->assertEquals(System::getEnv('_APP_REGION', 'default'), $output['APPWRITE_REGION']);
$this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT']);
$this->assertEquals('foobar', $output['APPWRITE_FUNCTION_DATA']);
$this->assertEquals($this->getUser()['$id'], $output['APPWRITE_FUNCTION_USER_ID']);
@@ -221,7 +222,7 @@ class FunctionsCustomClientTest extends Scope
$this->assertEquals('PHP', $output['APPWRITE_FUNCTION_RUNTIME_NAME']);
$this->assertEquals('8.0', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
$this->assertEquals(APP_VERSION_STABLE, $output['APPWRITE_VERSION']);
- $this->assertEquals('default', $output['APPWRITE_REGION']);
+ $this->assertEquals(System::getEnv('_APP_REGION', 'default'), $output['APPWRITE_REGION']);
$this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT']);
$this->assertEquals('foobar', $output['APPWRITE_FUNCTION_DATA']);
$this->assertEquals($this->getUser()['$id'], $output['APPWRITE_FUNCTION_USER_ID']);
diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php
index 3ed4ca727e..182e6902a3 100644
--- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php
+++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php
@@ -13,7 +13,6 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
-use Utopia\System\System;
class FunctionsCustomServerTest extends Scope
{
@@ -1686,9 +1685,6 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(1, count($executions['body']['executions']));
});
- // Await Aggregation
- sleep(System::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', 30));
-
$this->assertEventually(function () use ($functionId) {
$response = $this->getFunctionUsage($functionId, [
'range' => '24h'
diff --git a/tests/e2e/Services/Locale/LocaleBase.php b/tests/e2e/Services/Locale/LocaleBase.php
index 0e2928004d..ee731a99e5 100644
--- a/tests/e2e/Services/Locale/LocaleBase.php
+++ b/tests/e2e/Services/Locale/LocaleBase.php
@@ -228,8 +228,8 @@ trait LocaleBase
* Test for SUCCESS
*/
$languages = require(__DIR__ . '/../../../../app/config/locale/codes.php');
- $defaultCountries = require(__DIR__ . '/../../../../app/config/locale/countries.php');
- $defaultContinents = require(__DIR__ . '/../../../../app/config/locale/continents.php');
+ $defaultCountries = array_keys(require(__DIR__ . '/../../../../app/config/locale/countries.php'));
+ $defaultContinents = array_keys(require(__DIR__ . '/../../../../app/config/locale/continents.php'));
foreach ($languages as $lang) {
$response = $this->client->call(Client::METHOD_GET, '/locale/countries', [
diff --git a/tests/e2e/Services/Migrations/MigrationsBase.php b/tests/e2e/Services/Migrations/MigrationsBase.php
index 381706f5ee..45b57d6b0c 100644
--- a/tests/e2e/Services/Migrations/MigrationsBase.php
+++ b/tests/e2e/Services/Migrations/MigrationsBase.php
@@ -10,8 +10,10 @@ use Tests\E2E\Services\Functions\FunctionsBase;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
+use Utopia\Database\Query;
use Utopia\Migration\Resource;
use Utopia\Migration\Sources\Appwrite;
+use Utopia\Migration\Sources\CSV;
trait MigrationsBase
{
@@ -53,8 +55,9 @@ trait MigrationsBase
$this->assertNotEmpty($migration['body']);
$this->assertNotEmpty($migration['body']['$id']);
- $attempts = 0;
- while ($attempts < 5) {
+ $migrationResult = [];
+
+ $this->assertEventually(function () use ($migration, &$migrationResult) {
$response = $this->client->call(Client::METHOD_GET, '/migrations/' . $migration['body']['$id'], [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getDestinationProject()['$id'],
@@ -66,24 +69,18 @@ trait MigrationsBase
$this->assertNotEmpty($response['body']['$id']);
if ($response['body']['status'] === 'failed') {
- $this->fail('Migration failed', json_encode($response['body'], JSON_PRETTY_PRINT));
+ $this->fail('Migration failed' . json_encode($response['body'], JSON_PRETTY_PRINT));
}
$this->assertNotEquals('failed', $response['body']['status']);
+ $this->assertEquals('completed', $response['body']['status']);
- if ($response['body']['status'] === 'completed') {
- return $response['body'];
- }
+ $migrationResult = $response['body'];
- if ($attempts === 4) {
- $this->assertEquals('completed', $response['body']['status']);
- }
+ return true;
+ });
- $attempts++;
- sleep(5);
- }
-
- return [];
+ return $migrationResult;
}
/**
@@ -901,4 +898,332 @@ trait MigrationsBase
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
]);
}
+
+ /**
+ * Import documents from a CSV file.
+ */
+ public function testCreateCsvMigration(): array
+ {
+ // make a database
+ $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->assertNotEmpty($response['body']['$id']);
+ $this->assertEquals(201, $response['headers']['status-code']);
+ $this->assertEquals('Test Database', $response['body']['name']);
+
+ $databaseId = $response['body']['$id'];
+
+ // make a collection
+ $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'x-appwrite-key' => $this->getProject()['apiKey']
+ ]), [
+ 'name' => 'Test collection',
+ 'collectionId' => ID::unique(),
+ ]);
+
+ $this->assertEquals(201, $response['headers']['status-code']);
+ $this->assertEquals($response['body']['name'], 'Test collection');
+
+ $collectionId = $response['body']['$id'];
+
+ // make attributes
+ $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'x-appwrite-key' => $this->getProject()['apiKey']
+ ]), [
+ 'key' => 'name',
+ 'size' => 256,
+ 'required' => true,
+ ]);
+
+ $this->assertEquals(202, $response['headers']['status-code']);
+ $this->assertEquals($response['body']['key'], 'name');
+ $this->assertEquals($response['body']['type'], 'string');
+ $this->assertEquals($response['body']['size'], 256);
+ $this->assertEquals($response['body']['required'], true);
+
+ $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/integer', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'x-appwrite-key' => $this->getProject()['apiKey']
+ ]), [
+ 'key' => 'age',
+ 'min' => 18,
+ 'max' => 65,
+ 'required' => true,
+ ]);
+
+ $this->assertEquals(202, $response['headers']['status-code']);
+ $this->assertEquals($response['body']['key'], 'age');
+ $this->assertEquals($response['body']['type'], 'integer');
+ $this->assertEquals($response['body']['min'], 18);
+ $this->assertEquals($response['body']['max'], 65);
+ $this->assertEquals($response['body']['required'], true);
+
+ // make a bucket, upload a file to it!
+ // 1. enable compression, encryption
+ $bucketOne = $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',
+ 'maximumFileSize' => 2000000, //2MB
+ 'allowedFileExtensions' => ['csv'],
+ 'compression' => 'gzip',
+ 'encryption' => true
+ ]);
+ $this->assertEquals(201, $bucketOne['headers']['status-code']);
+ $this->assertNotEmpty($bucketOne['body']['$id']);
+
+ $bucketOneId = $bucketOne['body']['$id'];
+
+ // 2. no compression and encryption
+ $bucketTwo = $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 2',
+ 'maximumFileSize' => 2000000, //2MB
+ 'allowedFileExtensions' => ['csv'],
+ 'compression' => 'none',
+ 'encryption' => false
+ ]);
+
+ $this->assertNotEmpty($bucketTwo['body']['$id']);
+ $this->assertEquals(201, $bucketTwo['headers']['status-code']);
+
+ $bucketTwoId = $bucketTwo['body']['$id'];
+
+ $bucketIds = [
+ 'compressed' => $bucketOneId,
+ 'uncompressed' => $bucketTwoId,
+
+ // in uncompressed buckets!
+ 'missing-row' => $bucketTwoId,
+ 'missing-column' => $bucketTwoId,
+ 'irrelevant-column' => $bucketTwoId,
+ ];
+
+ $fileIds = [];
+
+ foreach ($bucketIds as $label => $bucketId) {
+ $csvFileName = match ($label) {
+ 'missing-row',
+ 'missing-column',
+ 'irrelevant-column' => "{$label}.csv",
+ default => 'documents.csv',
+ };
+
+ $mimeType = match ($csvFileName) {
+ default => 'text/csv',
+ 'missing-row.csv' => 'text/plain', // invalid csv structure, falls back to plain text!
+ };
+
+ $response = $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/csv/'.$csvFileName), $mimeType, $csvFileName),
+ ]);
+
+ $this->assertEquals(201, $response['headers']['status-code']);
+ $this->assertNotEmpty($response['body']['$id']);
+ $this->assertEquals($csvFileName, $response['body']['name']);
+ $this->assertEquals($mimeType, $response['body']['mimeType']);
+
+ $fileIds[$label] = $response['body']['$id'];
+ }
+
+ // compressed, fail.
+ $compressed = $this->performCsvMigration(
+ [
+ 'fileId' => $fileIds['compressed'],
+ 'bucketId' => $bucketIds['compressed'],
+ 'resourceId' => $databaseId . ':' . $collectionId,
+ ]
+ );
+
+ // fail on compressed, encrypted buckets!
+ $this->assertEquals(400, $compressed['body']['code']);
+ $this->assertEquals('storage_file_type_unsupported', $compressed['body']['type']);
+ $this->assertEquals('Only uncompressed, unencrypted CSV files can be used for document import.', $compressed['body']['message']);
+
+ // missing attribute, fail in worker.
+ $missingColumn = $this->performCsvMigration(
+ [
+ 'fileId' => $fileIds['missing-column'],
+ 'bucketId' => $bucketIds['missing-column'],
+ 'resourceId' => $databaseId . ':' . $collectionId,
+ ]
+ );
+
+ $this->assertEventually(function () use ($missingColumn, $databaseId, $collectionId) {
+ $migrationId = $missingColumn['body']['$id'];
+ $migration = $this->client->call(Client::METHOD_GET, '/migrations/'.$migrationId, array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()));
+
+ $this->assertEquals(200, $migration['headers']['status-code']);
+ $this->assertEquals('finished', $migration['body']['stage']);
+ $this->assertEquals('failed', $migration['body']['status']);
+ $this->assertEquals('CSV', $migration['body']['source']);
+ $this->assertEquals('Appwrite', $migration['body']['destination']);
+ $this->assertContains(Resource::TYPE_DOCUMENT, $migration['body']['resources']);
+ $this->assertEmpty($migration['body']['statusCounters']);
+ $this->assertThat(
+ implode("\n", $migration['body']['errors']),
+ $this->stringContains("CSV header mismatch. Missing attribute: 'age'")
+ );
+ }, 60000, 500);
+
+ // missing row data, fail in worker.
+ $missingColumn = $this->performCsvMigration(
+ [
+ 'fileId' => $fileIds['missing-row'],
+ 'bucketId' => $bucketIds['missing-row'],
+ 'resourceId' => $databaseId . ':' . $collectionId,
+ ]
+ );
+
+ $this->assertEventually(function () use ($missingColumn, $databaseId, $collectionId) {
+ $migrationId = $missingColumn['body']['$id'];
+ $migration = $this->client->call(Client::METHOD_GET, '/migrations/'.$migrationId, array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()));
+
+ $this->assertEquals(200, $migration['headers']['status-code']);
+ $this->assertEquals('finished', $migration['body']['stage']);
+ $this->assertEquals('failed', $migration['body']['status']);
+ $this->assertEquals('CSV', $migration['body']['source']);
+ $this->assertEquals('Appwrite', $migration['body']['destination']);
+ $this->assertContains(Resource::TYPE_DOCUMENT, $migration['body']['resources']);
+ $this->assertEmpty($migration['body']['statusCounters']);
+ $this->assertThat(
+ implode("\n", $migration['body']['errors']),
+ $this->stringContains('CSV row does not match the number of header columns')
+ );
+ }, 60000, 500);
+
+ // irrelevant column - email, fail in worker.
+ $irrelevantColumn = $this->performCsvMigration(
+ [
+ 'fileId' => $fileIds['irrelevant-column'],
+ 'bucketId' => $bucketIds['irrelevant-column'],
+ 'resourceId' => $databaseId . ':' . $collectionId,
+ ]
+ );
+
+ $this->assertEventually(function () use ($irrelevantColumn, $databaseId, $collectionId) {
+ $migrationId = $irrelevantColumn['body']['$id'];
+ $migration = $this->client->call(Client::METHOD_GET, '/migrations/'.$migrationId, array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()));
+
+ $this->assertEquals(200, $migration['headers']['status-code']);
+ $this->assertEquals('finished', $migration['body']['stage']);
+ $this->assertEquals('failed', $migration['body']['status']);
+ $this->assertEquals('CSV', $migration['body']['source']);
+ $this->assertEquals('Appwrite', $migration['body']['destination']);
+ $this->assertContains(Resource::TYPE_DOCUMENT, $migration['body']['resources']);
+ $this->assertEmpty($migration['body']['statusCounters']);
+ $this->assertThat(
+ implode("\n", $migration['body']['errors']),
+ $this->stringContains("CSV header mismatch. Unexpected attribute: 'email'")
+ );
+ }, 60000, 500);
+
+ // no compression, no encryption, pass.
+ $migration = $this->performCsvMigration(
+ [
+ 'endpoint' => 'http://localhost/v1',
+ 'fileId' => $fileIds['uncompressed'],
+ 'bucketId' => $bucketIds['uncompressed'],
+ 'resourceId' => $databaseId . ':' . $collectionId,
+ ]
+ );
+
+ $this->assertEmpty($migration['body']['statusCounters']);
+ $this->assertEquals('CSV', $migration['body']['source']);
+ $this->assertEquals('pending', $migration['body']['status']);
+ $this->assertEquals('Appwrite', $migration['body']['destination']);
+ $this->assertContains(Resource::TYPE_DOCUMENT, $migration['body']['resources']);
+
+ return [
+ 'databaseId' => $databaseId,
+ 'collectionId' => $collectionId,
+ 'migrationId' => $migration['body']['$id'],
+ ];
+ }
+
+ /**
+ * @depends testCreateCsvMigration
+ */
+ public function testImportSuccessful(array $response): void
+ {
+ $databaseId = $response['databaseId'];
+ $collectionId = $response['collectionId'];
+ $migrationId = $response['migrationId'];
+
+ $documentsCountInCSV = 100;
+
+ // get migration stats
+ $this->assertEventually(function () use ($migrationId, $databaseId, $collectionId, $documentsCountInCSV) {
+ $migration = $this->client->call(Client::METHOD_GET, '/migrations/'.$migrationId, array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()));
+
+ $this->assertEquals(200, $migration['headers']['status-code']);
+ $this->assertEquals('finished', $migration['body']['stage']);
+ $this->assertEquals('completed', $migration['body']['status']);
+ $this->assertEquals('CSV', $migration['body']['source']);
+ $this->assertEquals('Appwrite', $migration['body']['destination']);
+ $this->assertContains(Resource::TYPE_DOCUMENT, $migration['body']['resources']);
+ $this->assertArrayHasKey(Resource::TYPE_DOCUMENT, $migration['body']['statusCounters']);
+ $this->assertEquals($documentsCountInCSV, $migration['body']['statusCounters'][Resource::TYPE_DOCUMENT]['success']);
+ }, 60000, 500);
+
+ // get documents count
+ $documents = $this->client->call(Client::METHOD_GET, '/databases/'.$databaseId.'/collections/'.$collectionId.'/documents', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'queries' => [
+ // there should be only 100!
+ Query::limit(150)->toString()
+ ]
+ ]);
+
+ $this->assertEquals(200, $documents['headers']['status-code']);
+ $this->assertIsArray($documents['body']['documents']);
+ $this->assertIsNumeric($documents['body']['total']);
+ $this->assertEquals($documentsCountInCSV, $documents['body']['total']);
+ }
+
+ private function performCsvMigration(array $body): array
+ {
+ return $this->client->call(Client::METHOD_POST, '/migrations/csv', [
+ 'content-type' => 'application/json',
+ 'x-appwrite-key' => $this->getProject()['apiKey'],
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $body);
+ }
}
diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php
index 34c1142619..a0ada0b774 100644
--- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php
+++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php
@@ -14,6 +14,7 @@ use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
+use Utopia\System\System;
class ProjectsConsoleClientTest extends Scope
{
@@ -49,7 +50,7 @@ class ProjectsConsoleClientTest extends Scope
'projectId' => ID::unique(),
'name' => 'Project Test',
'teamId' => $team['body']['$id'],
- 'region' => 'default',
+ 'region' => System::getEnv('_APP_REGION', 'default')
]);
$this->assertEquals(201, $response['headers']['status-code']);
@@ -89,7 +90,7 @@ class ProjectsConsoleClientTest extends Scope
'projectId' => ID::unique(),
'name' => '',
'teamId' => $team['body']['$id'],
- 'region' => 'default'
+ 'region' => System::getEnv('_APP_REGION', 'default')
]);
$this->assertEquals(400, $response['headers']['status-code']);
@@ -100,7 +101,7 @@ class ProjectsConsoleClientTest extends Scope
], $this->getHeaders()), [
'projectId' => ID::unique(),
'name' => 'Project Test',
- 'region' => 'default'
+ 'region' => System::getEnv('_APP_REGION', 'default')
]);
$this->assertEquals(401, $response['headers']['status-code']);
@@ -129,7 +130,7 @@ class ProjectsConsoleClientTest extends Scope
'projectId' => $projectId,
'name' => 'Project Duplicate',
'teamId' => $teamId,
- 'region' => 'default'
+ 'region' => System::getEnv('_APP_REGION', 'default')
]);
$this->assertEquals(409, $response['headers']['status-code']);
@@ -178,7 +179,7 @@ class ProjectsConsoleClientTest extends Scope
'projectId' => ID::unique(),
'name' => 'Team 1 Project',
'teamId' => $team1,
- 'region' => 'default',
+ 'region' => System::getEnv('_APP_REGION', 'default'),
]);
$this->assertEquals(201, $response['headers']['status-code']);
@@ -277,7 +278,7 @@ class ProjectsConsoleClientTest extends Scope
'projectId' => ID::unique(),
'name' => 'Project Test 2',
'teamId' => $team['body']['$id'],
- 'region' => 'default'
+ 'region' => System::getEnv('_APP_REGION', 'default')
]);
$this->assertEquals(201, $response['headers']['status-code']);
@@ -2042,7 +2043,7 @@ class ProjectsConsoleClientTest extends Scope
'projectId' => ID::unique(),
'name' => 'Project Test',
'teamId' => $team['body']['$id'],
- 'region' => 'default'
+ 'region' => System::getEnv('_APP_REGION', 'default')
]);
$this->assertEquals(201, $project['headers']['status-code']);
@@ -2135,7 +2136,7 @@ class ProjectsConsoleClientTest extends Scope
'projectId' => ID::unique(),
'name' => 'Project Test',
'teamId' => $team['body']['$id'],
- 'region' => 'default'
+ 'region' => System::getEnv('_APP_REGION', 'default')
]);
$this->assertEquals(201, $project['headers']['status-code']);
@@ -3749,7 +3750,7 @@ class ProjectsConsoleClientTest extends Scope
'projectId' => ID::unique(),
'name' => 'Amazing Project',
'teamId' => $teamId,
- 'region' => 'default'
+ 'region' => System::getEnv('_APP_REGION', 'default')
]);
$this->assertEquals(201, $project['headers']['status-code']);
@@ -3820,7 +3821,7 @@ class ProjectsConsoleClientTest extends Scope
'projectId' => ID::unique(),
'name' => 'Amazing Project 1',
'teamId' => $teamId,
- 'region' => 'default'
+ 'region' => System::getEnv('_APP_REGION', 'default')
]);
$project2 = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
@@ -3830,7 +3831,7 @@ class ProjectsConsoleClientTest extends Scope
'projectId' => ID::unique(),
'name' => 'Amazing Project 2',
'teamId' => $teamId,
- 'region' => 'default'
+ 'region' => System::getEnv('_APP_REGION', 'default')
]);
$project1Id = $project1['body']['$id'];
diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php
index dda524fc7c..e356397408 100644
--- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php
+++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php
@@ -1312,22 +1312,15 @@ class RealtimeCustomClientTest extends Scope
$this->assertNotEmpty($deployment['body']['$id']);
// Poll until deployment is built
- while (true) {
+ $this->assertEventually(function () use ($function, $deploymentId) {
$deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
- if (
- $deployment['headers']['status-code'] >= 400
- || \in_array($deployment['body']['status'], ['ready', 'failed'])
- ) {
- break;
- }
-
- \sleep(1);
- }
+ $this->assertEquals('ready', $deployment['body']['status'], \json_encode($deployment['body']));
+ });
$response = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
'content-type' => 'application/json',
diff --git a/tests/e2e/Services/Storage/StorageConsoleClientTest.php b/tests/e2e/Services/Storage/StorageConsoleClientTest.php
index 5b6731b35e..bbb14fb136 100644
--- a/tests/e2e/Services/Storage/StorageConsoleClientTest.php
+++ b/tests/e2e/Services/Storage/StorageConsoleClientTest.php
@@ -98,11 +98,13 @@ class StorageConsoleClientTest extends Scope
]);
$this->assertEquals(200, $response['headers']['status-code']);
- $this->assertEquals(5, count($response['body']));
+ $this->assertEquals(7, count($response['body']));
$this->assertEquals('24h', $response['body']['range']);
$this->assertIsNumeric($response['body']['filesTotal']);
$this->assertIsNumeric($response['body']['filesStorageTotal']);
$this->assertIsArray($response['body']['files']);
$this->assertIsArray($response['body']['storage']);
+ $this->assertIsArray($response['body']['imageTransformations']);
+ $this->assertIsNumeric($response['body']['imageTransformationsTotal']);
}
}
diff --git a/tests/e2e/Services/Teams/TeamsBase.php b/tests/e2e/Services/Teams/TeamsBase.php
index 2328e4cdbf..80ac1621ee 100644
--- a/tests/e2e/Services/Teams/TeamsBase.php
+++ b/tests/e2e/Services/Teams/TeamsBase.php
@@ -37,6 +37,37 @@ trait TeamsBase
$teamUid = $response1['body']['$id'];
$teamName = $response1['body']['name'];
+ /**
+ * Test: Attempt to downgrade the only OWNER in an organization (should fail)
+ */
+ if ($this->getProject()['$id'] === 'console') {
+ // Step 1: Fetch all team memberships — only one exists at this point
+ $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'queries' => [
+ Query::limit(1)->toString(),
+ ],
+ ]);
+
+ // Step 2: Extract the membership ID of the only member (also the only OWNER)
+ $membershipID = $response['body']['memberships'][0]['$id'];
+
+ // Step 3: Attempt to downgrade the member's role to 'developer'
+ $response = $this->client->call(Client::METHOD_PATCH, '/teams/' . $teamUid . '/memberships/' . $membershipID, array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'roles' => ['developer']
+ ]);
+
+ // Step 4: Assert failure — cannot remove the only OWNER from a team
+ $this->assertEquals(400, $response['headers']['status-code']);
+ $this->assertEquals('general_argument_invalid', $response['body']['type']);
+ $this->assertEquals('There must be at least one owner in the organization.', $response['body']['message']);
+ }
+
$teamId = ID::unique();
$response2 = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
'content-type' => 'application/json',
diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php
index 04e0eb5bc3..00e999672f 100644
--- a/tests/e2e/Services/Users/UsersBase.php
+++ b/tests/e2e/Services/Users/UsersBase.php
@@ -801,6 +801,97 @@ trait UsersBase
return $data;
}
+ /**
+ * @depends testGetUser
+ */
+ public function testListUserMemberships(array $data): array
+ {
+ /**
+ * Test for SUCCESS
+ */
+
+ // create a new team
+ $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'teamId' => 'unique()',
+ 'name' => 'Test Team',
+ ]);
+
+ // create a new membership
+ $membership = $this->client->call(Client::METHOD_POST, '/teams/' . $team['body']['$id'] . '/memberships', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'userId' => $data['userId'],
+ 'roles' => ['new-role'],
+ ]);
+
+ // list the memberships
+ $response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/memberships', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()));
+
+ $this->assertEquals($response['headers']['status-code'], 200);
+ $this->assertEquals($response['body']['memberships'][0]['$id'], $membership['body']['$id']);
+ $this->assertEquals($response['body']['memberships'][0]['roles'], ['new-role']);
+ $this->assertEquals($response['body']['total'], 1);
+
+ // create another membership with a new role
+ $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'teamId' => 'unique()',
+ 'name' => 'Test Team 2',
+ ]);
+
+ $membership = $this->client->call(Client::METHOD_POST, '/teams/' . $team['body']['$id'] . '/memberships', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'userId' => $data['userId'],
+ 'roles' => ['new-role-2'],
+ ]);
+
+ // list out memberships and query by role
+ $response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/memberships', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'queries' => [
+ Query::contains('roles', ['new-role-2'])->toString()
+ ]
+ ]);
+
+ $this->assertEquals($response['headers']['status-code'], 200);
+ $this->assertEquals($response['body']['memberships'][0]['$id'], $membership['body']['$id']);
+ $this->assertEquals($response['body']['memberships'][0]['roles'], ['new-role-2']);
+ $this->assertEquals($response['body']['total'], 1);
+
+ /**
+ * Test for FAILURE
+ */
+
+ // query using equal on array field
+ $response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/memberships', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'queries' => [
+ Query::equal('roles', ['new-role-2'])->toString()
+ ]
+ ]);
+
+ $this->assertEquals($response['body']['code'], 400);
+ $this->assertEquals($response['body']['message'], 'Invalid `queries` param: Invalid query: Cannot query equal on attribute "roles" because it is an array.');
+ $this->assertEquals($response['body']['type'], 'general_argument_invalid');
+
+ return $data;
+ }
+
/**
* @depends testGetUser
*/
diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php
index 2ef41003ee..c743810feb 100644
--- a/tests/e2e/Services/Webhooks/WebhooksBase.php
+++ b/tests/e2e/Services/Webhooks/WebhooksBase.php
@@ -2,6 +2,7 @@
namespace Tests\E2E\Services\Webhooks;
+use Appwrite\Tests\Async;
use Appwrite\Tests\Retry;
use CURLFile;
use Tests\E2E\Client;
@@ -12,29 +13,20 @@ use Utopia\Database\Validator\Datetime as DatetimeValidator;
trait WebhooksBase
{
- protected function awaitDeploymentIsBuilt($functionId, $deploymentId, $checkForSuccess = true): void
+ use Async;
+
+ protected function awaitDeploymentIsBuilt($functionId, $deploymentId): void
{
- while (true) {
+ $this->assertEventually(function () use ($functionId, $deploymentId) {
$deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
- if (
- $deployment['headers']['status-code'] >= 400
- || \in_array($deployment['body']['status'], ['ready', 'failed'])
- ) {
- break;
- }
-
- \sleep(1);
- }
-
- if ($checkForSuccess) {
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertEquals('ready', $deployment['body']['status'], \json_encode($deployment['body']));
- }
+ });
}
public static function getWebhookSignature(array $webhook, string $signatureKey): string
diff --git a/tests/resources/csv/documents.csv b/tests/resources/csv/documents.csv
new file mode 100644
index 0000000000..ea1e33b5bd
--- /dev/null
+++ b/tests/resources/csv/documents.csv
@@ -0,0 +1,101 @@
+$id,name,age
+hxfcwpcas5xokpwe,Diamond Mendez,56
+gw8nxwf6esn3tfwf,Michael Huff,20
+xb6bxg56lral1qy9,Alyssa Rodriguez,37
+imerjq5j36y3agh2,Barbara Smith,26
+07yq9qdlhmbzmr35,Evelyn Edwards,54
+ksqo631sbhwj5ltg,Tina Richardson,41
+j7zlndgu0gbshp15,Joel Hernandez,49
+mfntvnljrcmf7h6v,Zachary Cooper,59
+5f9b01nziqu2h8ed,Brittany Spears,20
+4vxzbnzraqznk5u8,Holly White,47
+d4ywy3mtphaatbpf,Kimberly Barnes,27
+88odnk6nthyyvbal,Stephen Miller,53
+08oekee3fn7mzaa5,Yvonne Newman,41
+quw55kn9895i5e4v,Carol Kane,38
+nge6bm8ykripei6f,Doris Foster,44
+4k16i33s0xl2ypx9,Joseph Stokes,28
+q0j5rxbgid66snyf,Steve Williams,31
+n1oxun7mqq3p103y,James Carey,29
+0dbvs840jkf8i0ye,Kathryn Henry,38
+5sfaidgs1h87v15v,Christopher Landry,23
+vg3punvfu5khmf41,Jennifer Mcgee,62
+f933qydr9u5b2r11,Cathy Church,35
+wjv87y1inf8yk32s,Jose Lopez,41
+uljysdvdlcyrbrwk,William Rose,30
+ot8xtzh77j55wq0s,Sarah Ford,26
+9t76vnsv2u36s43t,Alisha Jones,61
+66y4tnty62hw8c02,Kristin Kelly,61
+2punfblazi5v16ar,Brendan Stout,40
+sxhr4nf5w2gx4wbg,Kelly Cruz,18
+68dvrqfwqnkq5el9,Samantha Martin,50
+20192l6dbeinhkh0,David Santos,46
+si0l4dgay09ebfmf,Elizabeth Carroll,22
+lhse40vbldqb6ap1,Corey Owens,46
+h5t3pslykyx3kxfm,Shelby Mueller,65
+ldc0luydrw6jub0f,Dr. Sylvia Myers,29
+voc9628xg4dsgw2y,Scott Freeman,48
+o4y0gk3gqv1ax2fz,Christopher Atkinson,21
+u1n3x4e4u7e0vzj6,Sean Diaz,31
+s36eskwtm0w7lwr7,Bobby Dyer,57
+4hjnag1p5iwvtixd,Daniel Hall,62
+m91d80oxsa216zbh,Jennifer Ramirez,65
+5hj6858zo2g85n6v,Angela Jackson,57
+8m8oihv9a1e7nn92,Kelly Lewis,36
+7azy39la0no0mxi7,Jessica Munoz,55
+47pmjkhnnqhyit8c,Kelly George,65
+6j6cpy4kgneg1mmh,Anthony Johnson,65
+tnlmtvap1zz89km9,Regina Fields,61
+6cyuvnwwqdmrpfzh,Sharon Schaefer,30
+p1v4pyu2pqodc0ey,Jacob French,62
+6npynnhjt2jd05xo,Jessica Costa,23
+wcxedf13n2e9qi4l,George Hardy,53
+yf2xlcmszk2tqeig,Andrea Allison,20
+3bf2zzv7poststwa,Kevin Ferguson,32
+c2iataz0hhv39q63,Joseph Johnson,58
+3e8npxhov4a39pvq,Ashley Martinez,18
+t7dp41tysipytywq,Charles Nixon,23
+z8cztq7c47phyfhk,Carol Dudley,40
+2636f9d8r4ipm3h6,David Weber,51
+eh3f6wxtvkjq6ykq,Scott Robinson,32
+raskbwpsje69a59h,Anthony Hardy,38
+90hn1p0b4cs9e2og,Mackenzie Owens,52
+am3swwfbo076x0v1,Brian Foster,27
+5uw7utb9lq5cfncw,Hannah Forbes,56
+cs6mbfzkzifefx6r,Lauren Reed,26
+ftw3uvztziiz9x00,Morgan Smith,28
+uhrqseeo43mozpaq,Samantha Alexander,65
+pvvmzyfc1lxor11e,Tiffany Roberts,20
+jia7bdag4abz123s,Emily Hayes,34
+h6oozcngbz8o5x4y,Rebecca Villegas,52
+9v6z1pn2f9twcy12,Donald Shah,61
+wzz3jduioso77o7f,Denise Cain,59
+u51plhgvjodkswnr,Kristine Ramirez,53
+t1uhkmiytfyc13vc,Stacey Adkins,61
+iqaqnf0ybg2ct507,Daniel Hunt,20
+idwrwv2uu4hcpv2i,Roberta Johnson,48
+2yd2hd6auetjacyo,Jason Williamson,39
+egrmdbibnjhi914x,Sandra Robinson,50
+15m1pz2bb0ercgyk,Steve Rice,25
+0i21bhkxdagjurb7,Kimberly Fritz,53
+726ofi7h5snreq67,Brianna Reynolds,33
+csqxse3wym56eim6,Alexander Williams,50
+qeaoylnrsf8p3byg,Andrew Thomas,25
+edsswobumzyzbvhf,Austin Williams,57
+hdzhzpt0ahy5hkib,Nicholas Williams,24
+w1qmvmg4roa8xnwu,Mrs. Michelle Cisneros,48
+3z3o73x7adyuo6w0,Stacey Smith,39
+sse2u5zlgoqrgmcf,Laura Beck,20
+rvovijmvch58r4yx,Molly Clark,51
+doe06nrx8sg5mcuv,Carmen Morris,41
+jbjdwuvj5s4kw04y,Amanda Munoz,20
+6k2ewkla7js0yw23,Rachel Collins,44
+fcxuyr4kkhrnigu1,John Alexander,18
+d25fuwlos5mk07o0,Stacy Hunter,22
+1vdai2rxmwd57oet,Eric Massey,40
+pq4jnt9izu1wlrzd,Scott Garcia,20
+lz9kfc0lty5xcz14,Cassandra Nelson,35
+pu7w6tyab5jd4we9,Aaron Johnson,50
+8dupswd2kqwdyn8v,Shannon Sherman,45
+ye466l71jthiz2p6,April Garcia,60
+xogsmfwb73l16qdt,Evan Lynn,20
diff --git a/tests/resources/csv/irrelevant-column.csv b/tests/resources/csv/irrelevant-column.csv
new file mode 100644
index 0000000000..92105ceaa2
--- /dev/null
+++ b/tests/resources/csv/irrelevant-column.csv
@@ -0,0 +1,101 @@
+$id,name,age,email
+hxfcwpcas5xokpwe,Diamond Mendez,56,diamond.mendez@example.com
+gw8nxwf6esn3tfwf,Michael Huff,20,michael.huff@example.com
+xb6bxg56lral1qy9,Alyssa Rodriguez,37,alyssa.rodriguez@example.com
+imerjq5j36y3agh2,Barbara Smith,26,barbara.smith@example.com
+07yq9qdlhmbzmr35,Evelyn Edwards,54,evelyn.edwards@example.com
+ksqo631sbhwj5ltg,Tina Richardson,41,tina.richardson@example.com
+j7zlndgu0gbshp15,Joel Hernandez,49,joel.hernandez@example.com
+mfntvnljrcmf7h6v,Zachary Cooper,59,zachary.cooper@example.com
+5f9b01nziqu2h8ed,Brittany Spears,20,brittany.spears@example.com
+4vxzbnzraqznk5u8,Holly White,47,holly.white@example.com
+d4ywy3mtphaatbpf,Kimberly Barnes,27,kimberly.barnes@example.com
+88odnk6nthyyvbal,Stephen Miller,53,stephen.miller@example.com
+08oekee3fn7mzaa5,Yvonne Newman,41,yvonne.newman@example.com
+quw55kn9895i5e4v,Carol Kane,38,carol.kane@example.com
+nge6bm8ykripei6f,Doris Foster,44,doris.foster@example.com
+4k16i33s0xl2ypx9,Joseph Stokes,28,joseph.stokes@example.com
+q0j5rxbgid66snyf,Steve Williams,31,steve.williams@example.com
+n1oxun7mqq3p103y,James Carey,29,james.carey@example.com
+0dbvs840jkf8i0ye,Kathryn Henry,38,kathryn.henry@example.com
+5sfaidgs1h87v15v,Christopher Landry,23,christopher.landry@example.com
+vg3punvfu5khmf41,Jennifer Mcgee,62,jennifer.mcgee@example.com
+f933qydr9u5b2r11,Cathy Church,35,cathy.church@example.com
+wjv87y1inf8yk32s,Jose Lopez,41,jose.lopez@example.com
+uljysdvdlcyrbrwk,William Rose,30,william.rose@example.com
+ot8xtzh77j55wq0s,Sarah Ford,26,sarah.ford@example.com
+9t76vnsv2u36s43t,Alisha Jones,61,alisha.jones@example.com
+66y4tnty62hw8c02,Kristin Kelly,61,kristin.kelly@example.com
+2punfblazi5v16ar,Brendan Stout,40,brendan.stout@example.com
+sxhr4nf5w2gx4wbg,Kelly Cruz,18,kelly.cruz@example.com
+68dvrqfwqnkq5el9,Samantha Martin,50,samantha.martin@example.com
+20192l6dbeinhkh0,David Santos,46,david.santos@example.com
+si0l4dgay09ebfmf,Elizabeth Carroll,22,elizabeth.carroll@example.com
+lhse40vbldqb6ap1,Corey Owens,46,corey.owens@example.com
+h5t3pslykyx3kxfm,Shelby Mueller,65,shelby.mueller@example.com
+ldc0luydrw6jub0f,Dr. Sylvia Myers,29,sylvia.myers@example.com
+voc9628xg4dsgw2y,Scott Freeman,48,scott.freeman@example.com
+o4y0gk3gqv1ax2fz,Christopher Atkinson,21,christopher.atkinson@example.com
+u1n3x4e4u7e0vzj6,Sean Diaz,31,sean.diaz@example.com
+s36eskwtm0w7lwr7,Bobby Dyer,57,bobby.dyer@example.com
+4hjnag1p5iwvtixd,Daniel Hall,62,daniel.hall@example.com
+m91d80oxsa216zbh,Jennifer Ramirez,65,jennifer.ramirez@example.com
+5hj6858zo2g85n6v,Angela Jackson,57,angela.jackson@example.com
+8m8oihv9a1e7nn92,Kelly Lewis,36,kelly.lewis@example.com
+7azy39la0no0mxi7,Jessica Munoz,55,jessica.munoz@example.com
+47pmjkhnnqhyit8c,Kelly George,65,kelly.george@example.com
+6j6cpy4kgneg1mmh,Anthony Johnson,65,anthony.johnson@example.com
+tnlmtvap1zz89km9,Regina Fields,61,regina.fields@example.com
+6cyuvnwwqdmrpfzh,Sharon Schaefer,30,sharon.schaefer@example.com
+p1v4pyu2pqodc0ey,Jacob French,62,jacob.french@example.com
+6npynnhjt2jd05xo,Jessica Costa,23,jessica.costa@example.com
+wcxedf13n2e9qi4l,George Hardy,53,george.hardy@example.com
+yf2xlcmszk2tqeig,Andrea Allison,20,andrea.allison@example.com
+3bf2zzv7poststwa,Kevin Ferguson,32,kevin.ferguson@example.com
+c2iataz0hhv39q63,Joseph Johnson,58,joseph.johnson@example.com
+3e8npxhov4a39pvq,Ashley Martinez,18,ashley.martinez@example.com
+t7dp41tysipytywq,Charles Nixon,23,charles.nixon@example.com
+z8cztq7c47phyfhk,Carol Dudley,40,carol.dudley@example.com
+2636f9d8r4ipm3h6,David Weber,51,david.weber@example.com
+eh3f6wxtvkjq6ykq,Scott Robinson,32,scott.robinson@example.com
+raskbwpsje69a59h,Anthony Hardy,38,anthony.hardy@example.com
+90hn1p0b4cs9e2og,Mackenzie Owens,52,mackenzie.owens@example.com
+am3swwfbo076x0v1,Brian Foster,27,brian.foster@example.com
+5uw7utb9lq5cfncw,Hannah Forbes,56,hannah.forbes@example.com
+cs6mbfzkzifefx6r,Lauren Reed,26,lauren.reed@example.com
+ftw3uvztziiz9x00,Morgan Smith,28,morgan.smith@example.com
+uhrqseeo43mozpaq,Samantha Alexander,65,samantha.alexander@example.com
+pvvmzyfc1lxor11e,Tiffany Roberts,20,tiffany.roberts@example.com
+jia7bdag4abz123s,Emily Hayes,34,emily.hayes@example.com
+h6oozcngbz8o5x4y,Rebecca Villegas,52,rebecca.villegas@example.com
+9v6z1pn2f9twcy12,Donald Shah,61,donald.shah@example.com
+wzz3jduioso77o7f,Denise Cain,59,denise.cain@example.com
+u51plhgvjodkswnr,Kristine Ramirez,53,kristine.ramirez@example.com
+t1uhkmiytfyc13vc,Stacey Adkins,61,stacey.adkins@example.com
+iqaqnf0ybg2ct507,Daniel Hunt,20,daniel.hunt@example.com
+idwrwv2uu4hcpv2i,Roberta Johnson,48,roberta.johnson@example.com
+2yd2hd6auetjacyo,Jason Williamson,39,jason.williamson@example.com
+egrmdbibnjhi914x,Sandra Robinson,50,sandra.robinson@example.com
+15m1pz2bb0ercgyk,Steve Rice,25,steve.rice@example.com
+0i21bhkxdagjurb7,Kimberly Fritz,53,kimberly.fritz@example.com
+726ofi7h5snreq67,Brianna Reynolds,33,brianna.reynolds@example.com
+csqxse3wym56eim6,Alexander Williams,50,alexander.williams@example.com
+qeaoylnrsf8p3byg,Andrew Thomas,25,andrew.thomas@example.com
+edsswobumzyzbvhf,Austin Williams,57,austin.williams@example.com
+hdzhzpt0ahy5hkib,Nicholas Williams,24,nicholas.williams@example.com
+w1qmvmg4roa8xnwu,Mrs. Michelle Cisneros,48,michelle.cisneros@example.com
+3z3o73x7adyuo6w0,Stacey Smith,39,stacey.smith@example.com
+sse2u5zlgoqrgmcf,Laura Beck,20,laura.beck@example.com
+rvovijmvch58r4yx,Molly Clark,51,molly.clark@example.com
+doe06nrx8sg5mcuv,Carmen Morris,41,carmen.morris@example.com
+jbjdwuvj5s4kw04y,Amanda Munoz,20,amanda.munoz@example.com
+6k2ewkla7js0yw23,Rachel Collins,44,rachel.collins@example.com
+fcxuyr4kkhrnigu1,John Alexander,18,john.alexander@example.com
+d25fuwlos5mk07o0,Stacy Hunter,22,stacy.hunter@example.com
+1vdai2rxmwd57oet,Eric Massey,40,eric.massey@example.com
+pq4jnt9izu1wlrzd,Scott Garcia,20,scott.garcia@example.com
+lz9kfc0lty5xcz14,Cassandra Nelson,35,cassandra.nelson@example.com
+pu7w6tyab5jd4we9,Aaron Johnson,50,aaron.johnson@example.com
+8dupswd2kqwdyn8v,Shannon Sherman,45,shannon.sherman@example.com
+ye466l71jthiz2p6,April Garcia,60,april.garcia@example.com
+xogsmfwb73l16qdt,Evan Lynn,20,evan.lynn@example.com
diff --git a/tests/resources/csv/missing-column.csv b/tests/resources/csv/missing-column.csv
new file mode 100644
index 0000000000..e57b5ccb2e
--- /dev/null
+++ b/tests/resources/csv/missing-column.csv
@@ -0,0 +1,101 @@
+$id,name
+hxfcwpcas5xokpwe,Diamond Mendez
+gw8nxwf6esn3tfwf,Michael Huff
+xb6bxg56lral1qy9,Alyssa Rodriguez
+imerjq5j36y3agh2,Barbara Smith
+07yq9qdlhmbzmr35,Evelyn Edwards
+ksqo631sbhwj5ltg,Tina Richardson
+j7zlndgu0gbshp15,Joel Hernandez
+mfntvnljrcmf7h6v,Zachary Cooper
+5f9b01nziqu2h8ed,Brittany Spears
+4vxzbnzraqznk5u8,Holly White
+d4ywy3mtphaatbpf,Kimberly Barnes
+88odnk6nthyyvbal,Stephen Miller
+08oekee3fn7mzaa5,Yvonne Newman
+quw55kn9895i5e4v,Carol Kane
+nge6bm8ykripei6f,Doris Foster
+4k16i33s0xl2ypx9,Joseph Stokes
+q0j5rxbgid66snyf,Steve Williams
+n1oxun7mqq3p103y,James Carey
+0dbvs840jkf8i0ye,Kathryn Henry
+5sfaidgs1h87v15v,Christopher Landry
+vg3punvfu5khmf41,Jennifer Mcgee
+f933qydr9u5b2r11,Cathy Church
+wjv87y1inf8yk32s,Jose Lopez
+uljysdvdlcyrbrwk,William Rose
+ot8xtzh77j55wq0s,Sarah Ford
+9t76vnsv2u36s43t,Alisha Jones
+66y4tnty62hw8c02,Kristin Kelly
+2punfblazi5v16ar,Brendan Stout
+sxhr4nf5w2gx4wbg,Kelly Cruz
+68dvrqfwqnkq5el9,Samantha Martin
+20192l6dbeinhkh0,David Santos
+si0l4dgay09ebfmf,Elizabeth Carroll
+lhse40vbldqb6ap1,Corey Owens
+h5t3pslykyx3kxfm,Shelby Mueller
+ldc0luydrw6jub0f,Dr. Sylvia Myers
+voc9628xg4dsgw2y,Scott Freeman
+o4y0gk3gqv1ax2fz,Christopher Atkinson
+u1n3x4e4u7e0vzj6,Sean Diaz
+s36eskwtm0w7lwr7,Bobby Dyer
+4hjnag1p5iwvtixd,Daniel Hall
+m91d80oxsa216zbh,Jennifer Ramirez
+5hj6858zo2g85n6v,Angela Jackson
+8m8oihv9a1e7nn92,Kelly Lewis
+7azy39la0no0mxi7,Jessica Munoz
+47pmjkhnnqhyit8c,Kelly George
+6j6cpy4kgneg1mmh,Anthony Johnson
+tnlmtvap1zz89km9,Regina Fields
+6cyuvnwwqdmrpfzh,Sharon Schaefer
+p1v4pyu2pqodc0ey,Jacob French
+6npynnhjt2jd05xo,Jessica Costa
+wcxedf13n2e9qi4l,George Hardy
+yf2xlcmszk2tqeig,Andrea Allison
+3bf2zzv7poststwa,Kevin Ferguson
+c2iataz0hhv39q63,Joseph Johnson
+3e8npxhov4a39pvq,Ashley Martinez
+t7dp41tysipytywq,Charles Nixon
+z8cztq7c47phyfhk,Carol Dudley
+2636f9d8r4ipm3h6,David Weber
+eh3f6wxtvkjq6ykq,Scott Robinson
+raskbwpsje69a59h,Anthony Hardy
+90hn1p0b4cs9e2og,Mackenzie Owens
+am3swwfbo076x0v1,Brian Foster
+5uw7utb9lq5cfncw,Hannah Forbes
+cs6mbfzkzifefx6r,Lauren Reed
+ftw3uvztziiz9x00,Morgan Smith
+uhrqseeo43mozpaq,Samantha Alexander
+pvvmzyfc1lxor11e,Tiffany Roberts
+jia7bdag4abz123s,Emily Hayes
+h6oozcngbz8o5x4y,Rebecca Villegas
+9v6z1pn2f9twcy12,Donald Shah
+wzz3jduioso77o7f,Denise Cain
+u51plhgvjodkswnr,Kristine Ramirez
+t1uhkmiytfyc13vc,Stacey Adkins
+iqaqnf0ybg2ct507,Daniel Hunt
+idwrwv2uu4hcpv2i,Roberta Johnson
+2yd2hd6auetjacyo,Jason Williamson
+egrmdbibnjhi914x,Sandra Robinson
+15m1pz2bb0ercgyk,Steve Rice
+0i21bhkxdagjurb7,Kimberly Fritz
+726ofi7h5snreq67,Brianna Reynolds
+csqxse3wym56eim6,Alexander Williams
+qeaoylnrsf8p3byg,Andrew Thomas
+edsswobumzyzbvhf,Austin Williams
+hdzhzpt0ahy5hkib,Nicholas Williams
+w1qmvmg4roa8xnwu,Mrs. Michelle Cisneros
+3z3o73x7adyuo6w0,Stacey Smith
+sse2u5zlgoqrgmcf,Laura Beck
+rvovijmvch58r4yx,Molly Clark
+doe06nrx8sg5mcuv,Carmen Morris
+jbjdwuvj5s4kw04y,Amanda Munoz
+6k2ewkla7js0yw23,Rachel Collins
+fcxuyr4kkhrnigu1,John Alexander
+d25fuwlos5mk07o0,Stacy Hunter
+1vdai2rxmwd57oet,Eric Massey
+pq4jnt9izu1wlrzd,Scott Garcia
+lz9kfc0lty5xcz14,Cassandra Nelson
+pu7w6tyab5jd4we9,Aaron Johnson
+8dupswd2kqwdyn8v,Shannon Sherman
+ye466l71jthiz2p6,April Garcia
+xogsmfwb73l16qdt,Evan Lynn
diff --git a/tests/resources/csv/missing-row.csv b/tests/resources/csv/missing-row.csv
new file mode 100644
index 0000000000..7399fa9f51
--- /dev/null
+++ b/tests/resources/csv/missing-row.csv
@@ -0,0 +1,101 @@
+$id,name,age
+hxfcwpcas5xokpwe,Diamond Mendez
+gw8nxwf6esn3tfwf,Michael Huff
+xb6bxg56lral1qy9,Alyssa Rodriguez
+imerjq5j36y3agh2,Barbara Smith
+07yq9qdlhmbzmr35,Evelyn Edwards
+ksqo631sbhwj5ltg,Tina Richardson
+j7zlndgu0gbshp15,Joel Hernandez
+mfntvnljrcmf7h6v,Zachary Cooper
+5f9b01nziqu2h8ed,Brittany Spears
+4vxzbnzraqznk5u8,Holly White
+d4ywy3mtphaatbpf,Kimberly Barnes
+88odnk6nthyyvbal,Stephen Miller
+08oekee3fn7mzaa5,Yvonne Newman
+quw55kn9895i5e4v,Carol Kane
+nge6bm8ykripei6f,Doris Foster
+4k16i33s0xl2ypx9,Joseph Stokes
+q0j5rxbgid66snyf,Steve Williams
+n1oxun7mqq3p103y,James Carey
+0dbvs840jkf8i0ye,Kathryn Henry
+5sfaidgs1h87v15v,Christopher Landry
+vg3punvfu5khmf41,Jennifer Mcgee
+f933qydr9u5b2r11,Cathy Church
+wjv87y1inf8yk32s,Jose Lopez
+uljysdvdlcyrbrwk,William Rose
+ot8xtzh77j55wq0s,Sarah Ford
+9t76vnsv2u36s43t,Alisha Jones
+66y4tnty62hw8c02,Kristin Kelly
+2punfblazi5v16ar,Brendan Stout
+sxhr4nf5w2gx4wbg,Kelly Cruz
+68dvrqfwqnkq5el9,Samantha Martin
+20192l6dbeinhkh0,David Santos
+si0l4dgay09ebfmf,Elizabeth Carroll
+lhse40vbldqb6ap1,Corey Owens
+h5t3pslykyx3kxfm,Shelby Mueller
+ldc0luydrw6jub0f,Dr. Sylvia Myers
+voc9628xg4dsgw2y,Scott Freeman
+o4y0gk3gqv1ax2fz,Christopher Atkinson
+u1n3x4e4u7e0vzj6,Sean Diaz
+s36eskwtm0w7lwr7,Bobby Dyer
+4hjnag1p5iwvtixd,Daniel Hall
+m91d80oxsa216zbh,Jennifer Ramirez
+5hj6858zo2g85n6v,Angela Jackson
+8m8oihv9a1e7nn92,Kelly Lewis
+7azy39la0no0mxi7,Jessica Munoz
+47pmjkhnnqhyit8c,Kelly George
+6j6cpy4kgneg1mmh,Anthony Johnson
+tnlmtvap1zz89km9,Regina Fields
+6cyuvnwwqdmrpfzh,Sharon Schaefer
+p1v4pyu2pqodc0ey,Jacob French
+6npynnhjt2jd05xo,Jessica Costa
+wcxedf13n2e9qi4l,George Hardy
+yf2xlcmszk2tqeig,Andrea Allison
+3bf2zzv7poststwa,Kevin Ferguson
+c2iataz0hhv39q63,Joseph Johnson
+3e8npxhov4a39pvq,Ashley Martinez
+t7dp41tysipytywq,Charles Nixon
+z8cztq7c47phyfhk,Carol Dudley
+2636f9d8r4ipm3h6,David Weber
+eh3f6wxtvkjq6ykq,Scott Robinson
+raskbwpsje69a59h,Anthony Hardy
+90hn1p0b4cs9e2og,Mackenzie Owens
+am3swwfbo076x0v1,Brian Foster
+5uw7utb9lq5cfncw,Hannah Forbes
+cs6mbfzkzifefx6r,Lauren Reed
+ftw3uvztziiz9x00,Morgan Smith
+uhrqseeo43mozpaq,Samantha Alexander
+pvvmzyfc1lxor11e,Tiffany Roberts
+jia7bdag4abz123s,Emily Hayes
+h6oozcngbz8o5x4y,Rebecca Villegas
+9v6z1pn2f9twcy12,Donald Shah
+wzz3jduioso77o7f,Denise Cain
+u51plhgvjodkswnr,Kristine Ramirez
+t1uhkmiytfyc13vc,Stacey Adkins
+iqaqnf0ybg2ct507,Daniel Hunt
+idwrwv2uu4hcpv2i,Roberta Johnson
+2yd2hd6auetjacyo,Jason Williamson
+egrmdbibnjhi914x,Sandra Robinson
+15m1pz2bb0ercgyk,Steve Rice
+0i21bhkxdagjurb7,Kimberly Fritz
+726ofi7h5snreq67,Brianna Reynolds
+csqxse3wym56eim6,Alexander Williams
+qeaoylnrsf8p3byg,Andrew Thomas
+edsswobumzyzbvhf,Austin Williams
+hdzhzpt0ahy5hkib,Nicholas Williams
+w1qmvmg4roa8xnwu,Mrs. Michelle Cisneros
+3z3o73x7adyuo6w0,Stacey Smith
+sse2u5zlgoqrgmcf,Laura Beck
+rvovijmvch58r4yx,Molly Clark
+doe06nrx8sg5mcuv,Carmen Morris
+jbjdwuvj5s4kw04y,Amanda Munoz
+6k2ewkla7js0yw23,Rachel Collins
+fcxuyr4kkhrnigu1,John Alexander
+d25fuwlos5mk07o0,Stacy Hunter
+1vdai2rxmwd57oet,Eric Massey
+pq4jnt9izu1wlrzd,Scott Garcia
+lz9kfc0lty5xcz14,Cassandra Nelson
+pu7w6tyab5jd4we9,Aaron Johnson
+8dupswd2kqwdyn8v,Shannon Sherman
+ye466l71jthiz2p6,April Garcia
+xogsmfwb73l16qdt,Evan Lynn