Merge conflicts in CHANGES.md

This commit is contained in:
Khushboo Verma
2024-05-24 13:03:41 +05:30
109 changed files with 287 additions and 2663 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
[submodule "app/console"]
path = app/console
url = https://github.com/appwrite/console
branch = 4.3.0
branch = 4.3.2
+32 -1
View File
@@ -1,9 +1,40 @@
# Version 1.5.7
## What's Changed
## Fixes
### Notable Changes
* Prevent functions domain to be used as custom domain in [#7934](https://github.com/appwrite/appwrite/pull/7934)
### Fixes
* Fix auth mode check in [#7980](https://github.com/appwrite/appwrite/pull/7980)
* Fix templates not copying hidden files in [#7610](https://github.com/appwrite/appwrite/pull/7610)
* Use `resourceInternalId` for Querying Function Deployments in [#8038](https://github.com/appwrite/appwrite/pull/8038)
* Fix Email OTP not verifying account in [#8084](https://github.com/appwrite/appwrite/pull/8084)
* Fix MFA email verification code font in [#8082](https://github.com/appwrite/appwrite/pull/8082)
* Don't kick user and require verification after enabling MFA in [#8081](https://github.com/appwrite/appwrite/pull/8081)
* Fix typo in credit-cards.php credit card image filename in [#8074](https://github.com/appwrite/appwrite/pull/8074)
* Fix Deprecated Warning in Doctor.php in [#8105](https://github.com/appwrite/appwrite/pull/8105)
* Set limit to retrieve all stats for the usage range in [#8117](https://github.com/appwrite/appwrite/pull/8117)
* Fix email used for name when user is created via Apple OAuth2 in [#8102](https://github.com/appwrite/appwrite/pull/8102)
* Change executor hostname back to exc1 to prevent name too long errors in the swoole table [#8147](https://github.com/appwrite/appwrite/pull/8147)
### Miscellaneous
* Add GitHub action to close stale issues in [#7927](https://github.com/appwrite/appwrite/pull/7927)
* Document the standard we follow for country codes in [#8014](https://github.com/appwrite/appwrite/pull/8014)
* Add OSV Scanner for vulnerability scans in [#6506](https://github.com/appwrite/appwrite/pull/6506)
* Fix stale action close reason in [#8046](https://github.com/appwrite/appwrite/pull/8046)
* Add OSV Scanner for vulnerability scans in [#8021](https://github.com/appwrite/appwrite/pull/8021)
* Fix some typos in comments in [#7993](https://github.com/appwrite/appwrite/pull/7993)
* Replace missing domain paths in README.md in [#8049](https://github.com/appwrite/appwrite/pull/8049)
* Add the React Native SDK in [#7776](https://github.com/appwrite/appwrite/pull/7776)
* Bump database in [#8080](https://github.com/appwrite/appwrite/pull/8080)
* Add documentation for metrics in [#8088](https://github.com/appwrite/appwrite/pull/8088)
* Add new country Palestine with its translations in [#8031](https://github.com/appwrite/appwrite/pull/8031)
* Update users create token description in [#8129](https://github.com/appwrite/appwrite/pull/8129)
* Bump dependencies in [#8130](https://github.com/appwrite/appwrite/pull/8130)
# Version 1.5.5
## What's Changed
### Notable changes
+10 -2
View File
@@ -148,6 +148,14 @@ Learn more at our [Technology Stack](#technology-stack) section.
- [Microservices vs Monolithic](https://www.mulesoft.com/resources/api/microservices-vs-monolithic#:~:text=Microservices%20architecture%20vs%20monolithic%20architecture&text=A%20monolithic%20application%20is%20built%20as%20a%20single%20unit.&text=To%20make%20any%20alterations%20to,formally%20with%20business%2Doriented%20APIs.)
- [MVVM](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel) - Appwrite console architecture
##### Container Namespace Conventions
To keep our services easy to understand within Docker we follow a naming convention for all our containers depending on it's intended use.
`appwrite-worker-X` - Workers (`src/Appwrite/Platform/Workers/*`)
`appwrite-task-X` - Tasks (`src/Appwrite/Platform/Tasks/*`)
Other containes should be named the same as their service, for example `redis` should just be called `redis`.
##### Security
- [Appwrite Auth and ACL](https://github.com/appwrite/appwrite/blob/master/docs/specs/authentication.drawio.svg)
@@ -481,9 +489,9 @@ Things to remember when releasing SDKs:
## Debug
Appwrite uses [XDebug](https://github.com/xdebug/xdebug) debugger, which can be made available during build of Appwrite. You can connect to the debugger using VS Code's [PHP Debug](https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug) extension.
Appwrite uses [XDebug](https://github.com/xdebug/xdebug) debugger, which can be made available during build of Appwrite. You can connect to the debugger using VS Code's [PHP Debug](https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug) extension.
If you are in PHP Storm you don't need any plugin. Below are the settings required for remote debugger connection:
If you are in PHP Storm you don't need any plugin. Below are the settings required for remote debugger connection:
1. Set **DEBUG** build arg in **appwrite** service in **docker-compose.yml** file.
2. If needed edit the **dev/xdebug.ini** file to your needs.
-25
View File
@@ -72,9 +72,6 @@ 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/install && \
@@ -99,35 +96,13 @@ RUN chmod +x /usr/local/bin/doctor && \
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-hamster && \
chmod +x /usr/local/bin/worker-mails && \
chmod +x /usr/local/bin/worker-messaging && \
chmod +x /usr/local/bin/worker-migrations && \
chmod +x /usr/local/bin/worker-webhooks && \
chmod +x /usr/local/bin/worker-hamster && \
chmod +x /usr/local/bin/worker-usage && \
chmod +x /usr/local/bin/worker-usage-dump
# Cloud Executabless
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/create-inf-metric
# Letsencrypt Permissions
RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
+3 -3
View File
@@ -67,7 +67,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.5.5
appwrite/appwrite:1.5.6
```
### Windows
@@ -79,7 +79,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.5.5
appwrite/appwrite:1.5.6
```
#### PowerShell
@@ -89,7 +89,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.5.5
appwrite/appwrite:1.5.6
```
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
+3 -3
View File
@@ -75,7 +75,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.5.5
appwrite/appwrite:1.5.6
```
### Windows
@@ -87,7 +87,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.5.5
appwrite/appwrite:1.5.6
```
#### PowerShell
@@ -97,7 +97,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.5.5
appwrite/appwrite:1.5.6
```
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.
-4
View File
@@ -6,7 +6,6 @@ require_once __DIR__ . '/controllers/general.php';
use Appwrite\Event\Certificate;
use Appwrite\Event\Delete;
use Appwrite\Event\Func;
use Appwrite\Event\Hamster;
use Appwrite\Platform\Appwrite;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
@@ -131,9 +130,6 @@ CLI::setResource('queue', function (Group $pools) {
CLI::setResource('queueForFunctions', function (Connection $queue) {
return new Func($queue);
}, ['queue']);
CLI::setResource('queueForHamster', function (Connection $queue) {
return new Hamster($queue);
}, ['queue']);
CLI::setResource('queueForDeletes', function (Connection $queue) {
return new Delete($queue);
}, ['queue']);
+5
View File
@@ -519,6 +519,11 @@ return [
'description' => 'Entrypoint for your Appwrite Function is missing. Please specify it when making deployment or update the entrypoint under your function\'s "Settings" > "Configuration" > "Entrypoint".',
'code' => 404,
],
Exception::FUNCTION_SYNCHRONOUS_TIMEOUT => [
'name' => Exception::FUNCTION_SYNCHRONOUS_TIMEOUT,
'description' => 'Synchronous function execution timed out. Use asynchronous execution instead, or ensure the execution duration doesn\'t exceed 30 seconds.',
'code' => 408,
],
/** Builds */
Exception::BUILD_NOT_FOUND => [
+2 -2
View File
@@ -926,12 +926,12 @@ return [
[
"code" => "zh-cn",
"name" => "Chinese (Simplified)",
"nativeName" => "国人"
"nativeName" => ""
],
[
"code" => "zh-tw",
"name" => "Chinese (Traditional)",
"nativeName" => "國人"
"nativeName" => ""
],
[
"code" => "zu",
+18 -18
View File
@@ -15,7 +15,7 @@ return [
[
'key' => 'web',
'name' => 'Web',
'version' => '14.0.0',
'version' => '14.0.2',
'url' => 'https://github.com/appwrite/sdk-for-web',
'package' => 'https://www.npmjs.com/package/appwrite',
'enabled' => true,
@@ -63,7 +63,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '12.0.3',
'version' => '12.0.4',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@@ -81,7 +81,7 @@ return [
[
'key' => 'apple',
'name' => 'Apple',
'version' => '5.0.0',
'version' => '6.0.0',
'url' => 'https://github.com/appwrite/sdk-for-apple',
'package' => 'https://github.com/appwrite/sdk-for-apple',
'enabled' => true,
@@ -116,7 +116,7 @@ return [
[
'key' => 'android',
'name' => 'Android',
'version' => '5.0.0',
'version' => '5.1.1',
'url' => 'https://github.com/appwrite/sdk-for-android',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
'enabled' => true,
@@ -138,7 +138,7 @@ return [
[
'key' => 'react-native',
'name' => 'React Native',
'version' => '0.3.0',
'version' => '0.3.2',
'url' => 'https://github.com/appwrite/sdk-for-react-native',
'package' => 'https://npmjs.com/package/react-native-appwrite',
'enabled' => true,
@@ -203,7 +203,7 @@ return [
[
'key' => 'web',
'name' => 'Console',
'version' => '0.6.1',
'version' => '0.6.3',
'url' => 'https://github.com/appwrite/sdk-for-console',
'package' => '',
'enabled' => true,
@@ -221,7 +221,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '5.0.2',
'version' => '5.0.5',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
@@ -249,7 +249,7 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
'version' => '12.0.1',
'version' => '13.0.0',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,
@@ -267,7 +267,7 @@ return [
[
'key' => 'deno',
'name' => 'Deno',
'version' => '10.0.1',
'version' => '10.0.2',
'url' => 'https://github.com/appwrite/sdk-for-deno',
'package' => 'https://deno.land/x/appwrite',
'enabled' => true,
@@ -285,7 +285,7 @@ return [
[
'key' => 'php',
'name' => 'PHP',
'version' => '11.0.1',
'version' => '11.0.2',
'url' => 'https://github.com/appwrite/sdk-for-php',
'package' => 'https://packagist.org/packages/appwrite/appwrite',
'enabled' => true,
@@ -303,7 +303,7 @@ return [
[
'key' => 'python',
'name' => 'Python',
'version' => '5.0.2',
'version' => '5.0.3',
'url' => 'https://github.com/appwrite/sdk-for-python',
'package' => 'https://pypi.org/project/appwrite/',
'enabled' => true,
@@ -321,7 +321,7 @@ return [
[
'key' => 'ruby',
'name' => 'Ruby',
'version' => '11.0.1',
'version' => '11.0.2',
'url' => 'https://github.com/appwrite/sdk-for-ruby',
'package' => 'https://rubygems.org/gems/appwrite',
'enabled' => true,
@@ -339,7 +339,7 @@ return [
[
'key' => 'go',
'name' => 'Go',
'version' => '4.0.0',
'version' => '4.0.1',
'url' => 'https://github.com/appwrite/sdk-for-go',
'package' => '',
'enabled' => false,
@@ -357,7 +357,7 @@ return [
[
'key' => 'java',
'name' => 'Java',
'version' => '4.0.1',
'version' => '4.0.2',
'url' => 'https://github.com/appwrite/sdk-for-java',
'package' => '',
'enabled' => false,
@@ -375,7 +375,7 @@ return [
[
'key' => 'dotnet',
'name' => '.NET',
'version' => '0.8.1',
'version' => '0.8.2',
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
'package' => 'https://www.nuget.org/packages/Appwrite',
'enabled' => true,
@@ -393,7 +393,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '11.0.2',
'version' => '11.0.3',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
@@ -411,7 +411,7 @@ return [
[
'key' => 'kotlin',
'name' => 'Kotlin',
'version' => '5.0.1',
'version' => '5.0.2',
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
'enabled' => true,
@@ -433,7 +433,7 @@ return [
[
'key' => 'swift',
'name' => 'Swift',
'version' => '5.0.1',
'version' => '5.0.2',
'url' => 'https://github.com/appwrite/sdk-for-swift',
'package' => 'https://github.com/appwrite/sdk-for-swift',
'enabled' => true,
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+9 -4
View File
@@ -9,6 +9,7 @@ use Appwrite\Event\Func;
use Appwrite\Event\Usage;
use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Extend\Exception;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Task\Validator\Cron;
use Appwrite\Utopia\Database\Validator\CustomId;
@@ -1750,6 +1751,10 @@ App::post('/v1/functions/:functionId/executions')
->setAttribute('responseStatusCode', 500)
->setAttribute('errors', $th->getMessage() . '\nError Code: ' . $th->getCode());
Console::error($th->getMessage());
if ($th instanceof AppwriteException) {
throw $th;
}
} finally {
$queueForUsage
->addMetric(METRIC_EXECUTIONS, 1)
@@ -1757,11 +1762,11 @@ App::post('/v1/functions/:functionId/executions')
->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000)) // per project
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function
;
}
if ($function->getAttribute('logging')) {
/** @var Document $execution */
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
if ($function->getAttribute('logging')) {
/** @var Document $execution */
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
}
}
$roles = Authorization::getRoles();
+1 -2
View File
@@ -856,8 +856,7 @@ App::get('/v1/health/queue/failed/:name')
Event::CERTIFICATES_QUEUE_NAME,
Event::BUILDS_QUEUE_NAME,
Event::MESSAGING_QUEUE_NAME,
Event::MIGRATIONS_QUEUE_NAME,
Event::HAMSTER_CLASS_NAME
Event::MIGRATIONS_QUEUE_NAME
]), 'The name of the queue')
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->label('sdk.description', '/docs/references/health/get-failed-queue-jobs.md')
+1 -1
View File
@@ -453,7 +453,7 @@ App::post('/v1/teams/:teamId/memberships')
if (empty($invitee)) { // Create new user if no user with same email found
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
if ($limit !== 0 && $project->getId() !== 'console') { // check users limit, console invites are allways allowed.
if (!$isPrivilegedUser && !$isAppUser && $limit !== 0 && $project->getId() !== 'console') { // check users limit, console invites are allways allowed.
$total = $dbForProject->count('users', [], APP_LIMIT_USERS);
if ($total >= $limit) {
+9 -4
View File
@@ -288,6 +288,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
$execution->setAttribute('logs', $executionResponse['logs']);
$execution->setAttribute('errors', $executionResponse['errors']);
$execution->setAttribute('duration', $executionResponse['duration']);
} catch (\Throwable $th) {
$durationEnd = \microtime(true);
@@ -297,6 +298,10 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
->setAttribute('responseStatusCode', 500)
->setAttribute('errors', $th->getMessage() . '\nError Code: ' . $th->getCode());
Console::error($th->getMessage());
if ($th instanceof AppwriteException) {
throw $th;
}
} finally {
$queueForUsage
->addMetric(METRIC_EXECUTIONS, 1)
@@ -304,11 +309,11 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000)) // per project
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function
;
}
if ($function->getAttribute('logging')) {
/** @var Document $execution */
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
if ($function->getAttribute('logging')) {
/** @var Document $execution */
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
}
}
$execution->setAttribute('logs', '');
+2 -2
View File
@@ -411,7 +411,7 @@ App::init()
$useCache = $route->getLabel('cache', false);
if ($useCache) {
$key = md5($request->getURI() . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER);
$key = md5($request->getURI() . '*' . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER);
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
$cache = new Cache(
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
@@ -666,7 +666,7 @@ App::shutdown()
$resourceType = $parseLabel($pattern, $responsePayload, $requestParams, $user);
}
$key = md5($request->getURI() . '*' . implode('*', $request->getParams())) . '*' . APP_CACHE_BUSTER;
$key = md5($request->getURI() . '*' . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER);
$signature = md5($data['payload']);
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
$accessedAt = $cacheLog->getAttribute('accessedAt', '');
+2 -2
View File
@@ -112,8 +112,8 @@ const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return
const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours
const APP_USER_ACCCESS = 24 * 60 * 60; // 24 hours
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
const APP_CACHE_BUSTER = 430;
const APP_VERSION_STABLE = '1.5.5';
const APP_CACHE_BUSTER = 432;
const APP_VERSION_STABLE = '1.5.6';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
+7 -7
View File
@@ -574,11 +574,11 @@ services:
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
appwrite-maintenance:
appwrite-task-maintenance:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
entrypoint: maintenance
<<: *x-logging
container_name: appwrite-maintenance
container_name: appwrite-task-maintenance
restart: unless-stopped
networks:
- appwrite
@@ -665,10 +665,10 @@ services:
- _APP_LOGGING_CONFIG
- _APP_USAGE_AGGREGATION_INTERVAL
appwrite-scheduler-functions:
appwrite-task-scheduler-functions:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
entrypoint: schedule-functions
container_name: appwrite-scheduler-functions
container_name: appwrite-task-scheduler-functions
<<: *x-logging
restart: unless-stopped
networks:
@@ -690,10 +690,10 @@ services:
- _APP_DB_USER
- _APP_DB_PASS
appwrite-scheduler-messages:
appwrite-task-scheduler-messages:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
entrypoint: schedule-messages
container_name: appwrite-scheduler-messages
container_name: appwrite-task-scheduler-messages
<<: *x-logging
restart: unless-stopped
networks:
@@ -731,7 +731,7 @@ services:
<<: *x-logging
restart: unless-stopped
stop_signal: SIGINT
image: openruntimes/executor:0.4.12
image: openruntimes/executor:0.5.5
networks:
- appwrite
- runtimes
-5
View File
@@ -9,7 +9,6 @@ use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Hamster;
use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
use Appwrite\Event\Migration;
@@ -194,10 +193,6 @@ Server::setResource('queueForMigrations', function (Connection $queue) {
return new Migration($queue);
}, ['queue']);
Server::setResource('queueForHamster', function (Connection $queue) {
return new Hamster($queue);
}, ['queue']);
Server::setResource('logger', function (Registry $register) {
return $register->get('logger');
}, ['register']);
-3
View File
@@ -1,3 +0,0 @@
#!/bin/sh
php /usr/src/code/app/cli.php calc-tier-stats $@
-3
View File
@@ -1,3 +0,0 @@
#!/bin/sh
php /usr/src/code/app/cli.php calc-users-stats $@
-3
View File
@@ -1,3 +0,0 @@
#!/bin/sh
php /usr/src/code/app/cli.php clear-card-cache $@
-3
View File
@@ -1,3 +0,0 @@
#!/bin/sh
php /usr/src/code/app/cli.php create-inf-metric $@
-3
View File
@@ -1,3 +0,0 @@
#!/bin/sh
php /usr/src/code/app/cli.php delete-orphaned-projects $@
-3
View File
@@ -1,3 +0,0 @@
#!/bin/sh
php /usr/src/code/app/cli.php dev-generate-translations $@
-3
View File
@@ -1,3 +0,0 @@
#!/bin/sh
php /usr/src/code/app/cli.php get-migration-stats $@
-3
View File
@@ -1,3 +0,0 @@
#!/bin/sh
php /usr/src/code/app/cli.php hamster $@
-3
View File
@@ -1,3 +0,0 @@
#!/bin/sh
php /usr/src/code/app/cli.php patch-delete-project-collections $@
@@ -1,3 +0,0 @@
#!/bin/sh
php /usr/src/code/app/cli.php patch-delete-schedule-updated-at-attribute $@
@@ -1,3 +0,0 @@
#!/bin/sh
php /usr/src/code/app/cli.php patch-recreate-repositories-documents $@
-3
View File
@@ -1,3 +0,0 @@
#!/bin/sh
php /usr/src/code/app/cli.php volume-sync $@
-3
View File
@@ -1,3 +0,0 @@
#!/bin/sh
php /usr/src/code/app/worker.php hamster $@
Generated
+24 -24
View File
@@ -1556,16 +1556,16 @@
},
{
"name": "utopia-php/database",
"version": "0.49.9",
"version": "0.49.10",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "ee93c14b99820f791c669648854f094fe399a085"
"reference": "216209121bc97a2010f67a39c561fafe1e936bec"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/ee93c14b99820f791c669648854f094fe399a085",
"reference": "ee93c14b99820f791c669648854f094fe399a085",
"url": "https://api.github.com/repos/utopia-php/database/zipball/216209121bc97a2010f67a39c561fafe1e936bec",
"reference": "216209121bc97a2010f67a39c561fafe1e936bec",
"shasum": ""
},
"require": {
@@ -1606,9 +1606,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/0.49.9"
"source": "https://github.com/utopia-php/database/tree/0.49.10"
},
"time": "2024-05-12T23:58:34+00:00"
"time": "2024-05-20T02:14:20+00:00"
},
{
"name": "utopia-php/domains",
@@ -1966,16 +1966,16 @@
},
{
"name": "utopia-php/migration",
"version": "0.4.3",
"version": "0.4.4",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/migration.git",
"reference": "117be70da329dac047d22b4250dfa435a725e187"
"reference": "a8a5d392bebf082faf289f4dfe09d9fd76844c33"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/117be70da329dac047d22b4250dfa435a725e187",
"reference": "117be70da329dac047d22b4250dfa435a725e187",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/a8a5d392bebf082faf289f4dfe09d9fd76844c33",
"reference": "a8a5d392bebf082faf289f4dfe09d9fd76844c33",
"shasum": ""
},
"require": {
@@ -2007,9 +2007,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/migration/issues",
"source": "https://github.com/utopia-php/migration/tree/0.4.3"
"source": "https://github.com/utopia-php/migration/tree/0.4.4"
},
"time": "2024-05-15T04:49:28+00:00"
"time": "2024-05-17T05:25:31+00:00"
},
{
"name": "utopia-php/mongo",
@@ -2498,16 +2498,16 @@
},
{
"name": "utopia-php/vcs",
"version": "0.6.5",
"version": "0.6.6",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/vcs.git",
"reference": "104e47ea8e38c156ec0e0bd415caa3dcd5046fe2"
"reference": "e538264cfee5e3efdfe1771efba04750cf20b2c4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/vcs/zipball/104e47ea8e38c156ec0e0bd415caa3dcd5046fe2",
"reference": "104e47ea8e38c156ec0e0bd415caa3dcd5046fe2",
"url": "https://api.github.com/repos/utopia-php/vcs/zipball/e538264cfee5e3efdfe1771efba04750cf20b2c4",
"reference": "e538264cfee5e3efdfe1771efba04750cf20b2c4",
"shasum": ""
},
"require": {
@@ -2541,9 +2541,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/vcs/issues",
"source": "https://github.com/utopia-php/vcs/tree/0.6.5"
"source": "https://github.com/utopia-php/vcs/tree/0.6.6"
},
"time": "2024-01-08T17:11:12+00:00"
"time": "2024-05-17T09:36:30+00:00"
},
{
"name": "utopia-php/websocket",
@@ -2730,16 +2730,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "0.38.4",
"version": "0.38.6",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "af7e4b53e9d5467fcb03d482d539669bf2eacdd8"
"reference": "d7016d6d72545e84709892faca972eb4bf5bd699"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/af7e4b53e9d5467fcb03d482d539669bf2eacdd8",
"reference": "af7e4b53e9d5467fcb03d482d539669bf2eacdd8",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/d7016d6d72545e84709892faca972eb4bf5bd699",
"reference": "d7016d6d72545e84709892faca972eb4bf5bd699",
"shasum": ""
},
"require": {
@@ -2775,9 +2775,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.38.4"
"source": "https://github.com/appwrite/sdk-generator/tree/0.38.6"
},
"time": "2024-05-15T00:35:29+00:00"
"time": "2024-05-20T18:00:16+00:00"
},
{
"name": "doctrine/deprecations",
+7 -64
View File
@@ -627,10 +627,10 @@ services:
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
appwrite-maintenance:
appwrite-task-maintenance:
entrypoint: maintenance
<<: *x-logging
container_name: appwrite-maintenance
container_name: appwrite-task-maintenance
image: appwrite-dev
networks:
- appwrite
@@ -726,10 +726,10 @@ services:
- _APP_LOGGING_CONFIG
- _APP_USAGE_AGGREGATION_INTERVAL
appwrite-scheduler-functions:
appwrite-task-scheduler-functions:
entrypoint: schedule-functions
<<: *x-logging
container_name: appwrite-scheduler-functions
container_name: appwrite-task-scheduler-functions
image: appwrite-dev
networks:
- appwrite
@@ -753,10 +753,10 @@ services:
- _APP_DB_USER
- _APP_DB_PASS
appwrite-scheduler-messages:
appwrite-task-scheduler-messages:
entrypoint: schedule-messages
<<: *x-logging
container_name: appwrite-scheduler-messages
container_name: appwrite-task-scheduler-messages
image: appwrite-dev
networks:
- appwrite
@@ -788,69 +788,12 @@ services:
environment:
- _APP_ASSISTANT_OPENAI_API_KEY
appwrite-worker-hamster:
entrypoint: worker-hamster
<<: *x-logging
container_name: appwrite-worker-hamster
image: appwrite-dev
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_MIXPANEL_TOKEN
appwrite-hamster-scheduler:
entrypoint: hamster
<<: *x-logging
container_name: appwrite-hamster-scheduler
image: appwrite-dev
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_HAMSTER_TIME
- _APP_HAMSTER_INTERVAL
openruntimes-executor:
container_name: openruntimes-executor
hostname: exc1
<<: *x-logging
stop_signal: SIGINT
image: openruntimes/executor:0.4.12
image: openruntimes/executor:0.5.5
restart: unless-stopped
networks:
- appwrite
@@ -7,7 +7,7 @@ let client = Client()
let account = Account(client)
let user = try await account.deleteMfaAuthenticator(
let result = try await account.deleteMfaAuthenticator(
type: .totp,
otp: "<OTP>"
)
@@ -2,6 +2,7 @@ mutation {
accountCreateAnonymousSession {
_id
_createdAt
_updatedAt
userId
expire
provider
@@ -5,6 +5,7 @@ mutation {
) {
_id
_createdAt
_updatedAt
userId
expire
provider
@@ -5,6 +5,7 @@ mutation {
) {
_id
_createdAt
_updatedAt
userId
expire
provider
@@ -3,35 +3,6 @@ mutation {
type: "totp",
otp: "<OTP>"
) {
_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
}
accessedAt
}
}
@@ -4,6 +4,7 @@ query {
) {
_id
_createdAt
_updatedAt
userId
expire
provider
@@ -3,5 +3,6 @@ query {
totp
phone
email
recoveryCode
}
}
@@ -4,6 +4,7 @@ query {
sessions {
_id
_createdAt
_updatedAt
userId
expire
provider
@@ -5,6 +5,7 @@ mutation {
) {
_id
_createdAt
_updatedAt
userId
expire
provider
@@ -5,6 +5,7 @@ mutation {
) {
_id
_createdAt
_updatedAt
userId
expire
provider
@@ -4,6 +4,7 @@ mutation {
) {
_id
_createdAt
_updatedAt
userId
expire
provider
@@ -10,3 +10,4 @@ appwrite messaging updateEmail \
@@ -17,7 +17,8 @@ const result = await messaging.updateEmail(
false, // html (optional)
[], // cc (optional)
[], // bcc (optional)
'' // scheduledAt (optional)
'', // scheduledAt (optional)
[] // attachments (optional)
);
console.log(response);
@@ -19,4 +19,5 @@ Message result = await messaging.updateEmail(
cc: [], // (optional)
bcc: [], // (optional)
scheduledAt: '', // (optional)
attachments: [], // (optional)
);
@@ -18,5 +18,6 @@ const response = await messaging.updateEmail(
false, // html (optional)
[], // cc (optional)
[], // bcc (optional)
'' // scheduledAt (optional)
'', // scheduledAt (optional)
[] // attachments (optional)
);
@@ -20,5 +20,6 @@ Message result = await messaging.UpdateEmail(
html: false, // optional
cc: new List<string>(), // optional
bcc: new List<string>(), // optional
scheduledAt: "" // optional
scheduledAt: "", // optional
attachments: new List<string>() // optional
);
@@ -2,6 +2,7 @@ mutation {
accountCreateAnonymousSession {
_id
_createdAt
_updatedAt
userId
expire
provider
@@ -5,6 +5,7 @@ mutation {
) {
_id
_createdAt
_updatedAt
userId
expire
provider
@@ -5,6 +5,7 @@ mutation {
) {
_id
_createdAt
_updatedAt
userId
expire
provider
@@ -3,35 +3,6 @@ mutation {
type: "totp",
otp: "<OTP>"
) {
_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
}
accessedAt
}
}
@@ -4,6 +4,7 @@ query {
) {
_id
_createdAt
_updatedAt
userId
expire
provider
@@ -3,5 +3,6 @@ query {
totp
phone
email
recoveryCode
}
}
@@ -4,6 +4,7 @@ query {
sessions {
_id
_createdAt
_updatedAt
userId
expire
provider
@@ -5,6 +5,7 @@ mutation {
) {
_id
_createdAt
_updatedAt
userId
expire
provider
@@ -5,6 +5,7 @@ mutation {
) {
_id
_createdAt
_updatedAt
userId
expire
provider
@@ -4,6 +4,7 @@ mutation {
) {
_id
_createdAt
_updatedAt
userId
expire
provider
@@ -10,7 +10,8 @@ mutation {
html: false,
cc: [],
bcc: [],
scheduledAt: ""
scheduledAt: "",
attachments: []
) {
_id
_createdAt
@@ -4,6 +4,7 @@ mutation {
) {
_id
_createdAt
_updatedAt
userId
expire
provider
@@ -5,5 +5,6 @@ query {
totp
phone
email
recoveryCode
}
}
@@ -6,6 +6,7 @@ query {
sessions {
_id
_createdAt
_updatedAt
userId
expire
provider
@@ -21,6 +21,7 @@ messaging.updateEmail(
listOf(), // cc (optional)
listOf(), // bcc (optional)
"", // scheduledAt (optional)
listOf(), // attachments (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -20,5 +20,6 @@ val response = messaging.updateEmail(
html = false, // optional
cc = listOf(), // optional
bcc = listOf(), // optional
scheduledAt = "" // optional
scheduledAt = "", // optional
attachments = listOf() // optional
)
@@ -10,7 +10,7 @@ const functions = new sdk.Functions(client);
const result = await functions.createDeployment(
'<FUNCTION_ID>', // functionId
InputFile.fromPath('/path/to/file.png', 'file.png'), // code
InputFile.fromPath('/path/to/file', 'filename'), // code
false, // activate
'<ENTRYPOINT>', // entrypoint (optional)
'<COMMANDS>' // commands (optional)
@@ -18,5 +18,6 @@ const result = await messaging.updateEmail(
false, // html (optional)
[], // cc (optional)
[], // bcc (optional)
'' // scheduledAt (optional)
'', // scheduledAt (optional)
[] // attachments (optional)
);
@@ -11,6 +11,6 @@ const storage = new sdk.Storage(client);
const result = await storage.createFile(
'<BUCKET_ID>', // bucketId
'<FILE_ID>', // fileId
InputFile.fromPath('/path/to/file.png', 'file.png'), // file
InputFile.fromPath('/path/to/file', 'filename'), // file
["read("any")"] // permissions (optional)
);
@@ -21,5 +21,6 @@ $result = $messaging->updateEmail(
html: false, // optional
cc: [], // optional
bcc: [], // optional
scheduledAt: '' // optional
scheduledAt: '', // optional
attachments: [] // optional
);
@@ -18,5 +18,6 @@ result = messaging.update_email(
html = False, # optional
cc = [], # optional
bcc = [], # optional
scheduled_at = '' # optional
scheduled_at = '', # optional
attachments = [] # optional
)
@@ -15,5 +15,6 @@ X-Appwrite-Key: 919c2d18fb5d4...a2ae413da83346ad2
"html": false,
"cc": [],
"bcc": [],
"scheduledAt":
"scheduledAt": ,
"attachments": []
}
@@ -20,5 +20,6 @@ result = messaging.update_email(
html: false, # optional
cc: [], # optional
bcc: [], # optional
scheduled_at: '' # optional
scheduled_at: '', # optional
attachments: [] # optional
)
@@ -8,7 +8,7 @@ let client = Client()
let account = Account(client)
let user = try await account.deleteMfaAuthenticator(
let result = try await account.deleteMfaAuthenticator(
type: .totp,
otp: "<OTP>"
)
@@ -18,6 +18,7 @@ let message = try await messaging.updateEmail(
html: false, // optional
cc: [], // optional
bcc: [], // optional
scheduledAt: "" // optional
scheduledAt: "", // optional
attachments: [] // optional
)
+4
View File
@@ -1,3 +1,7 @@
## 11.0.3
* Minor bugfixes
## 11.0.2
* Fixed MSG91 missing template ID
+4
View File
@@ -1,3 +1,7 @@
## 12.0.4
* Fixed concurrent modification error when closing realtime socket
## 12.0.3
* Upgrade dependencies
-3
View File
@@ -45,9 +45,6 @@ class Event
public const MIGRATIONS_QUEUE_NAME = 'v1-migrations';
public const MIGRATIONS_CLASS_NAME = 'MigrationsV1';
public const HAMSTER_QUEUE_NAME = 'v1-hamster';
public const HAMSTER_CLASS_NAME = 'HamsterV1';
protected string $queue = '';
protected string $class = '';
protected string $event = '';
-157
View File
@@ -1,157 +0,0 @@
<?php
namespace Appwrite\Event;
use Utopia\Database\Document;
use Utopia\Queue\Client;
use Utopia\Queue\Connection;
class Hamster extends Event
{
protected string $type = '';
protected ?Document $project = null;
protected ?Document $organization = null;
protected ?Document $user = null;
public const TYPE_PROJECT = 'project';
public const TYPE_ORGANISATION = 'organisation';
public const TYPE_USER = 'user';
public function __construct(protected Connection $connection)
{
parent::__construct($connection);
$this
->setQueue(Event::HAMSTER_QUEUE_NAME)
->setClass(Event::HAMSTER_CLASS_NAME);
}
/**
* Sets the type for the hamster event.
*
* @param string $type
* @return self
*/
public function setType(string $type): self
{
$this->type = $type;
return $this;
}
/**
* Returns the set type for the hamster event.
*
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* Sets the project for the hamster event.
*
* @param Document $project
*/
public function setProject(Document $project): self
{
$this->project = $project;
return $this;
}
/**
* Returns the set project for the hamster event.
*
* @return Document
*/
public function getProject(): Document
{
return $this->project;
}
/**
* Sets the organization for the hamster event.
*
* @param Document $organization
*/
public function setOrganization(Document $organization): self
{
$this->organization = $organization;
return $this;
}
/**
* Returns the set organization for the hamster event.
*
* @return string
*/
public function getOrganization(): Document
{
return $this->organization;
}
/**
* Sets the user for the hamster event.
*
* @param Document $user
*/
public function setUser(Document $user): self
{
$this->user = $user;
return $this;
}
/**
* Returns the set user for the hamster event.
*
* @return Document
*/
public function getUser(): Document
{
return $this->user;
}
/**
* Executes the function event and sends it to the functions worker.
*
* @return string|bool
* @throws \InvalidArgumentException
*/
public function trigger(): string|bool
{
if ($this->paused) {
return false;
}
$client = new Client($this->queue, $this->connection);
$events = $this->getEvent() ? Event::generateEvents($this->getEvent(), $this->getParams()) : null;
return $client->enqueue([
'type' => $this->type,
'project' => $this->project,
'organization' => $this->organization,
'user' => $this->user,
'events' => $events,
]);
}
/**
* Generate a function event from a base event
*
* @param Event $event
*
* @return self
*
*/
public function from(Event $event): self
{
$this->event = $event->getEvent();
$this->params = $event->getParams();
return $this;
}
}
+1
View File
@@ -153,6 +153,7 @@ class Exception extends \Exception
public const FUNCTION_NOT_FOUND = 'function_not_found';
public const FUNCTION_RUNTIME_UNSUPPORTED = 'function_runtime_unsupported';
public const FUNCTION_ENTRYPOINT_MISSING = 'function_entrypoint_missing';
public const FUNCTION_SYNCHRONOUS_TIMEOUT = 'function_synchronous_timeout';
/** Deployments */
public const DEPLOYMENT_NOT_FOUND = 'deployment_not_found';
+1
View File
@@ -84,6 +84,7 @@ abstract class Migration
'1.5.3' => 'V20',
'1.5.4' => 'V20',
'1.5.5' => 'V20',
'1.5.6' => 'V20',
];
/**
-16
View File
@@ -2,17 +2,10 @@
namespace Appwrite\Platform\Services;
use Appwrite\Platform\Tasks\CalcTierStats;
use Appwrite\Platform\Tasks\CreateInfMetric;
use Appwrite\Platform\Tasks\DeleteOrphanedProjects;
use Appwrite\Platform\Tasks\DevGenerateTranslations;
use Appwrite\Platform\Tasks\Doctor;
use Appwrite\Platform\Tasks\GetMigrationStats;
use Appwrite\Platform\Tasks\Hamster;
use Appwrite\Platform\Tasks\Install;
use Appwrite\Platform\Tasks\Maintenance;
use Appwrite\Platform\Tasks\Migrate;
use Appwrite\Platform\Tasks\PatchRecreateRepositoriesDocuments;
use Appwrite\Platform\Tasks\QueueCount;
use Appwrite\Platform\Tasks\QueueRetry;
use Appwrite\Platform\Tasks\ScheduleFunctions;
@@ -23,7 +16,6 @@ use Appwrite\Platform\Tasks\SSL;
use Appwrite\Platform\Tasks\Upgrade;
use Appwrite\Platform\Tasks\Vars;
use Appwrite\Platform\Tasks\Version;
use Appwrite\Platform\Tasks\VolumeSync;
use Utopia\Platform\Service;
class Tasks extends Service
@@ -32,17 +24,10 @@ class Tasks extends Service
{
$this->type = self::TYPE_CLI;
$this
->addAction(CalcTierStats::getName(), new CalcTierStats())
->addAction(CreateInfMetric::getName(), new CreateInfMetric())
->addAction(DeleteOrphanedProjects::getName(), new DeleteOrphanedProjects())
->addAction(DevGenerateTranslations::getName(), new DevGenerateTranslations())
->addAction(Doctor::getName(), new Doctor())
->addAction(GetMigrationStats::getName(), new GetMigrationStats())
->addAction(Hamster::getName(), new Hamster())
->addAction(Install::getName(), new Install())
->addAction(Maintenance::getName(), new Maintenance())
->addAction(Migrate::getName(), new Migrate())
->addAction(PatchRecreateRepositoriesDocuments::getName(), new PatchRecreateRepositoriesDocuments())
->addAction(QueueCount::getName(), new QueueCount())
->addAction(QueueRetry::getName(), new QueueRetry())
->addAction(SDKs::getName(), new SDKs())
@@ -53,7 +38,6 @@ class Tasks extends Service
->addAction(Upgrade::getName(), new Upgrade())
->addAction(Vars::getName(), new Vars())
->addAction(Version::getName(), new Version())
->addAction(VolumeSync::getName(), new VolumeSync())
;
}
}
@@ -8,7 +8,6 @@ use Appwrite\Platform\Workers\Certificates;
use Appwrite\Platform\Workers\Databases;
use Appwrite\Platform\Workers\Deletes;
use Appwrite\Platform\Workers\Functions;
use Appwrite\Platform\Workers\Hamster;
use Appwrite\Platform\Workers\Mails;
use Appwrite\Platform\Workers\Messaging;
use Appwrite\Platform\Workers\Migrations;
@@ -32,7 +31,6 @@ class Workers extends Service
->addAction(Mails::getName(), new Mails())
->addAction(Messaging::getName(), new Messaging())
->addAction(Webhooks::getName(), new Webhooks())
->addAction(Hamster::getName(), new Hamster())
->addAction(UsageDump::getName(), new UsageDump())
->addAction(Usage::getName(), new Usage())
->addAction(Migrations::getName(), new Migrations())
@@ -1,407 +0,0 @@
<?php
namespace Appwrite\Platform\Tasks;
use League\Csv\Writer;
use PHPMailer\PHPMailer\PHPMailer;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Action;
use Utopia\Pools\Group;
use Utopia\Registry\Registry;
use Utopia\System\System;
use Utopia\Validator\Text;
class CalcTierStats extends Action
{
/*
* Csv cols headers
*/
private array $columns = [
'Project ID',
'Organization ID',
'Organization Email',
'Organization Members',
'Teams',
'Users',
'Requests',
'Bandwidth',
'Domains',
'Api keys',
'Webhooks',
'Platforms',
'Buckets',
'Files',
'Storage (bytes)',
'Max File Size (bytes)',
'Databases',
'Functions',
'Deployments',
'Executions',
'Migrations',
];
protected string $directory = '/usr/local';
protected string $path;
protected string $date;
private array $usageStats = [
'network.requests' => 'Requests',
'network.inbound' => 'Inbound',
'network.outbound' => 'Outbound',
];
public static function getName(): string
{
return 'calc-tier-stats';
}
public function __construct()
{
$this
->desc('Get stats for projects')
->param('after', '', new Text(36), 'After cursor', true)
->param('projectId', '', new Text(36), 'Select project to validate', true)
->inject('pools')
->inject('cache')
->inject('dbForConsole')
->inject('getProjectDB')
->inject('register')
->callback(function ($after, $projectId, Group $pools, Cache $cache, Database $dbForConsole, callable $getProjectDB, Registry $register) {
$this->action($after, $projectId, $pools, $cache, $dbForConsole, $getProjectDB, $register);
});
}
public function action(string $after, string $projectId, Group $pools, Cache $cache, Database $dbForConsole, callable $getProjectDB, Registry $register): void
{
//docker compose exec -t appwrite calc-tier-stats
Console::title('Cloud free tier stats calculation V1');
Console::success(APP_NAME . ' cloud free tier stats calculation has started');
/** CSV stuff */
$this->date = date('Y-m-d');
$this->path = "{$this->directory}/tier_stats_{$this->date}.csv";
$csv = Writer::createFromPath($this->path, 'w');
$csv->insertOne($this->columns);
if (!empty($projectId)) {
try {
console::log("Project " . $projectId);
$project = $dbForConsole->getDocument('projects', $projectId);
$dbForProject = call_user_func($getProjectDB, $project);
$data = $this->getData($project, $dbForConsole, $dbForProject);
$csv->insertOne($data);
$this->sendMail($register);
return;
} catch (\Throwable $th) {
Console::error("Unexpected error occurred with Project ID {$projectId}");
Console::error('[Error] Type: ' . get_class($th));
Console::error('[Error] Message: ' . $th->getMessage());
Console::error('[Error] File: ' . $th->getFile());
Console::error('[Error] Line: ' . $th->getLine());
}
}
$queries = [];
if (!empty($after)) {
Console::info("Iterating remaining projects after project with ID {$after}");
$project = $dbForConsole->getDocument('projects', $after);
$queries = [Query::cursorAfter($project)];
} else {
Console::info("Iterating all projects");
}
$this->foreachDocument($dbForConsole, 'projects', $queries, function (Document $project) use ($getProjectDB, $dbForConsole, $csv) {
$projectId = $project->getId();
console::log("Project " . $projectId);
try {
$dbForProject = call_user_func($getProjectDB, $project);
$data = $this->getData($project, $dbForConsole, $dbForProject);
$csv->insertOne($data);
} catch (\Throwable $th) {
Console::error("Unexpected error occurred with Project ID {$projectId}");
Console::error('[Error] Type: ' . get_class($th));
Console::error('[Error] Message: ' . $th->getMessage());
Console::error('[Error] File: ' . $th->getFile());
Console::error('[Error] Line: ' . $th->getLine());
}
});
$this->sendMail($register);
}
private function foreachDocument(Database $database, string $collection, array $queries = [], callable $callback = null): void
{
$limit = 1000;
$results = [];
$sum = $limit;
$latestDocument = null;
while ($sum === $limit) {
$newQueries = $queries;
if ($latestDocument != null) {
array_unshift($newQueries, Query::cursorAfter($latestDocument));
}
$newQueries[] = Query::limit($limit);
$results = $database->find('projects', $newQueries);
if (empty($results)) {
return;
}
$sum = count($results);
foreach ($results as $document) {
if (is_callable($callback)) {
$callback($document);
}
}
$latestDocument = $results[array_key_last($results)];
}
}
private function sendMail(Registry $register): void
{
/** @var PHPMailer $mail */
$mail = $register->get('smtp');
$mail->clearAddresses();
$mail->clearAllRecipients();
$mail->clearReplyTos();
$mail->clearAttachments();
$mail->clearBCCs();
$mail->clearCCs();
try {
/** Addresses */
$mail->setFrom(System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), 'Appwrite Cloud Hamster');
$recipients = explode(',', System::getEnv('_APP_USERS_STATS_RECIPIENTS', ''));
foreach ($recipients as $recipient) {
$mail->addAddress($recipient);
}
/** Attachments */
$mail->addAttachment($this->path);
/** Content */
$mail->Subject = "Cloud Report for {$this->date}";
$mail->Body = "Please find the daily cloud report atttached";
$mail->send();
Console::success('Email has been sent!');
} catch (\Throwable $e) {
Console::error("Message could not be sent. Mailer Error: {$mail->ErrorInfo}");
}
}
private function getData(Document $project, Database $dbForConsole, Database $dbForProject): array
{
$stats['Project ID'] = $project->getId();
$stats['Organization ID'] = $project->getAttribute('teamId', null);
$teamInternalId = $project->getAttribute('teamInternalId', null);
if ($teamInternalId) {
$membership = $dbForConsole->findOne('memberships', [
Query::equal('teamInternalId', [$teamInternalId]),
]);
if (!$membership || $membership->isEmpty()) {
Console::error('Membership not found. Skipping project : ' . $project->getId());
}
$userId = $membership->getAttribute('userId', null);
if ($userId) {
$user = $dbForConsole->getDocument('users', $userId);
$stats['Organization Email'] = $user->getAttribute('email', null);
}
} else {
Console::error("Email was not found for this Organization ID :{$teamInternalId}");
}
/** Get Total Members */
if ($teamInternalId) {
$stats['Organization Members'] = $dbForConsole->count('memberships', [
Query::equal('teamInternalId', [$teamInternalId])
]);
} else {
$stats['Organization Members'] = 0;
}
/** Get Total internal Teams */
try {
$stats['Teams'] = $dbForProject->count('teams', []);
} catch (\Throwable) {
$stats['Teams'] = 0;
}
/** Get Total users */
try {
$stats['Users'] = $dbForProject->count('users', []);
} catch (\Throwable) {
$stats['Users'] = 0;
}
/** Get Usage stats */
$range = '30d';
$periods = [
'30d' => [
'period' => '1d',
'limit' => 30,
]
];
$tmp = [];
$metrics = $this->usageStats;
Authorization::skip(function () use ($dbForProject, $periods, $range, $metrics, &$tmp) {
foreach ($metrics as $metric => $name) {
$limit = $periods[$range]['limit'];
$period = $periods[$range]['period'];
$requestDocs = $dbForProject->find('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', [$period]),
Query::limit($limit),
Query::orderDesc('time'),
]);
$tmp[$metric] = [];
foreach ($requestDocs as $requestDoc) {
if (empty($requestDoc)) {
continue;
}
$tmp[$metric][] = [
'value' => $requestDoc->getAttribute('value'),
'date' => $requestDoc->getAttribute('time'),
];
}
$tmp[$metric] = array_reverse($tmp[$metric]);
$tmp[$metric] = array_sum(array_column($tmp[$metric], 'value'));
}
});
foreach ($tmp as $key => $value) {
$stats[$metrics[$key]] = $value;
}
/**
* Workaround to combine network.inbound+network.outbound as network.
*/
$stats['Bandwidth'] = ($stats['Inbound'] ?? 0) + ($stats['Outbound'] ?? 0);
unset($stats['Inbound']);
unset($stats['Outbound']);
try {
/** Get Domains */
$stats['Domains'] = $dbForConsole->count('rules', [
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
} catch (\Throwable) {
$stats['Domains'] = 0;
}
try {
/** Get Api keys */
$stats['Api keys'] = $dbForConsole->count('keys', [
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
} catch (\Throwable) {
$stats['Api keys'] = 0;
}
try {
/** Get Webhooks */
$stats['Webhooks'] = $dbForConsole->count('webhooks', [
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
} catch (\Throwable) {
$stats['Webhooks'] = 0;
}
try {
/** Get Platforms */
$stats['Platforms'] = $dbForConsole->count('platforms', [
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
} catch (\Throwable) {
$stats['Platforms'] = 0;
}
/** Get Files & Buckets */
$filesCount = 0;
$filesSum = 0;
$maxFileSize = 0;
$counter = 0;
try {
$buckets = $dbForProject->find('buckets', []);
foreach ($buckets as $bucket) {
$file = $dbForProject->findOne('bucket_' . $bucket->getInternalId(), [Query::orderDesc('sizeOriginal'),]);
if (empty($file)) {
continue;
}
$filesSum += $dbForProject->sum('bucket_' . $bucket->getInternalId(), 'sizeOriginal', []);
$filesCount += $dbForProject->count('bucket_' . $bucket->getInternalId(), []);
if ($file->getAttribute('sizeOriginal') > $maxFileSize) {
$maxFileSize = $file->getAttribute('sizeOriginal');
}
$counter++;
}
} catch (\Throwable $t) {
Console::error("Error while counting buckets: {$project->getId()}");
}
$stats['Buckets'] = $counter;
$stats['Files'] = $filesCount;
$stats['Storage (bytes)'] = $filesSum;
$stats['Max File Size (bytes)'] = $maxFileSize;
try {
/** Get Total Functions */
$stats['Databases'] = $dbForProject->count('databases', []);
} catch (\Throwable) {
$stats['Databases'] = 0;
}
/** Get Total Functions */
try {
$stats['Functions'] = $dbForProject->count('functions', []);
} catch (\Throwable) {
$stats['Functions'] = 0;
}
/** Get Total Deployments */
try {
$stats['Deployments'] = $dbForProject->count('deployments', []);
} catch (\Throwable) {
$stats['Deployments'] = 0;
}
/** Get Total Executions */
try {
$stats['Executions'] = $dbForProject->count('executions', []);
} catch (\Throwable) {
$stats['Executions'] = 0;
}
/** Get Total Migrations */
try {
$stats['Migrations'] = $dbForProject->count('migrations', []);
} catch (\Throwable) {
$stats['Migrations'] = 0;
}
return array_values($stats);
}
}
@@ -1,409 +0,0 @@
<?php
namespace Appwrite\Platform\Tasks;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception;
use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Query;
use Utopia\Platform\Action;
use Utopia\Validator\Text;
class CreateInfMetric extends Action
{
public static function getName(): string
{
return 'create-inf-metric';
}
public function __construct()
{
$this
->desc('Create infinity stats metric')
->param('after', '', new Text(36), 'After cursor', true)
->param('projectId', '', new Text(36), 'Select project to validate', true)
->inject('getProjectDB')
->inject('dbForConsole')
->callback(function (string $after, string $projectId, callable $getProjectDB, Database $dbForConsole) {
$this->action($after, $projectId, $getProjectDB, $dbForConsole);
});
}
/**
* @throws Exception
* @throws Exception\Timeout
* @throws Exception\Query
*/
public function action(string $after, string $projectId, callable $getProjectDB, Database $dbForConsole): void
{
Console::title('Create infinity metric V1');
Console::success(APP_NAME . ' Create infinity metric started');
if (!empty($projectId)) {
try {
$project = $dbForConsole->getDocument('projects', $projectId);
$dbForProject = call_user_func($getProjectDB, $project);
$this->getUsageData($dbForProject, $project);
} catch (\Throwable $th) {
Console::error("Unexpected error occurred with Project ID {$projectId}");
Console::error('[Error] Type: ' . get_class($th));
Console::error('[Error] Message: ' . $th->getMessage());
Console::error('[Error] File: ' . $th->getFile());
Console::error('[Error] Line: ' . $th->getLine());
}
} else {
$queries = [];
if (!empty($after)) {
Console::info("Iterating remaining projects after project with ID {$after}");
$project = $dbForConsole->getDocument('projects', $after);
$queries = [Query::cursorAfter($project)];
} else {
Console::info("Iterating all projects");
}
$this->foreachDocument($dbForConsole, 'projects', $queries, function (Document $project) use ($getProjectDB) {
$projectId = $project->getId();
try {
$dbForProject = call_user_func($getProjectDB, $project);
$this->getUsageData($dbForProject, $project);
} catch (\Throwable $th) {
Console::error("Unexpected error occurred with Project ID {$projectId}");
Console::error('[Error] Type: ' . get_class($th));
Console::error('[Error] Message: ' . $th->getMessage());
Console::error('[Error] File: ' . $th->getFile());
Console::error('[Error] Line: ' . $th->getLine());
}
});
}
}
/**
* @param Database $database
* @param string $collection
* @param array $queries
* @param callable|null $callback
* @return void
* @throws Exception
* @throws Exception\Query
* @throws Exception\Timeout
*/
private function foreachDocument(Database $database, string $collection, array $queries = [], callable $callback = null): void
{
$limit = 1000;
$results = [];
$sum = $limit;
$latestDocument = null;
while ($sum === $limit) {
$newQueries = $queries;
if ($latestDocument != null) {
array_unshift($newQueries, Query::cursorAfter($latestDocument));
}
$newQueries[] = Query::limit($limit);
$results = $database->find($collection, $newQueries);
if (empty($results)) {
return;
}
$sum = count($results);
foreach ($results as $document) {
if (is_callable($callback)) {
$callback($document);
}
}
$latestDocument = $results[array_key_last($results)];
}
}
/**
* @param Database $dbForProject
* @param Document $project
* @return void
*/
private function getUsageData(Database $dbForProject, Document $project): void
{
try {
$this->network($dbForProject);
$this->sessions($dbForProject);
$this->users($dbForProject);
$this->teams($dbForProject);
$this->databases($dbForProject);
$this->functions($dbForProject);
$this->storage($dbForProject);
} catch (\Throwable $th) {
var_dump($th->getMessage());
}
Console::log('Finished project ' . $project->getId() . ' ' . $project->getInternalId());
}
/**
* @param Database $dbForProject
* @param string $metric
* @param int|float $value
* @return void
* @throws Exception
* @throws Exception\Authorization
* @throws Exception\Conflict
* @throws Exception\Restricted
* @throws Exception\Structure
*/
private function createInfMetric(database $dbForProject, string $metric, int|float $value): void
{
try {
$id = \md5("_inf_{$metric}");
$dbForProject->deleteDocument('stats', $id);
$dbForProject->createDocument('stats', new Document([
'$id' => $id,
'metric' => $metric,
'period' => 'inf',
'value' => (int)$value,
'time' => null,
'region' => 'default',
]));
} catch (Duplicate $th) {
console::log("Error while creating inf metric: duplicate id {$metric} {$id}");
}
}
/**
* @param Database $dbForProject
* @param string $metric
* @return int|float
* @throws Exception
*/
protected function getFromMetric(database $dbForProject, string $metric): int|float
{
return $dbForProject->sum('stats', 'value', [
Query::equal('metric', [
$metric,
]),
Query::equal('period', ['1d']),
]);
}
/**
* @param Database $dbForProject
* @throws Exception
* @throws Exception\Authorization
* @throws Exception\Conflict
* @throws Exception\Restricted
* @throws Exception\Structure
*/
private function network(database $dbForProject)
{
$this->createInfMetric($dbForProject, 'network.inbound', $this->getFromMetric($dbForProject, 'network.inbound'));
$this->createInfMetric($dbForProject, 'network.outbound', $this->getFromMetric($dbForProject, 'network.outbound'));
$this->createInfMetric($dbForProject, 'network.requests', $this->getFromMetric($dbForProject, 'network.requests'));
}
/**
* @throws Exception\Authorization
* @throws Exception\Restricted
* @throws Exception\Conflict
* @throws Exception\Timeout
* @throws Exception\Structure
* @throws Exception
* @throws Exception\Query
*/
private function storage(database $dbForProject)
{
$bucketsCount = 0;
$filesCount = 0;
$filesStorageSum = 0;
$buckets = $dbForProject->find('buckets');
foreach ($buckets as $bucket) {
$files = $dbForProject->count('bucket_' . $bucket->getInternalId());
$this->createInfMetric($dbForProject, $bucket->getInternalId() . '.files', $files);
$filesStorage = $dbForProject->sum('bucket_' . $bucket->getInternalId(), 'sizeOriginal');
$this->createInfMetric($dbForProject, $bucket->getInternalId() . '.files.storage', $filesStorage);
$bucketsCount++;
$filesCount += $files;
$filesStorageSum += $filesStorage;
}
$this->createInfMetric($dbForProject, 'buckets', $bucketsCount);
$this->createInfMetric($dbForProject, 'files', $filesCount);
$this->createInfMetric($dbForProject, 'files.storage', $filesStorageSum);
}
/**
* @throws Exception\Authorization
* @throws Exception\Timeout
* @throws Exception\Restricted
* @throws Exception\Structure
* @throws Exception\Conflict
* @throws Exception
* @throws Exception\Query
*/
private function functions(Database $dbForProject)
{
$functionsCount = 0;
$deploymentsCount = 0;
$buildsCount = 0;
$buildsStorageSum = 0;
$buildsComputeSum = 0;
$executionsCount = 0;
$executionsComputeSum = 0;
$deploymentsStorageSum = 0;
//functions
$functions = $dbForProject->find('functions');
foreach ($functions as $function) {
//deployments
$deployments = $dbForProject->find('deployments', [
Query::equal('resourceType', ['functions']),
Query::equal('resourceInternalId', [$function->getInternalId()]),
]);
$deploymentCount = 0;
$deploymentStorageSum = 0;
foreach ($deployments as $deployment) {
//builds
$builds = $dbForProject->count('builds', [
Query::equal('deploymentInternalId', [$deployment->getInternalId()]),
]);
$buildsCompute = $dbForProject->sum('builds', 'duration', [
Query::equal('deploymentInternalId', [$deployment->getInternalId()]),
]);
$buildsStorage = $dbForProject->sum('builds', 'size', [
Query::equal('deploymentInternalId', [$deployment->getInternalId()]),
]);
$this->createInfMetric($dbForProject, $function->getInternalId() . '.builds', $builds);
$this->createInfMetric($dbForProject, $function->getInternalId() . '.builds.storage', $buildsCompute * 1000);
$this->createInfMetric($dbForProject, $function->getInternalId() . '.builds.compute', $buildsStorage);
$buildsCount += $builds;
$buildsComputeSum += $buildsCompute;
$buildsStorageSum += $buildsStorage;
$deploymentCount++;
$deploymentsCount++;
$deploymentsStorageSum += $deployment['size'];
$deploymentStorageSum += $deployment['size'];
}
$this->createInfMetric($dbForProject, 'functions.' . $function->getInternalId() . '.deployments', $deploymentCount);
$this->createInfMetric($dbForProject, 'functions.' . $function->getInternalId() . '.deployments.storage', $deploymentStorageSum);
//executions
$executions = $dbForProject->count('executions', [
Query::equal('functionInternalId', [$function->getInternalId()]),
]);
$executionsCompute = $dbForProject->sum('executions', 'duration', [
Query::equal('functionInternalId', [$function->getInternalId()]),
]);
$this->createInfMetric($dbForProject, $function->getInternalId() . '.executions', $executions);
$this->createInfMetric($dbForProject, $function->getInternalId() . '.executions.compute', $executionsCompute * 1000);
$executionsCount += $executions;
$executionsComputeSum += $executionsCompute;
$functionsCount++;
}
$this->createInfMetric($dbForProject, 'functions', $functionsCount);
$this->createInfMetric($dbForProject, 'deployments', $deploymentsCount);
$this->createInfMetric($dbForProject, 'deployments.storage', $deploymentsStorageSum);
$this->createInfMetric($dbForProject, 'builds', $buildsCount);
$this->createInfMetric($dbForProject, 'builds.compute', $buildsComputeSum * 1000);
$this->createInfMetric($dbForProject, 'builds.storage', $buildsStorageSum);
$this->createInfMetric($dbForProject, 'executions', $executionsCount);
$this->createInfMetric($dbForProject, 'executions.compute', $executionsComputeSum * 1000);
}
/**
* @throws Exception\Authorization
* @throws Exception\Timeout
* @throws Exception\Structure
* @throws Exception\Restricted
* @throws Exception\Conflict
* @throws Exception
* @throws Exception\Query
*/
private function databases(Database $dbForProject)
{
$databasesCount = 0;
$collectionsCount = 0;
$documentsCount = 0;
$databases = $dbForProject->find('databases');
foreach ($databases as $database) {
$collectionCount = 0;
$collections = $dbForProject->find('database_' . $database->getInternalId());
foreach ($collections as $collection) {
$documents = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
$this->createInfMetric($dbForProject, $database->getInternalId() . '.' . $collection->getInternalId() . '.documents', $documents);
$documentsCount += $documents;
$collectionCount++;
$collectionsCount++;
}
$this->createInfMetric($dbForProject, $database->getInternalId() . '.collections', $collectionCount);
$this->createInfMetric($dbForProject, $database->getInternalId() . '.documents', $documentsCount);
$databasesCount++;
}
$this->createInfMetric($dbForProject, 'collections', $collectionsCount);
$this->createInfMetric($dbForProject, 'databases', $databasesCount);
$this->createInfMetric($dbForProject, 'documents', $documentsCount);
}
/**
* @throws Exception\Authorization
* @throws Exception\Structure
* @throws Exception\Restricted
* @throws Exception\Conflict
* @throws Exception
*/
private function users(Database $dbForProject)
{
$users = $dbForProject->count('users');
$this->createInfMetric($dbForProject, 'users', $users);
}
/**
* @throws Exception\Authorization
* @throws Exception\Structure
* @throws Exception\Restricted
* @throws Exception\Conflict
* @throws Exception
*/
private function sessions(Database $dbForProject)
{
$users = $dbForProject->count('sessions');
$this->createInfMetric($dbForProject, 'sessions', $users);
}
/**
* @throws Exception\Authorization
* @throws Exception\Structure
* @throws Exception\Restricted
* @throws Exception\Conflict
* @throws Exception
*/
private function teams(Database $dbForProject)
{
$teams = $dbForProject->count('teams');
$this->createInfMetric($dbForProject, 'teams', $teams);
}
}
@@ -1,161 +0,0 @@
<?php
namespace Appwrite\Platform\Tasks;
use Utopia\App;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\Platform\Action;
use Utopia\Pools\Group;
use Utopia\Registry\Registry;
use Utopia\Validator\Boolean;
class DeleteOrphanedProjects extends Action
{
public static function getName(): string
{
return 'delete-orphaned-projects';
}
public function __construct()
{
$this
->desc('Delete orphaned projects')
->param('commit', false, new Boolean(true), 'Commit project deletion', true)
->inject('pools')
->inject('cache')
->inject('dbForConsole')
->inject('register')
->callback(function (bool $commit, Group $pools, Cache $cache, Database $dbForConsole, Registry $register) {
$this->action($commit, $pools, $cache, $dbForConsole, $register);
});
}
public function action(bool $commit, Group $pools, Cache $cache, Database $dbForConsole, Registry $register): void
{
Console::title('Delete orphaned projects V1');
Console::success(APP_NAME . ' Delete orphaned projects started');
/** @var array $collections */
$collectionsConfig = Config::getParam('collections', [])['projects'] ?? [];
$collectionsConfig = array_merge([
'audit' => [
'$id' => ID::custom('audit'),
'$collection' => Database::METADATA
],
'abuse' => [
'$id' => ID::custom('abuse'),
'$collection' => Database::METADATA
]
], $collectionsConfig);
/* Initialise new Utopia app */
$app = new App('UTC');
$console = $app->getResource('console');
$projects = [$console];
/** Database connections */
$totalProjects = $dbForConsole->count('projects');
Console::success("Found a total of: {$totalProjects} projects");
$orphans = 1;
$cnt = 0;
$count = 0;
$limit = 30;
$sum = 30;
$offset = 0;
while (!empty($projects)) {
foreach ($projects as $project) {
/**
* Skip user projects with id 'console'
*/
if ($project->getId() === 'console') {
continue;
}
try {
$db = $project->getAttribute('database');
$adapter = $pools
->get($db)
->pop()
->getResource();
$dbForProject = new Database($adapter, $cache);
$dbForProject->setDatabase('appwrite');
$dbForProject->setNamespace('_' . $project->getInternalId());
$collectionsCreated = 0;
$cnt++;
if ($dbForProject->exists($dbForProject->getDatabase(), Database::METADATA)) {
$collectionsCreated = $dbForProject->count(Database::METADATA);
}
$msg = '(' . $cnt . ') found (' . $collectionsCreated . ') collections on project (' . $project->getInternalId() . ') , database (' . $project['database'] . ')';
if ($collectionsCreated >= count($collectionsConfig)) {
Console::log($msg . ' ignoring....');
continue;
}
Console::log($msg);
if ($collectionsCreated > 0) {
$collections = $dbForProject->find(Database::METADATA, []);
foreach ($collections as $collection) {
if ($commit) {
$dbForProject->deleteCollection($collection->getId());
$dbForConsole->purgeCachedCollection($collection->getId());
}
Console::info('--Deleting collection (' . $collection->getId() . ') project no (' . $project->getInternalId() . ')');
}
}
if ($commit) {
$dbForConsole->deleteDocument('projects', $project->getId());
$dbForConsole->purgeCachedDocument('projects', $project->getId());
if ($dbForProject->exists($dbForProject->getDefaultDatabase(), Database::METADATA)) {
try {
$dbForProject->deleteCollection(Database::METADATA);
$dbForProject->purgeCachedCollection(Database::METADATA);
} catch (\Throwable $th) {
Console::warning('Metadata collection does not exist');
}
}
}
Console::info('--Deleting project no (' . $project->getInternalId() . ')');
$orphans++;
} catch (\Throwable $th) {
Console::error('Error: ' . $th->getMessage() . ' ' . $th->getTraceAsString());
} finally {
$pools
->get($db)
->reclaim();
}
}
$sum = \count($projects);
$projects = $dbForConsole->find('projects', [
Query::limit($limit),
Query::offset($offset),
]);
$offset = $offset + $limit;
$count = $count + $sum;
}
Console::log('Iterated through ' . $count - 1 . '/' . $totalProjects . ' projects found ' . $orphans - 1 . ' orphans');
}
}
@@ -1,136 +0,0 @@
<?php
namespace Appwrite\Platform\Tasks;
use Exception;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Fetch\Client;
use Utopia\Platform\Action;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
class DevGenerateTranslations extends Action
{
private string $apiKey = '';
public static function getName(): string
{
return 'dev-generate-translations';
}
public function __construct()
{
$this
->desc('Generate translations in all languages')
->param('dry-run', 'true', new Boolean(true), 'If action should do a dry run. Dry run does not write into files', true)
->param('api-key', '', new Text(256), 'Open AI API key. Only used during non-dry runs to generate translations.', true)
->callback(fn ($dryRun, $apiKey) => $this->action($dryRun, $apiKey));
}
public function action(bool|string $dryRun, string $apiKey): void
{
$dryRun = \strval($dryRun) === 'true';
Console::info("Started");
if (!$dryRun && empty($apiKey)) {
Console::error("Please specify --api-key='OPEN_AI_API_KEY' or run with --dry-run");
return;
}
$this->apiKey = $apiKey;
$dir = __DIR__ . '/../../../../app/config/locale/translations';
$mainFile = 'en.json';
$mainJson = \json_decode(\file_get_contents($dir . '/' . $mainFile), true);
$mainKeys = \array_keys($mainJson);
$files = array_diff(scandir($dir), array('.', '..', $mainFile));
foreach ($files as $file) {
$fileJson = \json_decode(\file_get_contents($dir . '/' . $file), true);
$fileKeys = \array_keys($fileJson);
// Trick to clear specific key from all translation files:
// $json = \json_decode(\file_get_contents($dir . '/' . $file), true);
// unset($json['emails.magicSession.optionUrl']);
// \file_put_contents($dir . '/' . $file, \json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | 0));
// continue;
foreach ($mainKeys as $key) {
if (!(\in_array($key, $fileKeys))) {
if ($dryRun) {
Console::warning("{$file} missing translation for {$key}");
} else {
$language = \explode('.', $file)[0];
$translation = $this->generateTranslation($language, $mainJson[$key]);
if (!empty($translation)) {
$json = \json_decode(\file_get_contents($dir . '/' . $file), true);
$json[$key] = $translation;
\file_put_contents($dir . '/' . $file, \json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | 0));
Console::success("Generated {$key} for {$language}");
}
}
}
}
}
Console::info("Done");
}
private function generateTranslation(string $targetLanguage, string $enTranslation): string
{
$list = Config::getParam('locale-languages');
foreach ($list as $language) {
if ($language['code'] === $targetLanguage) {
$languageObject = $language;
}
}
if (!isset($languageObject)) {
Console::error("{$targetLanguage} language not found");
return '';
}
$targetLanguageName = $languageObject['name'];
$response = Client::fetch('https://api.openai.com/v1/chat/completions', [
'content-type' => Client::CONTENT_TYPE_APPLICATION_JSON,
'Authorization' => 'Bearer ' . $this->apiKey
], Client::METHOD_POST, [
'model' => 'gpt-4-1106-preview', // https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo
'messages' => [
[
'role' => 'system',
'content' => "Please translate the message user provides from English language to {$targetLanguageName}. Do not translate text inside {{ and }} placeholders. Provide only translated text."
],
[
'role' => 'user',
'content' => $enTranslation
]
]
], [], 60);
$body = \json_decode($response->getBody(), true);
if ($response->getStatusCode() >= 400) {
throw new Exception($response->getBody() . ' with status code ' . $response->getStatusCode() . ' for language ' . $targetLanguage . ' and message ' . $enTranslation);
}
$answer = $body['choices'][0]['message']['content'];
$failureDetectors = [ 'sorry', 'confusion', 'country code', 'misunderstanding', 'correct', 'clarify', 'specific', 'cannot', 'unable', 'language', 'appears' ];
foreach ($failureDetectors as $detector) {
if (\str_contains($answer, $detector)) {
Console::error("Translation of '{$enTranslation}' for {$targetLanguage} is incorrect: {$answer}");
}
}
return $answer;
}
}
@@ -1,187 +0,0 @@
<?php
namespace Appwrite\Platform\Tasks;
use League\Csv\CannotInsertRecord;
use League\Csv\Writer;
use PHPMailer\PHPMailer\PHPMailer;
use Utopia\App;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Query;
use Utopia\Platform\Action;
use Utopia\Pools\Group;
use Utopia\Registry\Registry;
use Utopia\System\System;
class GetMigrationStats extends Action
{
/*
* Csv cols headers
*/
private array $columns = [
'Project ID',
'$id',
'$createdAt',
'status',
'stage',
'source'
];
protected string $directory = '/usr/local';
protected string $path;
protected string $date;
public static function getName(): string
{
return 'get-migration-stats';
}
public function __construct()
{
$this
->desc('Get stats for projects')
->inject('pools')
->inject('cache')
->inject('dbForConsole')
->inject('register')
->callback(function (Group $pools, Cache $cache, Database $dbForConsole, Registry $register) {
$this->action($pools, $cache, $dbForConsole, $register);
});
}
/**
* @throws \Utopia\Exception
* @throws CannotInsertRecord
*/
public function action(Group $pools, Cache $cache, Database $dbForConsole, Registry $register): void
{
//docker compose exec -t appwrite get-migration-stats
Console::title('Migration stats calculation V1');
Console::success(APP_NAME . ' Migration stats calculation has started');
/* Initialise new Utopia app */
$app = new App('UTC');
$console = $app->getResource('console');
/** CSV stuff */
$this->date = date('Y-m-d');
$this->path = "{$this->directory}/migration_stats_{$this->date}.csv";
$csv = Writer::createFromPath($this->path, 'w');
$csv->insertOne($this->columns);
/** Database connections */
$totalProjects = $dbForConsole->count('projects');
Console::success("Found a total of: {$totalProjects} projects");
$projects = [$console];
$count = 0;
$limit = 100;
$sum = 100;
$offset = 0;
while (!empty($projects)) {
foreach ($projects as $project) {
/**
* Skip user projects with id 'console'
*/
if ($project->getId() === 'console') {
continue;
}
Console::info("Getting stats for {$project->getId()}");
try {
$db = $project->getAttribute('database');
$adapter = $pools
->get($db)
->pop()
->getResource();
$dbForProject = new Database($adapter, $cache);
$dbForProject->setDefaultDatabase('appwrite');
$dbForProject->setNamespace('_' . $project->getInternalId());
/** Get Project ID */
$stats['Project ID'] = $project->getId();
/** Get Migration details */
$migrations = $dbForProject->find('migrations', [
Query::limit(500)
]);
$migrations = array_map(function ($migration) use ($project) {
return [
$project->getId(),
$migration->getAttribute('$id'),
$migration->getAttribute('$createdAt'),
$migration->getAttribute('status'),
$migration->getAttribute('stage'),
$migration->getAttribute('source'),
];
}, $migrations);
if (!empty($migrations)) {
$csv->insertAll($migrations);
}
} catch (\Throwable $th) {
Console::error('Failed on project ("' . $project->getId() . '") with error on File: ' . $th->getFile() . ' line no: ' . $th->getline() . ' with message: ' . $th->getMessage());
} finally {
$pools
->get($db)
->reclaim();
}
}
$sum = \count($projects);
$projects = $dbForConsole->find('projects', [
Query::limit($limit),
Query::offset($offset),
]);
$offset = $offset + $limit;
$count = $count + $sum;
}
Console::log('Iterated through ' . $count - 1 . '/' . $totalProjects . ' projects...');
$pools
->get('console')
->reclaim();
/** @var PHPMailer $mail */
$mail = $register->get('smtp');
$mail->clearAddresses();
$mail->clearAllRecipients();
$mail->clearReplyTos();
$mail->clearAttachments();
$mail->clearBCCs();
$mail->clearCCs();
try {
/** Addresses */
$mail->setFrom(System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), 'Appwrite Cloud Hamster');
$recipients = explode(',', System::getEnv('_APP_USERS_STATS_RECIPIENTS', ''));
foreach ($recipients as $recipient) {
$mail->addAddress($recipient);
}
/** Attachments */
$mail->addAttachment($this->path);
/** Content */
$mail->Subject = "Migration Report for {$this->date}";
$mail->Body = "Please find the migration report atttached";
$mail->send();
Console::success('Email has been sent!');
} catch (\Throwable $e) {
Console::error("Message could not be sent. Mailer Error: {$mail->ErrorInfo}");
}
}
}
-157
View File
@@ -1,157 +0,0 @@
<?php
namespace Appwrite\Platform\Tasks;
use Appwrite\Event\Hamster as EventHamster;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Platform\Action;
use Utopia\System\System;
class Hamster extends Action
{
public static function getName(): string
{
return 'hamster';
}
public function __construct()
{
$this
->desc('Get stats for projects')
->inject('queueForHamster')
->inject('dbForConsole')
->callback(function (EventHamster $queueForHamster, Database $dbForConsole) {
$this->action($queueForHamster, $dbForConsole);
});
}
public function action(EventHamster $queueForHamster, Database $dbForConsole): void
{
Console::title('Cloud Hamster V1');
Console::success(APP_NAME . ' cloud hamster process has started');
$sleep = (int) System::getEnv('_APP_HAMSTER_INTERVAL', '30'); // 30 seconds (by default)
$jobInitTime = System::getEnv('_APP_HAMSTER_TIME', '22:00'); // (hour:minutes)
$now = new \DateTime();
$now->setTimezone(new \DateTimeZone(date_default_timezone_get()));
$next = new \DateTime($now->format("Y-m-d $jobInitTime"));
$next->setTimezone(new \DateTimeZone(date_default_timezone_get()));
$delay = $next->getTimestamp() - $now->getTimestamp();
/**
* If time passed for the target day.
*/
if ($delay <= 0) {
$next->add(\DateInterval::createFromDateString('1 days'));
$delay = $next->getTimestamp() - $now->getTimestamp();
}
Console::log('[' . $now->format("Y-m-d H:i:s.v") . '] Delaying for ' . $delay . ' setting loop to [' . $next->format("Y-m-d H:i:s.v") . ']');
Console::loop(function () use ($queueForHamster, $dbForConsole, $sleep) {
$now = date('d-m-Y H:i:s', time());
Console::info("[{$now}] Queuing Cloud Usage Stats every {$sleep} seconds");
$loopStart = microtime(true);
Console::info('Queuing stats for all projects');
$this->getStatsPerProject($queueForHamster, $dbForConsole, $loopStart);
Console::success('Completed queuing stats for all projects');
Console::info('Queuing stats for all organizations');
$this->getStatsPerOrganization($queueForHamster, $dbForConsole, $loopStart);
Console::success('Completed queuing stats for all organizations');
Console::info('Queuing stats for all users');
$this->getStatsPerUser($queueForHamster, $dbForConsole, $loopStart);
Console::success('Completed queuing stats for all users');
$loopTook = microtime(true) - $loopStart;
$now = date('d-m-Y H:i:s', time());
Console::info("[{$now}] Cloud Stats took {$loopTook} seconds");
}, $sleep, $delay);
}
protected function calculateByGroup(string $collection, Database $database, callable $callback)
{
$count = 0;
$chunk = 0;
$limit = 50;
$results = [];
$sum = $limit;
$executionStart = \microtime(true);
while ($sum === $limit) {
$chunk++;
$results = $database->find($collection, \array_merge([
Query::limit($limit),
Query::offset($count)
]));
$sum = count($results);
Console::log('Processing chunk #' . $chunk . '. Found ' . $sum . ' documents');
foreach ($results as $document) {
call_user_func($callback, $database, $document);
$count++;
}
}
$executionEnd = \microtime(true);
Console::log("Processed {$count} document by group in " . ($executionEnd - $executionStart) . " seconds");
}
protected function getStatsPerOrganization(EventHamster $hamster, Database $dbForConsole, float $loopStart)
{
$this->calculateByGroup('teams', $dbForConsole, function (Database $dbForConsole, Document $organization) use ($hamster, $loopStart) {
try {
$organization->setAttribute('$time', $loopStart);
$hamster
->setType(EventHamster::TYPE_ORGANISATION)
->setOrganization($organization)
->trigger();
} catch (\Throwable $e) {
Console::error($e->getMessage());
}
});
}
private function getStatsPerProject(EventHamster $hamster, Database $dbForConsole, float $loopStart)
{
$this->calculateByGroup('projects', $dbForConsole, function (Database $dbForConsole, Document $project) use ($hamster, $loopStart) {
try {
$project->setAttribute('$time', $loopStart);
$hamster
->setType(EventHamster::TYPE_PROJECT)
->setProject($project)
->trigger();
} catch (\Throwable $e) {
Console::error($e->getMessage());
}
});
}
protected function getStatsPerUser(EventHamster $hamster, Database $dbForConsole, float $loopStart)
{
$this->calculateByGroup('users', $dbForConsole, function (Database $dbForConsole, Document $user) use ($hamster, $loopStart) {
try {
$user->setAttribute('$time', $loopStart);
$hamster
->setType(EventHamster::TYPE_USER)
->setUser($user)
->trigger();
} catch (\Throwable $e) {
Console::error($e->getMessage());
}
});
}
}

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