diff --git a/.env b/.env
index f77083a035..cab8336e30 100644
--- a/.env
+++ b/.env
@@ -4,12 +4,13 @@ _APP_WORKER_PER_CORE=6
_APP_CONSOLE_WHITELIST_ROOT=disabled
_APP_CONSOLE_WHITELIST_EMAILS=
_APP_CONSOLE_WHITELIST_IPS=
+_APP_CONSOLE_HOSTNAMES=localhost,appwrite.io,*.appwrite.io
_APP_SYSTEM_EMAIL_NAME=Appwrite
_APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=security@appwrite.io
_APP_SYSTEM_RESPONSE_FORMAT=
_APP_OPTIONS_ABUSE=disabled
-_APP_OPTIONS_ROUTER_PROTECTION=disbled
+_APP_OPTIONS_ROUTER_PROTECTION=disabled
_APP_OPTIONS_FORCE_HTTPS=disabled
_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS=disabled
_APP_OPENSSL_KEY_V1=your-secret-key
@@ -50,10 +51,6 @@ _APP_STORAGE_WASABI_BUCKET=
_APP_STORAGE_ANTIVIRUS=disabled
_APP_STORAGE_ANTIVIRUS_HOST=clamav
_APP_STORAGE_ANTIVIRUS_PORT=3310
-_APP_INFLUXDB_HOST=influxdb
-_APP_INFLUXDB_PORT=8086
-_APP_STATSD_HOST=telegraf
-_APP_STATSD_PORT=8125
_APP_SMTP_HOST=maildev
_APP_SMTP_PORT=1025
_APP_SMTP_SECURE=
@@ -79,7 +76,7 @@ _APP_MAINTENANCE_RETENTION_CACHE=2592000
_APP_MAINTENANCE_RETENTION_EXECUTION=1209600
_APP_MAINTENANCE_RETENTION_ABUSE=86400
_APP_MAINTENANCE_RETENTION_AUDIT=1209600
-_APP_USAGE_AGGREGATION_INTERVAL=5
+_APP_USAGE_AGGREGATION_INTERVAL=60000
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
_APP_MAINTENANCE_RETENTION_SCHEDULES=86400
_APP_USAGE_STATS=enabled
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 14e1ac5e44..9c9b678302 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -75,8 +75,32 @@ jobs:
- name: Run Unit Tests
run: docker compose exec appwrite test /usr/src/code/tests/unit
- e2e_test:
- name: E2E Test
+ e2e_general_test:
+ name: E2E General Test
+ runs-on: ubuntu-latest
+ needs: setup
+ steps:
+ - name: checkout
+ uses: actions/checkout@v3
+
+ - name: Load Cache
+ uses: actions/cache@v3
+ with:
+ key: ${{ env.CACHE_KEY }}
+ path: /tmp/${{ env.IMAGE }}.tar
+ fail-on-cache-miss: true
+
+ - name: Load and Start Appwrite
+ run: |
+ docker load --input /tmp/${{ env.IMAGE }}.tar
+ docker compose up -d
+ sleep 10
+
+ - name: Run General Tests
+ run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/General --debug
+
+ e2e_service_test:
+ name: E2E Service Test
runs-on: ubuntu-latest
needs: setup
strategy:
@@ -120,4 +144,4 @@ jobs:
sleep 10
- name: Run ${{matrix.service}} Tests
- run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug
\ No newline at end of file
+ run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug
diff --git a/CHANGES.md b/CHANGES.md
index 5634340d69..5dd4ba8770 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,14 @@
+# Version 1.4.14
+
+## Changes
+- New usage metrics collection flow [#7005](https://github.com/appwrite/appwrite/pull/7005)
+ - Deprecated influxdb, telegraf containers and removed all of their occurrences from the code.
+ - Removed _APP_INFLUXDB_HOST, _APP_INFLUXDB_PORT, _APP_STATSD_HOST, _APP_STATSD_PORT env variables.
+ - Removed usage labels dependency.
+ - Dropped type attribute from stats collection.
+ - Usage metrics are processed via new usage worker.
+ - updated Metric names.
+
# Version 1.4.13
## Notable changes
@@ -49,6 +60,7 @@
* Use getQueueSize() in the Health service's get X queue endpoints [#7073](https://github.com/appwrite/appwrite/pull/7073)
* Delete linked VCS repos and comments [#7066](https://github.com/appwrite/appwrite/pull/7066)
+
# Version 1.4.9
## Bug fixes
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ccd61e742b..0522e33da5 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -210,7 +210,6 @@ Appwrite's current structure is a combination of both [Monolithic](https://en.wi
│ ├── Task
│ ├── Template
│ ├── URL
-│ ├── Usage
│ └── Utopia
└── tests # End to end & unit tests
├── e2e
diff --git a/Dockerfile b/Dockerfile
index 2f85f2cc43..7c37d4a4f1 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -71,43 +71,56 @@ RUN mkdir -p /storage/uploads && \
chown -Rf www-data.www-data /storage/functions && chmod -Rf 0755 /storage/functions && \
chown -Rf www-data.www-data /storage/debug && chmod -Rf 0755 /storage/debug
+# Development Executables
+RUN chmod +x /usr/local/bin/dev-generate-translations
+
# Executables
RUN chmod +x /usr/local/bin/doctor && \
- chmod +x /usr/local/bin/maintenance && \
- chmod +x /usr/local/bin/usage && \
chmod +x /usr/local/bin/install && \
- chmod +x /usr/local/bin/upgrade && \
+ chmod +x /usr/local/bin/maintenance && \
chmod +x /usr/local/bin/migrate && \
chmod +x /usr/local/bin/realtime && \
- chmod +x /usr/local/bin/schedule && \
+ chmod +x /usr/local/bin/schedule-functions && \
+ chmod +x /usr/local/bin/schedule-messages && \
chmod +x /usr/local/bin/sdks && \
chmod +x /usr/local/bin/specs && \
chmod +x /usr/local/bin/ssl && \
chmod +x /usr/local/bin/test && \
+ chmod +x /usr/local/bin/upgrade && \
chmod +x /usr/local/bin/vars && \
chmod +x /usr/local/bin/worker-audits && \
+ chmod +x /usr/local/bin/worker-builds && \
chmod +x /usr/local/bin/worker-certificates && \
chmod +x /usr/local/bin/worker-databases && \
chmod +x /usr/local/bin/worker-deletes && \
chmod +x /usr/local/bin/worker-functions && \
- chmod +x /usr/local/bin/worker-builds && \
+ chmod +x /usr/local/bin/worker-hamster && \
chmod +x /usr/local/bin/worker-mails && \
chmod +x /usr/local/bin/worker-messaging && \
- chmod +x /usr/local/bin/worker-webhooks && \
chmod +x /usr/local/bin/worker-migrations && \
- chmod +x /usr/local/bin/worker-hamster
+ chmod +x /usr/local/bin/worker-webhooks && \
+ chmod +x /usr/local/bin/worker-hamster && \
+ chmod +x /usr/local/bin/worker-usage
+
# Cloud Executabless
-RUN chmod +x /usr/local/bin/hamster && \
- chmod +x /usr/local/bin/volume-sync && \
+RUN chmod +x /usr/local/bin/calc-tier-stats && \
+ chmod +x /usr/local/bin/calc-users-stats && \
+ chmod +x /usr/local/bin/clear-card-cache && \
+ chmod +x /usr/local/bin/delete-orphaned-projects && \
+ chmod +x /usr/local/bin/get-migration-stats && \
+ chmod +x /usr/local/bin/hamster && \
+ chmod +x /usr/local/bin/patch-delete-project-collections && \
chmod +x /usr/local/bin/patch-delete-schedule-updated-at-attribute && \
chmod +x /usr/local/bin/patch-recreate-repositories-documents && \
+ chmod +x /usr/local/bin/volume-sync && \
chmod +x /usr/local/bin/patch-delete-project-collections && \
chmod +x /usr/local/bin/delete-orphaned-projects && \
chmod +x /usr/local/bin/clear-card-cache && \
chmod +x /usr/local/bin/calc-users-stats && \
chmod +x /usr/local/bin/calc-tier-stats && \
- chmod +x /usr/local/bin/get-migration-stats
+ chmod +x /usr/local/bin/get-migration-stats && \
+ chmod +x /usr/local/bin/create-inf-metric
# Letsencrypt Permissions
RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
diff --git a/app/cli.php b/app/cli.php
index e4ecf17f3c..668b70d8b7 100644
--- a/app/cli.php
+++ b/app/cli.php
@@ -125,30 +125,6 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
};
}, ['pools', 'dbForConsole', 'cache']);
-CLI::setResource('influxdb', function (Registry $register) {
- $client = $register->get('influxdb'); /** @var InfluxDB\Client $client */
- $attempts = 0;
- $max = 10;
- $sleep = 1;
-
- do { // check if telegraf database is ready
- try {
- $attempts++;
- $database = $client->selectDB('telegraf');
- if (in_array('telegraf', $client->listDatabases())) {
- break; // leave the do-while if successful
- }
- } catch (\Throwable $th) {
- Console::warning("InfluxDB not ready. Retrying connection ({$attempts})...");
- if ($attempts >= $max) {
- throw new \Exception('InfluxDB database not ready yet');
- }
- sleep($sleep);
- }
- } while ($attempts < $max);
- return $database;
-}, ['register']);
-
CLI::setResource('queue', function (Group $pools) {
return $pools->get('queue')->pop()->getResource();
}, ['pools']);
diff --git a/app/config/auth.php b/app/config/auth.php
index cf1d180aaa..2330fe75cf 100644
--- a/app/config/auth.php
+++ b/app/config/auth.php
@@ -7,21 +7,28 @@ return [
'name' => 'Email/Password',
'key' => 'emailPassword',
'icon' => '/images/users/email.png',
- 'docs' => 'https://appwrite.io/docs/client/account?sdk=web-default#accountCreateEmailSession',
+ 'docs' => 'https://appwrite.io/docs/references/cloud/client-web/account#accountCreateEmailPasswordSession',
'enabled' => true,
],
'magic-url' => [
'name' => 'Magic URL',
'key' => 'usersAuthMagicURL',
'icon' => '/images/users/magic-url.png',
- 'docs' => 'https://appwrite.io/docs/client/account?sdk=web-default#accountCreateMagicURLSession',
+ 'docs' => 'https://appwrite.io/docs/references/cloud/client-web/account#accountCreateMagicURLToken',
+ 'enabled' => true,
+ ],
+ 'email-otp' => [
+ 'name' => 'Email (OTP)',
+ 'key' => 'emailOtp',
+ 'icon' => '/images/users/email.png',
+ 'docs' => 'https://appwrite.io/docs/references/cloud/client-web/account#accountCreateEmailToken',
'enabled' => true,
],
'anonymous' => [
'name' => 'Anonymous',
'key' => 'anonymous',
'icon' => '/images/users/anonymous.png',
- 'docs' => 'https://appwrite.io/docs/client/account?sdk=web-default#accountCreateAnonymousSession',
+ 'docs' => 'https://appwrite.io/docs/references/cloud/client-web/account#accountCreateAnonymousSession',
'enabled' => true,
],
'invites' => [
@@ -42,7 +49,7 @@ return [
'name' => 'Phone',
'key' => 'phone',
'icon' => '/images/users/phone.png',
- 'docs' => 'https://appwrite.io/docs/client/account?sdk=web-default#accountCreatePhoneSession',
+ 'docs' => 'https://appwrite.io/docs/references/cloud/client-web/account#accountCreatePhoneToken',
'enabled' => true,
],
];
diff --git a/app/config/collections.php b/app/config/collections.php
index b3e3417555..43954d667c 100644
--- a/app/config/collections.php
+++ b/app/config/collections.php
@@ -18,6 +18,63 @@ $auth = Config::getParam('auth', []);
*/
$commonCollections = [
+ 'cache' => [
+ '$collection' => Database::METADATA,
+ '$id' => 'cache',
+ 'name' => 'Cache',
+ 'attributes' => [
+ [
+ '$id' => 'resource',
+ 'type' => Database::VAR_STRING,
+ 'format' => '',
+ 'size' => 255,
+ 'signed' => true,
+ 'required' => false,
+ 'default' => null,
+ 'array' => false,
+ 'filters' => [],
+ ],
+ [
+ '$id' => 'accessedAt',
+ 'type' => Database::VAR_DATETIME,
+ 'format' => '',
+ 'size' => 0,
+ 'signed' => false,
+ 'required' => false,
+ 'default' => null,
+ 'array' => false,
+ 'filters' => ['datetime'],
+ ],
+ [
+ '$id' => 'signature',
+ 'type' => Database::VAR_STRING,
+ 'format' => '',
+ 'size' => 255,
+ 'signed' => true,
+ 'required' => false,
+ 'default' => null,
+ 'array' => false,
+ 'filters' => [],
+ ],
+ ],
+ 'indexes' => [
+ [
+ '$id' => '_key_accessedAt',
+ 'type' => Database::INDEX_KEY,
+ 'attributes' => ['accessedAt'],
+ 'lengths' => [],
+ 'orders' => [],
+ ],
+ [
+ '$id' => '_key_resource',
+ 'type' => Database::INDEX_KEY,
+ 'attributes' => ['resource'],
+ 'lengths' => [],
+ 'orders' => [],
+ ],
+ ],
+ ],
+
'users' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('users'),
@@ -681,6 +738,17 @@ $commonCollections = [
'array' => false,
'filters' => [],
],
+ [
+ '$id' => ID::custom('expire'),
+ 'type' => Database::VAR_DATETIME,
+ 'format' => '',
+ 'size' => 0,
+ 'signed' => false,
+ 'required' => true,
+ 'default' => null,
+ 'array' => false,
+ 'filters' => ['datetime'],
+ ],
],
'indexes' => [
[
@@ -1281,10 +1349,10 @@ $commonCollections = [
]
],
- 'stats' => [
+ 'stats_v2' => [
'$collection' => ID::custom(Database::METADATA),
- '$id' => ID::custom('stats'),
- 'name' => 'Stats',
+ '$id' => ID::custom('stats_v2'),
+ 'name' => 'stats_v2',
'attributes' => [
[
'$id' => ID::custom('metric'),
@@ -1313,7 +1381,7 @@ $commonCollections = [
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 8,
- 'signed' => false,
+ 'signed' => true,
'required' => true,
'default' => null,
'array' => false,
@@ -1341,17 +1409,6 @@ $commonCollections = [
'array' => false,
'filters' => [],
],
- [
- '$id' => ID::custom('type'),
- 'type' => Database::VAR_INTEGER,
- 'format' => '',
- 'size' => 1,
- 'signed' => false,
- 'required' => true,
- 'default' => 0, // 0 -> count, 1 -> sum
- 'array' => false,
- 'filters' => [],
- ],
],
'indexes' => [
[
@@ -1370,7 +1427,7 @@ $commonCollections = [
],
[
'$id' => ID::custom('_key_metric_period_time'),
- 'type' => Database::INDEX_KEY,
+ 'type' => Database::INDEX_UNIQUE,
'attributes' => ['metric', 'period', 'time'],
'lengths' => [],
'orders' => [Database::ORDER_DESC],
@@ -1486,7 +1543,7 @@ $commonCollections = [
[
'$id' => ID::custom('_key_enabled_type'),
'type' => Database::INDEX_KEY,
- 'attributes' => ['enabled','type'],
+ 'attributes' => ['enabled', 'type'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
@@ -1593,6 +1650,28 @@ $commonCollections = [
'array' => false,
'filters' => ['datetime'],
],
+ [
+ '$id' => ID::custom('scheduleInternalId'),
+ 'type' => Database::VAR_STRING,
+ 'format' => '',
+ 'size' => Database::LENGTH_KEY,
+ 'signed' => true,
+ 'required' => false,
+ 'default' => null,
+ 'array' => false,
+ 'filters' => [],
+ ],
+ [
+ '$id' => ID::custom('scheduleId'),
+ 'type' => Database::VAR_STRING,
+ 'format' => '',
+ 'size' => Database::LENGTH_KEY,
+ 'signed' => true,
+ 'required' => false,
+ 'default' => null,
+ 'array' => false,
+ 'filters' => [],
+ ],
[
'$id' => ID::custom('deliveredAt'),
'type' => Database::VAR_DATETIME,
@@ -1810,6 +1889,17 @@ $commonCollections = [
'array' => false,
'filters' => [],
],
+ [
+ '$id' => ID::custom('search'),
+ 'type' => Database::VAR_STRING,
+ 'format' => '',
+ 'size' => 16384,
+ 'signed' => true,
+ 'required' => false,
+ 'default' => null,
+ 'array' => false,
+ 'filters' => [],
+ ],
],
'indexes' => [
[
@@ -1853,7 +1943,14 @@ $commonCollections = [
'attributes' => ['topicInternalId'],
'lengths' => [],
'orders' => [],
- ]
+ ],
+ [
+ '$id' => ID::custom('_fulltext_search'),
+ 'type' => Database::INDEX_FULLTEXT,
+ 'attributes' => ['search'],
+ 'lengths' => [],
+ 'orders' => [],
+ ],
],
],
@@ -3495,63 +3592,6 @@ $projectCollections = array_merge([
],
],
- 'cache' => [
- '$collection' => Database::METADATA,
- '$id' => 'cache',
- 'name' => 'Cache',
- 'attributes' => [
- [
- '$id' => 'resource',
- 'type' => Database::VAR_STRING,
- 'format' => '',
- 'size' => 255,
- 'signed' => true,
- 'required' => false,
- 'default' => null,
- 'array' => false,
- 'filters' => [],
- ],
- [
- '$id' => 'accessedAt',
- 'type' => Database::VAR_DATETIME,
- 'format' => '',
- 'size' => 0,
- 'signed' => false,
- 'required' => false,
- 'default' => null,
- 'array' => false,
- 'filters' => ['datetime'],
- ],
- [
- '$id' => 'signature',
- 'type' => Database::VAR_STRING,
- 'format' => '',
- 'size' => 255,
- 'signed' => true,
- 'required' => false,
- 'default' => null,
- 'array' => false,
- 'filters' => [],
- ],
- ],
- 'indexes' => [
- [
- '$id' => '_key_accessedAt',
- 'type' => Database::INDEX_KEY,
- 'attributes' => ['accessedAt'],
- 'lengths' => [],
- 'orders' => [],
- ],
- [
- '$id' => '_key_resource',
- 'type' => Database::INDEX_KEY,
- 'attributes' => ['resource'],
- 'lengths' => [],
- 'orders' => [],
- ],
- ],
- ],
-
'variables' => [
'$collection' => Database::METADATA,
'$id' => 'variables',
@@ -4120,6 +4160,17 @@ $consoleCollections = array_merge([
'array' => false,
'filters' => [],
],
+ [
+ '$id' => ID::custom('resourceCollection'),
+ 'type' => Database::VAR_STRING,
+ 'format' => '',
+ 'size' => Database::LENGTH_KEY,
+ 'signed' => true,
+ 'required' => true,
+ 'default' => null,
+ 'array' => false,
+ 'filters' => [],
+ ],
[
'$id' => ID::custom('resourceInternalId'),
'type' => Database::VAR_STRING,
@@ -4526,6 +4577,39 @@ $consoleCollections = array_merge([
'array' => false,
'filters' => [],
],
+ [
+ '$id' => ID::custom('enabled'),
+ 'type' => Database::VAR_BOOLEAN,
+ 'signed' => true,
+ 'size' => 0,
+ 'format' => '',
+ 'filters' => [],
+ 'required' => false,
+ 'default' => true,
+ 'array' => false,
+ ],
+ [
+ '$id' => ID::custom('logs'),
+ 'type' => Database::VAR_STRING,
+ 'format' => '',
+ 'size' => 1000000,
+ 'signed' => true,
+ 'required' => false,
+ 'default' => '',
+ 'array' => false,
+ 'filters' => [],
+ ],
+ [
+ '$id' => ID::custom('attempts'),
+ 'type' => Database::VAR_INTEGER,
+ 'format' => '',
+ 'size' => 0,
+ 'signed' => true,
+ 'required' => false,
+ 'default' => 0,
+ 'array' => false,
+ 'filters' => [],
+ ],
],
'indexes' => [
[
diff --git a/app/config/errors.php b/app/config/errors.php
index 2e35bfb881..e5c2d4e5bd 100644
--- a/app/config/errors.php
+++ b/app/config/errors.php
@@ -4,6 +4,7 @@
* List of server wide error codes and their respective messages.
*/
+use Appwrite\Enum\MessageStatus;
use Appwrite\Extend\Exception;
return [
@@ -807,7 +808,7 @@ return [
],
Exception::PROVIDER_INCORRECT_TYPE => [
'name' => Exception::PROVIDER_INCORRECT_TYPE,
- 'description' => 'Provider with the requested ID is of incorrect type: ',
+ 'description' => 'Provider with the requested ID is of the incorrect type.',
'code' => 400,
],
@@ -858,18 +859,27 @@ return [
],
Exception::MESSAGE_TARGET_NOT_EMAIL => [
'name' => Exception::MESSAGE_TARGET_NOT_EMAIL,
- 'description' => 'Message with the target ID is not an email target:',
+ 'description' => 'Message with the target ID is not an email target.',
'code' => 400,
],
Exception::MESSAGE_TARGET_NOT_SMS => [
'name' => Exception::MESSAGE_TARGET_NOT_SMS,
- 'description' => 'Message with the target ID is not an SMS target:',
+ 'description' => 'Message with the target ID is not an SMS target.',
'code' => 400,
],
Exception::MESSAGE_TARGET_NOT_PUSH => [
'name' => Exception::MESSAGE_TARGET_NOT_PUSH,
- 'description' => 'Message with the target ID is not a push target:',
+ 'description' => 'Message with the target ID is not a push target.',
'code' => 400,
],
-
+ Exception::MESSAGE_MISSING_SCHEDULE => [
+ 'name' => Exception::MESSAGE_MISSING_SCHEDULE,
+ 'description' => 'Message can not have status ' . MessageStatus::SCHEDULED . ' without a schedule.',
+ 'code' => 400,
+ ],
+ Exception::SCHEDULE_NOT_FOUND => [
+ 'name' => Exception::SCHEDULE_NOT_FOUND,
+ 'description' => 'Schedule with the requested ID could not be found.',
+ 'code' => 404,
+ ],
];
diff --git a/app/config/events.php b/app/config/events.php
index b0db9090fb..5378502faf 100644
--- a/app/config/events.php
+++ b/app/config/events.php
@@ -58,6 +58,14 @@ return [
'$description' => 'This event triggers when a user\'s target is deleted.',
],
],
+ 'tokens' => [
+ '$model' => Response::MODEL_TOKEN,
+ '$resource' => true,
+ '$description' => 'This event triggers on any user\'s token event.',
+ 'create' => [
+ '$description' => 'This event triggers when a user\'s token is created.',
+ ],
+ ],
'create' => [
'$description' => 'This event triggers when a user is created.'
],
diff --git a/app/config/locale/languages.php b/app/config/locale/languages.php
index 6272bd02a6..eeea92e636 100644
--- a/app/config/locale/languages.php
+++ b/app/config/locale/languages.php
@@ -654,10 +654,15 @@ return [
"nativeName" => "پښتو"
],
[
- "code" => "pt",
+ "code" => "pt-pt",
"name" => "Portuguese",
"nativeName" => "Português"
],
+ [
+ "code" => "pt-br",
+ "name" => "Portuguese (Brazilian)",
+ "nativeName" => "Português"
+ ],
[
"code" => "qu",
"name" => "Quechua",
@@ -919,9 +924,14 @@ return [
"nativeName" => "Cuengh / Tôô / 壮语"
],
[
- "code" => "zh",
- "name" => "Chinese",
- "nativeName" => "中文"
+ "code" => "zh-cn",
+ "name" => "Chinese (Simplified)",
+ "nativeName" => "中国人"
+ ],
+ [
+ "code" => "zh-tw",
+ "name" => "Chinese (Traditional)",
+ "nativeName" => "中國人"
],
[
"code" => "zu",
diff --git a/app/config/locale/templates/email-base-styled.tpl b/app/config/locale/templates/email-base-styled.tpl
new file mode 100644
index 0000000000..4d6972389e
--- /dev/null
+++ b/app/config/locale/templates/email-base-styled.tpl
@@ -0,0 +1,207 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+ © {{year}} Appwrite | 251 Little Falls Drive, Wilmington 19808,
+ Delaware, United States
+
+
+
+
\ No newline at end of file
diff --git a/app/config/locale/templates/email-base.tpl b/app/config/locale/templates/email-base.tpl
index f41a9530e1..13056fd5ae 100644
--- a/app/config/locale/templates/email-base.tpl
+++ b/app/config/locale/templates/email-base.tpl
@@ -1,5 +1,74 @@
+
+
+
+
+
+
@@ -8,13 +77,14 @@
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Poppins:wght@500;600&display=swap"
rel="stylesheet">