mirror of
https://github.com/appwrite/appwrite
synced 2026-05-24 09:28:40 +00:00
Merge branch '1.8.x' of https://github.com/appwrite/appwrite into delete-subscribers
This commit is contained in:
commit
c34873a8b0
15 changed files with 491 additions and 258 deletions
|
|
@ -77,6 +77,7 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||||
chmod +x /usr/local/bin/queue-count-success && \
|
chmod +x /usr/local/bin/queue-count-success && \
|
||||||
chmod +x /usr/local/bin/worker-audits && \
|
chmod +x /usr/local/bin/worker-audits && \
|
||||||
chmod +x /usr/local/bin/worker-builds && \
|
chmod +x /usr/local/bin/worker-builds && \
|
||||||
|
chmod +x /usr/local/bin/worker-screenshots && \
|
||||||
chmod +x /usr/local/bin/worker-certificates && \
|
chmod +x /usr/local/bin/worker-certificates && \
|
||||||
chmod +x /usr/local/bin/worker-databases && \
|
chmod +x /usr/local/bin/worker-databases && \
|
||||||
chmod +x /usr/local/bin/worker-deletes && \
|
chmod +x /usr/local/bin/worker-deletes && \
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ use Appwrite\Event\Func;
|
||||||
use Appwrite\Event\Mail;
|
use Appwrite\Event\Mail;
|
||||||
use Appwrite\Event\Messaging;
|
use Appwrite\Event\Messaging;
|
||||||
use Appwrite\Event\Migration;
|
use Appwrite\Event\Migration;
|
||||||
|
use Appwrite\Event\Screenshot;
|
||||||
use Appwrite\Event\StatsResources;
|
use Appwrite\Event\StatsResources;
|
||||||
use Appwrite\Event\StatsUsage;
|
use Appwrite\Event\StatsUsage;
|
||||||
use Appwrite\Event\Webhook;
|
use Appwrite\Event\Webhook;
|
||||||
|
|
@ -955,6 +956,7 @@ App::get('/v1/health/queue/failed/:name')
|
||||||
System::getEnv('_APP_WEBHOOK_QUEUE_NAME', Event::WEBHOOK_QUEUE_NAME),
|
System::getEnv('_APP_WEBHOOK_QUEUE_NAME', Event::WEBHOOK_QUEUE_NAME),
|
||||||
System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME),
|
System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME),
|
||||||
System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME),
|
System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME),
|
||||||
|
System::getEnv('_APP_SCREENSHOTS_QUEUE_NAME', Event::SCREENSHOTS_QUEUE_NAME),
|
||||||
System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME),
|
System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME),
|
||||||
System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME)
|
System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME)
|
||||||
]), 'The name of the queue')
|
]), 'The name of the queue')
|
||||||
|
|
@ -972,6 +974,7 @@ App::get('/v1/health/queue/failed/:name')
|
||||||
->inject('queueForBuilds')
|
->inject('queueForBuilds')
|
||||||
->inject('queueForMessaging')
|
->inject('queueForMessaging')
|
||||||
->inject('queueForMigrations')
|
->inject('queueForMigrations')
|
||||||
|
->inject('queueForScreenshots')
|
||||||
->action(function (
|
->action(function (
|
||||||
string $name,
|
string $name,
|
||||||
int|string $threshold,
|
int|string $threshold,
|
||||||
|
|
@ -987,7 +990,8 @@ App::get('/v1/health/queue/failed/:name')
|
||||||
Certificate $queueForCertificates,
|
Certificate $queueForCertificates,
|
||||||
Build $queueForBuilds,
|
Build $queueForBuilds,
|
||||||
Messaging $queueForMessaging,
|
Messaging $queueForMessaging,
|
||||||
Migration $queueForMigrations
|
Migration $queueForMigrations,
|
||||||
|
Screenshot $queueForScreenshots,
|
||||||
) {
|
) {
|
||||||
$threshold = \intval($threshold);
|
$threshold = \intval($threshold);
|
||||||
|
|
||||||
|
|
@ -1003,6 +1007,7 @@ App::get('/v1/health/queue/failed/:name')
|
||||||
System::getEnv('_APP_WEBHOOK_QUEUE_NAME', Event::WEBHOOK_QUEUE_NAME) => $queueForWebhooks,
|
System::getEnv('_APP_WEBHOOK_QUEUE_NAME', Event::WEBHOOK_QUEUE_NAME) => $queueForWebhooks,
|
||||||
System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME) => $queueForCertificates,
|
System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME) => $queueForCertificates,
|
||||||
System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME) => $queueForBuilds,
|
System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME) => $queueForBuilds,
|
||||||
|
System::getEnv('_APP_SCREENSHOTS_QUEUE_NAME', Event::SCREENSHOTS_QUEUE_NAME) => $queueForScreenshots,
|
||||||
System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME) => $queueForMessaging,
|
System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME) => $queueForMessaging,
|
||||||
System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME) => $queueForMigrations,
|
System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME) => $queueForMigrations,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ use Appwrite\Event\Mail;
|
||||||
use Appwrite\Event\Messaging;
|
use Appwrite\Event\Messaging;
|
||||||
use Appwrite\Event\Migration;
|
use Appwrite\Event\Migration;
|
||||||
use Appwrite\Event\Realtime;
|
use Appwrite\Event\Realtime;
|
||||||
|
use Appwrite\Event\Screenshot;
|
||||||
use Appwrite\Event\StatsResources;
|
use Appwrite\Event\StatsResources;
|
||||||
use Appwrite\Event\StatsUsage;
|
use Appwrite\Event\StatsUsage;
|
||||||
use Appwrite\Event\Webhook;
|
use Appwrite\Event\Webhook;
|
||||||
|
|
@ -129,6 +130,9 @@ App::setResource('queueForMails', function (Publisher $publisher) {
|
||||||
App::setResource('queueForBuilds', function (Publisher $publisher) {
|
App::setResource('queueForBuilds', function (Publisher $publisher) {
|
||||||
return new Build($publisher);
|
return new Build($publisher);
|
||||||
}, ['publisher']);
|
}, ['publisher']);
|
||||||
|
App::setResource('queueForScreenshots', function (Publisher $publisher) {
|
||||||
|
return new Screenshot($publisher);
|
||||||
|
}, ['publisher']);
|
||||||
App::setResource('queueForDatabase', function (Publisher $publisher) {
|
App::setResource('queueForDatabase', function (Publisher $publisher) {
|
||||||
return new EventDatabase($publisher);
|
return new EventDatabase($publisher);
|
||||||
}, ['publisher']);
|
}, ['publisher']);
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ use Appwrite\Event\Mail;
|
||||||
use Appwrite\Event\Messaging;
|
use Appwrite\Event\Messaging;
|
||||||
use Appwrite\Event\Migration;
|
use Appwrite\Event\Migration;
|
||||||
use Appwrite\Event\Realtime;
|
use Appwrite\Event\Realtime;
|
||||||
|
use Appwrite\Event\Screenshot;
|
||||||
use Appwrite\Event\StatsUsage;
|
use Appwrite\Event\StatsUsage;
|
||||||
use Appwrite\Event\Webhook;
|
use Appwrite\Event\Webhook;
|
||||||
use Appwrite\Platform\Appwrite;
|
use Appwrite\Platform\Appwrite;
|
||||||
|
|
@ -307,6 +308,10 @@ Server::setResource('queueForBuilds', function (Publisher $publisher) {
|
||||||
return new Build($publisher);
|
return new Build($publisher);
|
||||||
}, ['publisher']);
|
}, ['publisher']);
|
||||||
|
|
||||||
|
Server::setResource('queueForScreenshots', function (Publisher $publisher) {
|
||||||
|
return new Screenshot($publisher);
|
||||||
|
}, ['publisher']);
|
||||||
|
|
||||||
Server::setResource('queueForDeletes', function (Publisher $publisher) {
|
Server::setResource('queueForDeletes', function (Publisher $publisher) {
|
||||||
return new Delete($publisher);
|
return new Delete($publisher);
|
||||||
}, ['publisher']);
|
}, ['publisher']);
|
||||||
|
|
@ -544,9 +549,4 @@ $worker
|
||||||
Console::error('[Error] Line: ' . $error->getLine());
|
Console::error('[Error] Line: ' . $error->getLine());
|
||||||
});
|
});
|
||||||
|
|
||||||
$worker->workerStart()
|
|
||||||
->action(function () use ($workerName) {
|
|
||||||
Console::info("Worker $workerName started");
|
|
||||||
});
|
|
||||||
|
|
||||||
$worker->start();
|
$worker->start();
|
||||||
|
|
|
||||||
3
bin/worker-screenshots
Normal file
3
bin/worker-screenshots
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
exec php /usr/src/code/app/worker.php screenshots "$@"
|
||||||
|
|
@ -69,7 +69,7 @@
|
||||||
"utopia-php/platform": "0.7.*",
|
"utopia-php/platform": "0.7.*",
|
||||||
"utopia-php/pools": "0.8.*",
|
"utopia-php/pools": "0.8.*",
|
||||||
"utopia-php/preloader": "0.2.*",
|
"utopia-php/preloader": "0.2.*",
|
||||||
"utopia-php/queue": "0.11.*",
|
"utopia-php/queue": "0.15.*",
|
||||||
"utopia-php/registry": "0.5.*",
|
"utopia-php/registry": "0.5.*",
|
||||||
"utopia-php/storage": "0.18.*",
|
"utopia-php/storage": "0.18.*",
|
||||||
"utopia-php/swoole": "0.8.*",
|
"utopia-php/swoole": "0.8.*",
|
||||||
|
|
@ -100,12 +100,6 @@
|
||||||
"provide": {
|
"provide": {
|
||||||
"ext-phpiredis": "*"
|
"ext-phpiredis": "*"
|
||||||
},
|
},
|
||||||
"repositories": [
|
|
||||||
{
|
|
||||||
"type": "vcs",
|
|
||||||
"url": "https://github.com/utopia-php/migration.git"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"config": {
|
"config": {
|
||||||
"platform": {
|
"platform": {
|
||||||
"php": "8.3"
|
"php": "8.3"
|
||||||
|
|
@ -115,4 +109,4 @@
|
||||||
"tbachert/spi": true
|
"tbachert/spi": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
64
composer.lock
generated
64
composer.lock
generated
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "375a062e8675e7e6938c1d8cc7b61ecf",
|
"content-hash": "ff3172688b600aa3c560131c9d9c5588",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "adhocore/jwt",
|
"name": "adhocore/jwt",
|
||||||
|
|
@ -4516,16 +4516,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "utopia-php/migration",
|
"name": "utopia-php/migration",
|
||||||
"version": "1.3.12",
|
"version": "1.3.13",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/utopia-php/migration.git",
|
"url": "https://github.com/utopia-php/migration.git",
|
||||||
"reference": "1b8d5519c50630e4c0b6a79be615b70d5f23d2e4"
|
"reference": "c5e3f5e970e62e8f7db97b5b90baae2af800a715"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/1b8d5519c50630e4c0b6a79be615b70d5f23d2e4",
|
"url": "https://api.github.com/repos/utopia-php/migration/zipball/c5e3f5e970e62e8f7db97b5b90baae2af800a715",
|
||||||
"reference": "1b8d5519c50630e4c0b6a79be615b70d5f23d2e4",
|
"reference": "c5e3f5e970e62e8f7db97b5b90baae2af800a715",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
|
@ -4551,25 +4551,7 @@
|
||||||
"Utopia\\Migration\\": "src/Migration"
|
"Utopia\\Migration\\": "src/Migration"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
"psr-4": {
|
|
||||||
"Utopia\\Tests\\": "tests/Migration"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"test": [
|
|
||||||
"./vendor/bin/phpunit"
|
|
||||||
],
|
|
||||||
"lint": [
|
|
||||||
"./vendor/bin/pint --test"
|
|
||||||
],
|
|
||||||
"format": [
|
|
||||||
"./vendor/bin/pint"
|
|
||||||
],
|
|
||||||
"check": [
|
|
||||||
"./vendor/bin/phpstan analyse --level 3 src tests --memory-limit 2G"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"license": [
|
"license": [
|
||||||
"MIT"
|
"MIT"
|
||||||
],
|
],
|
||||||
|
|
@ -4582,10 +4564,10 @@
|
||||||
"utopia"
|
"utopia"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/utopia-php/migration/tree/1.3.12",
|
"issues": "https://github.com/utopia-php/migration/issues",
|
||||||
"issues": "https://github.com/utopia-php/migration/issues"
|
"source": "https://github.com/utopia-php/migration/tree/1.3.13"
|
||||||
},
|
},
|
||||||
"time": "2026-01-07T06:07:33+00:00"
|
"time": "2026-01-07T14:48:05+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "utopia-php/mongo",
|
"name": "utopia-php/mongo",
|
||||||
|
|
@ -4700,16 +4682,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "utopia-php/platform",
|
"name": "utopia-php/platform",
|
||||||
"version": "0.7.13",
|
"version": "0.7.14",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/utopia-php/platform.git",
|
"url": "https://github.com/utopia-php/platform.git",
|
||||||
"reference": "77a863a920122e2c6a6bc6ee5548d366a3f4c6c7"
|
"reference": "9f18ce63f1425ae2dae57468200e4a5d1239d57b"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/utopia-php/platform/zipball/77a863a920122e2c6a6bc6ee5548d366a3f4c6c7",
|
"url": "https://api.github.com/repos/utopia-php/platform/zipball/9f18ce63f1425ae2dae57468200e4a5d1239d57b",
|
||||||
"reference": "77a863a920122e2c6a6bc6ee5548d366a3f4c6c7",
|
"reference": "9f18ce63f1425ae2dae57468200e4a5d1239d57b",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
|
@ -4718,7 +4700,7 @@
|
||||||
"php": ">=8.0",
|
"php": ">=8.0",
|
||||||
"utopia-php/cli": "0.15.*",
|
"utopia-php/cli": "0.15.*",
|
||||||
"utopia-php/framework": "0.33.*",
|
"utopia-php/framework": "0.33.*",
|
||||||
"utopia-php/queue": "0.11.*"
|
"utopia-php/queue": "0.15.*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"laravel/pint": "1.*",
|
"laravel/pint": "1.*",
|
||||||
|
|
@ -4745,9 +4727,9 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/utopia-php/platform/issues",
|
"issues": "https://github.com/utopia-php/platform/issues",
|
||||||
"source": "https://github.com/utopia-php/platform/tree/0.7.13"
|
"source": "https://github.com/utopia-php/platform/tree/0.7.14"
|
||||||
},
|
},
|
||||||
"time": "2025-12-08T10:02:40+00:00"
|
"time": "2026-01-06T15:39:45+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "utopia-php/pools",
|
"name": "utopia-php/pools",
|
||||||
|
|
@ -4856,22 +4838,22 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "utopia-php/queue",
|
"name": "utopia-php/queue",
|
||||||
"version": "0.11.3",
|
"version": "0.15.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/utopia-php/queue.git",
|
"url": "https://github.com/utopia-php/queue.git",
|
||||||
"reference": "f3b2623efe87595c9ed907b3efd587e77c622d3d"
|
"reference": "6abb268ba7ec00dea4e5201b007776ea1bce9242"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/utopia-php/queue/zipball/f3b2623efe87595c9ed907b3efd587e77c622d3d",
|
"url": "https://api.github.com/repos/utopia-php/queue/zipball/6abb268ba7ec00dea4e5201b007776ea1bce9242",
|
||||||
"reference": "f3b2623efe87595c9ed907b3efd587e77c622d3d",
|
"reference": "6abb268ba7ec00dea4e5201b007776ea1bce9242",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=8.3",
|
"php": ">=8.3",
|
||||||
"php-amqplib/php-amqplib": "^3.7",
|
"php-amqplib/php-amqplib": "^3.7",
|
||||||
"utopia-php/cli": "0.15.*",
|
"utopia-php/console": "0.0.*",
|
||||||
"utopia-php/fetch": "0.5.*",
|
"utopia-php/fetch": "0.5.*",
|
||||||
"utopia-php/framework": "0.33.*",
|
"utopia-php/framework": "0.33.*",
|
||||||
"utopia-php/pools": "0.8.*",
|
"utopia-php/pools": "0.8.*",
|
||||||
|
|
@ -4916,9 +4898,9 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/utopia-php/queue/issues",
|
"issues": "https://github.com/utopia-php/queue/issues",
|
||||||
"source": "https://github.com/utopia-php/queue/tree/0.11.3"
|
"source": "https://github.com/utopia-php/queue/tree/0.15.0"
|
||||||
},
|
},
|
||||||
"time": "2025-12-19T10:56:22+00:00"
|
"time": "2026-01-06T12:41:51+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "utopia-php/registry",
|
"name": "utopia-php/registry",
|
||||||
|
|
|
||||||
|
|
@ -466,14 +466,12 @@ services:
|
||||||
- appwrite-functions:/storage/functions:rw
|
- appwrite-functions:/storage/functions:rw
|
||||||
- appwrite-sites:/storage/sites:rw
|
- appwrite-sites:/storage/sites:rw
|
||||||
- appwrite-builds:/storage/builds:rw
|
- appwrite-builds:/storage/builds:rw
|
||||||
- appwrite-uploads:/storage/uploads:rw
|
|
||||||
- ./app:/usr/src/code/app
|
- ./app:/usr/src/code/app
|
||||||
- ./src:/usr/src/code/src
|
- ./src:/usr/src/code/src
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
- mariadb
|
- mariadb
|
||||||
environment:
|
environment:
|
||||||
- _APP_BROWSER_HOST
|
|
||||||
- _APP_ENV
|
- _APP_ENV
|
||||||
- _APP_WORKER_PER_CORE
|
- _APP_WORKER_PER_CORE
|
||||||
- _APP_OPENSSL_KEY_V1
|
- _APP_OPENSSL_KEY_V1
|
||||||
|
|
@ -529,6 +527,65 @@ services:
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "host.docker.internal:host-gateway"
|
- "host.docker.internal:host-gateway"
|
||||||
|
|
||||||
|
appwrite-worker-screenshots:
|
||||||
|
entrypoint: worker-screenshots
|
||||||
|
<<: *x-logging
|
||||||
|
container_name: appwrite-worker-screenshots
|
||||||
|
image: appwrite-dev
|
||||||
|
networks:
|
||||||
|
- appwrite
|
||||||
|
volumes:
|
||||||
|
- appwrite-uploads:/storage/uploads:rw
|
||||||
|
- ./app:/usr/src/code/app
|
||||||
|
- ./src:/usr/src/code/src
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
- mariadb
|
||||||
|
environment:
|
||||||
|
# Specific
|
||||||
|
- _APP_BROWSER_HOST
|
||||||
|
# Basic
|
||||||
|
- _APP_ENV
|
||||||
|
- _APP_WORKER_PER_CORE
|
||||||
|
- _APP_LOGGING_CONFIG
|
||||||
|
# Database
|
||||||
|
- _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_DATABASE_SHARED_TABLES
|
||||||
|
# Storage
|
||||||
|
- _APP_STORAGE_DEVICE
|
||||||
|
- _APP_STORAGE_S3_ACCESS_KEY
|
||||||
|
- _APP_STORAGE_S3_SECRET
|
||||||
|
- _APP_STORAGE_S3_REGION
|
||||||
|
- _APP_STORAGE_S3_BUCKET
|
||||||
|
- _APP_STORAGE_S3_ENDPOINT
|
||||||
|
- _APP_STORAGE_DO_SPACES_ACCESS_KEY
|
||||||
|
- _APP_STORAGE_DO_SPACES_SECRET
|
||||||
|
- _APP_STORAGE_DO_SPACES_REGION
|
||||||
|
- _APP_STORAGE_DO_SPACES_BUCKET
|
||||||
|
- _APP_STORAGE_BACKBLAZE_ACCESS_KEY
|
||||||
|
- _APP_STORAGE_BACKBLAZE_SECRET
|
||||||
|
- _APP_STORAGE_BACKBLAZE_REGION
|
||||||
|
- _APP_STORAGE_BACKBLAZE_BUCKET
|
||||||
|
- _APP_STORAGE_LINODE_ACCESS_KEY
|
||||||
|
- _APP_STORAGE_LINODE_SECRET
|
||||||
|
- _APP_STORAGE_LINODE_REGION
|
||||||
|
- _APP_STORAGE_LINODE_BUCKET
|
||||||
|
- _APP_STORAGE_WASABI_ACCESS_KEY
|
||||||
|
- _APP_STORAGE_WASABI_SECRET
|
||||||
|
- _APP_STORAGE_WASABI_REGION
|
||||||
|
- _APP_STORAGE_WASABI_BUCKET
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
|
|
||||||
appwrite-worker-certificates:
|
appwrite-worker-certificates:
|
||||||
entrypoint: worker-certificates
|
entrypoint: worker-certificates
|
||||||
<<: *x-logging
|
<<: *x-logging
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,9 @@ class Event
|
||||||
public const BUILDS_QUEUE_NAME = 'v1-builds';
|
public const BUILDS_QUEUE_NAME = 'v1-builds';
|
||||||
public const BUILDS_CLASS_NAME = 'BuildsV1';
|
public const BUILDS_CLASS_NAME = 'BuildsV1';
|
||||||
|
|
||||||
|
public const SCREENSHOTS_QUEUE_NAME = 'v1-screenshots';
|
||||||
|
public const SCREENSHOTS_CLASS_NAME = 'ScreenshotsV1';
|
||||||
|
|
||||||
public const MESSAGING_QUEUE_NAME = 'v1-messaging';
|
public const MESSAGING_QUEUE_NAME = 'v1-messaging';
|
||||||
public const MESSAGING_CLASS_NAME = 'MessagingV1';
|
public const MESSAGING_CLASS_NAME = 'MessagingV1';
|
||||||
|
|
||||||
|
|
|
||||||
50
src/Appwrite/Event/Screenshot.php
Normal file
50
src/Appwrite/Event/Screenshot.php
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Appwrite\Event;
|
||||||
|
|
||||||
|
use Utopia\Config\Config;
|
||||||
|
use Utopia\Queue\Publisher;
|
||||||
|
use Utopia\System\System;
|
||||||
|
|
||||||
|
class Screenshot extends Event
|
||||||
|
{
|
||||||
|
protected string $deploymentId = '';
|
||||||
|
|
||||||
|
public function __construct(protected Publisher $publisher)
|
||||||
|
{
|
||||||
|
parent::__construct($publisher);
|
||||||
|
|
||||||
|
$this
|
||||||
|
->setQueue(System::getEnv('_APP_SCREENSHOTS_QUEUE_NAME', Event::SCREENSHOTS_QUEUE_NAME))
|
||||||
|
->setClass(System::getEnv('_APP_SCREENSHOTS_CLASS_NAME', Event::SCREENSHOTS_CLASS_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDeploymentId(string $deploymentId): self
|
||||||
|
{
|
||||||
|
$this->deploymentId = $deploymentId;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function preparePayload(): array
|
||||||
|
{
|
||||||
|
$platform = $this->platform;
|
||||||
|
if (empty($platform)) {
|
||||||
|
$platform = Config::getParam('platform', []);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'project' => $this->project,
|
||||||
|
'deploymentId' => $this->deploymentId,
|
||||||
|
'platform' => $platform,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reset(): self
|
||||||
|
{
|
||||||
|
$this->deploymentId = '';
|
||||||
|
parent::reset();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
namespace Appwrite\Platform\Modules\Functions\Services;
|
namespace Appwrite\Platform\Modules\Functions\Services;
|
||||||
|
|
||||||
use Appwrite\Platform\Modules\Functions\Workers\Builds;
|
use Appwrite\Platform\Modules\Functions\Workers\Builds;
|
||||||
|
use Appwrite\Platform\Modules\Functions\Workers\Screenshots;
|
||||||
use Utopia\Platform\Service;
|
use Utopia\Platform\Service;
|
||||||
|
|
||||||
class Workers extends Service
|
class Workers extends Service
|
||||||
|
|
@ -11,5 +12,6 @@ class Workers extends Service
|
||||||
{
|
{
|
||||||
$this->type = Service::TYPE_WORKER;
|
$this->type = Service::TYPE_WORKER;
|
||||||
$this->addAction(Builds::getName(), new Builds());
|
$this->addAction(Builds::getName(), new Builds());
|
||||||
|
$this->addAction(Screenshots::getName(), new Screenshots());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,9 @@ use Ahc\Jwt\JWT;
|
||||||
use Appwrite\Event\Event;
|
use Appwrite\Event\Event;
|
||||||
use Appwrite\Event\Func;
|
use Appwrite\Event\Func;
|
||||||
use Appwrite\Event\Realtime;
|
use Appwrite\Event\Realtime;
|
||||||
|
use Appwrite\Event\Screenshot;
|
||||||
use Appwrite\Event\StatsUsage;
|
use Appwrite\Event\StatsUsage;
|
||||||
use Appwrite\Event\Webhook;
|
use Appwrite\Event\Webhook;
|
||||||
use Appwrite\Permission;
|
|
||||||
use Appwrite\Role;
|
|
||||||
use Appwrite\Utopia\Response\Model\Deployment;
|
use Appwrite\Utopia\Response\Model\Deployment;
|
||||||
use Appwrite\Vcs\Comment;
|
use Appwrite\Vcs\Comment;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
|
@ -25,24 +24,19 @@ use Utopia\Database\Exception\Conflict;
|
||||||
use Utopia\Database\Exception\Duplicate;
|
use Utopia\Database\Exception\Duplicate;
|
||||||
use Utopia\Database\Exception\Restricted;
|
use Utopia\Database\Exception\Restricted;
|
||||||
use Utopia\Database\Exception\Structure;
|
use Utopia\Database\Exception\Structure;
|
||||||
use Utopia\Database\Helpers\ID;
|
|
||||||
use Utopia\Database\Query;
|
use Utopia\Database\Query;
|
||||||
use Utopia\Database\Validator\Authorization;
|
use Utopia\Database\Validator\Authorization;
|
||||||
use Utopia\Detector\Detection\Rendering\SSR;
|
use Utopia\Detector\Detection\Rendering\SSR;
|
||||||
use Utopia\Detector\Detection\Rendering\XStatic;
|
use Utopia\Detector\Detection\Rendering\XStatic;
|
||||||
use Utopia\Detector\Detector\Rendering;
|
use Utopia\Detector\Detector\Rendering;
|
||||||
use Utopia\Fetch\Client as FetchClient;
|
|
||||||
use Utopia\Logger\Log;
|
use Utopia\Logger\Log;
|
||||||
use Utopia\Platform\Action;
|
use Utopia\Platform\Action;
|
||||||
use Utopia\Queue\Message;
|
use Utopia\Queue\Message;
|
||||||
use Utopia\Storage\Compression\Compression;
|
|
||||||
use Utopia\Storage\Device;
|
use Utopia\Storage\Device;
|
||||||
use Utopia\Storage\Device\Local;
|
use Utopia\Storage\Device\Local;
|
||||||
use Utopia\System\System;
|
use Utopia\System\System;
|
||||||
use Utopia\VCS\Adapter\Git\GitHub;
|
use Utopia\VCS\Adapter\Git\GitHub;
|
||||||
|
|
||||||
use function Swoole\Coroutine\batch;
|
|
||||||
|
|
||||||
class Builds extends Action
|
class Builds extends Action
|
||||||
{
|
{
|
||||||
public static function getName(): string
|
public static function getName(): string
|
||||||
|
|
@ -62,6 +56,7 @@ class Builds extends Action
|
||||||
->inject('project')
|
->inject('project')
|
||||||
->inject('dbForPlatform')
|
->inject('dbForPlatform')
|
||||||
->inject('queueForEvents')
|
->inject('queueForEvents')
|
||||||
|
->inject('queueForScreenshots')
|
||||||
->inject('queueForWebhooks')
|
->inject('queueForWebhooks')
|
||||||
->inject('queueForFunctions')
|
->inject('queueForFunctions')
|
||||||
->inject('queueForRealtime')
|
->inject('queueForRealtime')
|
||||||
|
|
@ -83,6 +78,7 @@ class Builds extends Action
|
||||||
* @param Document $project
|
* @param Document $project
|
||||||
* @param Database $dbForPlatform
|
* @param Database $dbForPlatform
|
||||||
* @param Event $queueForEvents
|
* @param Event $queueForEvents
|
||||||
|
* @param Screenshot $queueForScreenshots
|
||||||
* @param Webhook $queueForWebhooks
|
* @param Webhook $queueForWebhooks
|
||||||
* @param Func $queueForFunctions
|
* @param Func $queueForFunctions
|
||||||
* @param Realtime $queueForRealtime
|
* @param Realtime $queueForRealtime
|
||||||
|
|
@ -103,6 +99,7 @@ class Builds extends Action
|
||||||
Document $project,
|
Document $project,
|
||||||
Database $dbForPlatform,
|
Database $dbForPlatform,
|
||||||
Event $queueForEvents,
|
Event $queueForEvents,
|
||||||
|
Screenshot $queueForScreenshots,
|
||||||
Webhook $queueForWebhooks,
|
Webhook $queueForWebhooks,
|
||||||
Func $queueForFunctions,
|
Func $queueForFunctions,
|
||||||
Realtime $queueForRealtime,
|
Realtime $queueForRealtime,
|
||||||
|
|
@ -143,6 +140,7 @@ class Builds extends Action
|
||||||
$deviceForFunctions,
|
$deviceForFunctions,
|
||||||
$deviceForSites,
|
$deviceForSites,
|
||||||
$deviceForFiles,
|
$deviceForFiles,
|
||||||
|
$queueForScreenshots,
|
||||||
$queueForWebhooks,
|
$queueForWebhooks,
|
||||||
$queueForFunctions,
|
$queueForFunctions,
|
||||||
$queueForRealtime,
|
$queueForRealtime,
|
||||||
|
|
@ -172,6 +170,7 @@ class Builds extends Action
|
||||||
* @param Device $deviceForFunctions
|
* @param Device $deviceForFunctions
|
||||||
* @param Device $deviceForSites
|
* @param Device $deviceForSites
|
||||||
* @param Device $deviceForFiles
|
* @param Device $deviceForFiles
|
||||||
|
* @param Screenshot $queueForScreenshots
|
||||||
* @param Webhook $queueForWebhooks
|
* @param Webhook $queueForWebhooks
|
||||||
* @param Func $queueForFunctions
|
* @param Func $queueForFunctions
|
||||||
* @param Realtime $queueForRealtime
|
* @param Realtime $queueForRealtime
|
||||||
|
|
@ -196,6 +195,7 @@ class Builds extends Action
|
||||||
Device $deviceForFunctions,
|
Device $deviceForFunctions,
|
||||||
Device $deviceForSites,
|
Device $deviceForSites,
|
||||||
Device $deviceForFiles,
|
Device $deviceForFiles,
|
||||||
|
Screenshot $queueForScreenshots,
|
||||||
Webhook $queueForWebhooks,
|
Webhook $queueForWebhooks,
|
||||||
Func $queueForFunctions,
|
Func $queueForFunctions,
|
||||||
Realtime $queueForRealtime,
|
Realtime $queueForRealtime,
|
||||||
|
|
@ -913,187 +913,6 @@ class Builds extends Action
|
||||||
Console::log('Build details stored');
|
Console::log('Build details stored');
|
||||||
|
|
||||||
$this->afterBuildSuccess($queueForRealtime, $dbForProject, $deployment, $runtime, $adapter);
|
$this->afterBuildSuccess($queueForRealtime, $dbForProject, $deployment, $runtime, $adapter);
|
||||||
$logs = $deployment->getAttribute('buildLogs', '');
|
|
||||||
|
|
||||||
/** Screenshot site */
|
|
||||||
if ($resource->getCollection() === 'sites') {
|
|
||||||
Console::log('Site screenshot started');
|
|
||||||
|
|
||||||
$date = \date('H:i:s');
|
|
||||||
$logs .= "[90m[$date] [90m[[0mappwrite[90m][97m Screenshot capturing started. [0m\n";
|
|
||||||
$deployment->setAttribute('buildLogs', $logs);
|
|
||||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
|
||||||
$queueForRealtime
|
|
||||||
->setPayload($deployment->getArrayCopy())
|
|
||||||
->trigger();
|
|
||||||
|
|
||||||
try {
|
|
||||||
$rule = Authorization::skip(fn () => $dbForPlatform->findOne('rules', [
|
|
||||||
Query::equal("projectInternalId", [$project->getSequence()]),
|
|
||||||
Query::equal("type", ["deployment"]),
|
|
||||||
Query::equal('deploymentInternalId', [$deployment->getSequence()]),
|
|
||||||
]));
|
|
||||||
|
|
||||||
if ($rule->isEmpty()) {
|
|
||||||
throw new \Exception("Rule for build not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
$client = new FetchClient();
|
|
||||||
$client->setTimeout(\intval($resource->getAttribute('timeout', '15')) * 1000);
|
|
||||||
$client->addHeader('content-type', FetchClient::CONTENT_TYPE_APPLICATION_JSON);
|
|
||||||
|
|
||||||
$bucket = Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots'));
|
|
||||||
|
|
||||||
$configs = [
|
|
||||||
'screenshotLight' => [
|
|
||||||
'headers' => [ 'x-appwrite-hostname' => $rule->getAttribute('domain') ],
|
|
||||||
'url' => 'http://appwrite/?appwrite-preview=1&appwrite-theme=light',
|
|
||||||
'theme' => 'light'
|
|
||||||
],
|
|
||||||
'screenshotDark' => [
|
|
||||||
'headers' => [ 'x-appwrite-hostname' => $rule->getAttribute('domain') ],
|
|
||||||
'url' => 'http://appwrite/?appwrite-preview=1&appwrite-theme=dark',
|
|
||||||
'theme' => 'dark'
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 0);
|
|
||||||
$apiKey = $jwtObj->encode([
|
|
||||||
'hostnameOverride' => true,
|
|
||||||
'disabledMetrics' => [
|
|
||||||
METRIC_EXECUTIONS,
|
|
||||||
METRIC_EXECUTIONS_COMPUTE,
|
|
||||||
METRIC_EXECUTIONS_MB_SECONDS,
|
|
||||||
METRIC_NETWORK_REQUESTS,
|
|
||||||
METRIC_NETWORK_INBOUND,
|
|
||||||
METRIC_NETWORK_OUTBOUND,
|
|
||||||
str_replace(["{resourceType}"], [RESOURCE_TYPE_SITES], METRIC_RESOURCE_TYPE_EXECUTIONS),
|
|
||||||
str_replace(["{resourceType}"], [RESOURCE_TYPE_SITES], METRIC_RESOURCE_TYPE_EXECUTIONS_COMPUTE),
|
|
||||||
str_replace(["{resourceType}"], [RESOURCE_TYPE_SITES], METRIC_RESOURCE_TYPE_EXECUTIONS_MB_SECONDS),
|
|
||||||
str_replace(["{resourceType}", "{resourceInternalId}"], [RESOURCE_TYPE_SITES, $resource->getSequence()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS),
|
|
||||||
str_replace(["{resourceType}", "{resourceInternalId}"], [RESOURCE_TYPE_SITES, $resource->getSequence()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS_COMPUTE),
|
|
||||||
str_replace(["{resourceType}", "{resourceInternalId}"], [RESOURCE_TYPE_SITES, $resource->getSequence()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS_MB_SECONDS),
|
|
||||||
],
|
|
||||||
'bannerDisabled' => true,
|
|
||||||
'projectCheckDisabled' => true,
|
|
||||||
'previewAuthDisabled' => true,
|
|
||||||
'deploymentStatusIgnored' => true
|
|
||||||
]);
|
|
||||||
|
|
||||||
$screenshotError = null;
|
|
||||||
$screenshots = batch(\array_map(function ($key) use ($configs, $apiKey, $resource, $client, &$screenshotError) {
|
|
||||||
return function () use ($key, $configs, $apiKey, $resource, $client, &$screenshotError) {
|
|
||||||
try {
|
|
||||||
$config = $configs[$key];
|
|
||||||
|
|
||||||
$config['headers'] = \array_merge($config['headers'] ?? [], [
|
|
||||||
'x-appwrite-key' => API_KEY_DYNAMIC . '_' . $apiKey
|
|
||||||
]);
|
|
||||||
$config['sleep'] = 3000;
|
|
||||||
|
|
||||||
$frameworks = Config::getParam('frameworks', []);
|
|
||||||
$framework = $frameworks[$resource->getAttribute('framework', '')] ?? null;
|
|
||||||
if (!is_null($framework)) {
|
|
||||||
$config['sleep'] = $framework['screenshotSleep'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$browserEndpoint = System::getEnv('_APP_BROWSER_HOST', 'http://appwrite-browser:3000/v1');
|
|
||||||
$fetchResponse = $client->fetch(
|
|
||||||
url: $browserEndpoint . '/screenshots',
|
|
||||||
method: 'POST',
|
|
||||||
body: $config
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($fetchResponse->getStatusCode() >= 400) {
|
|
||||||
throw new \Exception($fetchResponse->getBody());
|
|
||||||
}
|
|
||||||
|
|
||||||
$screenshot = $fetchResponse->getBody();
|
|
||||||
|
|
||||||
return ['key' => $key, 'screenshot' => $screenshot];
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
$screenshotError = $th->getMessage();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, \array_keys($configs)));
|
|
||||||
|
|
||||||
if (!\is_null($screenshotError)) {
|
|
||||||
throw new \Exception($screenshotError);
|
|
||||||
}
|
|
||||||
|
|
||||||
$mimeType = "image/png";
|
|
||||||
|
|
||||||
foreach ($screenshots as $data) {
|
|
||||||
$key = $data['key'];
|
|
||||||
$screenshot = $data['screenshot'];
|
|
||||||
|
|
||||||
$fileId = ID::unique();
|
|
||||||
$fileName = $fileId . '.png';
|
|
||||||
$path = $deviceForFiles->getPath($fileName);
|
|
||||||
$path = str_ireplace($deviceForFiles->getRoot(), $deviceForFiles->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $path); // Add bucket id to path after root
|
|
||||||
$success = $deviceForFiles->write($path, $screenshot, $mimeType);
|
|
||||||
|
|
||||||
if (!$success) {
|
|
||||||
throw new \Exception("Screenshot failed to save");
|
|
||||||
}
|
|
||||||
|
|
||||||
$teamId = $project->getAttribute('teamId', '');
|
|
||||||
$file = new Document([
|
|
||||||
'$id' => $fileId,
|
|
||||||
'$permissions' => [
|
|
||||||
Permission::read(Role::team(ID::custom($teamId))),
|
|
||||||
],
|
|
||||||
'bucketId' => $bucket->getId(),
|
|
||||||
'bucketInternalId' => $bucket->getSequence(),
|
|
||||||
'name' => $fileName,
|
|
||||||
'path' => $path,
|
|
||||||
'signature' => $deviceForFiles->getFileHash($path),
|
|
||||||
'mimeType' => $mimeType,
|
|
||||||
'sizeOriginal' => \strlen($screenshot),
|
|
||||||
'sizeActual' => $deviceForFiles->getFileSize($path),
|
|
||||||
'algorithm' => Compression::NONE,
|
|
||||||
'comment' => '',
|
|
||||||
'chunksTotal' => 1,
|
|
||||||
'chunksUploaded' => 1,
|
|
||||||
'openSSLVersion' => null,
|
|
||||||
'openSSLCipher' => null,
|
|
||||||
'openSSLTag' => null,
|
|
||||||
'openSSLIV' => null,
|
|
||||||
'search' => implode(' ', [$fileId, $fileName]),
|
|
||||||
'metadata' => ['content_type' => $mimeType],
|
|
||||||
]);
|
|
||||||
|
|
||||||
Authorization::skip(fn () => $dbForPlatform->createDocument('bucket_' . $bucket->getSequence(), $file));
|
|
||||||
|
|
||||||
$deployment->setAttribute($key, $fileId);
|
|
||||||
}
|
|
||||||
|
|
||||||
$logs = $deployment->getAttribute('buildLogs', '');
|
|
||||||
$date = \date('H:i:s');
|
|
||||||
$logs .= "[90m[$date] [90m[[0mappwrite[90m][97m Screenshot capturing finished. [0m\n";
|
|
||||||
|
|
||||||
$deployment->setAttribute('buildLogs', $logs);
|
|
||||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
|
||||||
|
|
||||||
$queueForRealtime
|
|
||||||
->setPayload($deployment->getArrayCopy())
|
|
||||||
->trigger();
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("Screenshot failed to generate:");
|
|
||||||
Console::warning($th->getMessage());
|
|
||||||
Console::warning($th->getTraceAsString());
|
|
||||||
|
|
||||||
$logs = $deployment->getAttribute('buildLogs', '');
|
|
||||||
$date = \date('H:i:s');
|
|
||||||
$logs .= "[90m[$date] [90m[[0mappwrite[90m][33m Screenshot capturing failed. Deployment will continue. [0m\n";
|
|
||||||
|
|
||||||
$deployment->setAttribute('buildLogs', $logs);
|
|
||||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console::log('Site screenshot finished');
|
|
||||||
}
|
|
||||||
|
|
||||||
$logs = $deployment->getAttribute('buildLogs', '');
|
$logs = $deployment->getAttribute('buildLogs', '');
|
||||||
$date = \date('H:i:s');
|
$date = \date('H:i:s');
|
||||||
|
|
@ -1114,6 +933,16 @@ class Builds extends Action
|
||||||
$this->runGitAction('ready', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime, $platform);
|
$this->runGitAction('ready', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime, $platform);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Screenshot site */
|
||||||
|
if ($resource->getCollection() === 'sites') {
|
||||||
|
$queueForScreenshots
|
||||||
|
->setDeploymentId($deployment->getId())
|
||||||
|
->setProject($project)
|
||||||
|
->trigger();
|
||||||
|
|
||||||
|
Console::log('Site screenshot queued');
|
||||||
|
}
|
||||||
|
|
||||||
/** Set auto deploy */
|
/** Set auto deploy */
|
||||||
$activateBuild = false;
|
$activateBuild = false;
|
||||||
if ($deployment->getAttribute('activate') === true) {
|
if ($deployment->getAttribute('activate') === true) {
|
||||||
|
|
@ -1171,8 +1000,6 @@ class Builds extends Action
|
||||||
'live' => true,
|
'live' => true,
|
||||||
'deploymentId' => $deployment->getId(),
|
'deploymentId' => $deployment->getId(),
|
||||||
'deploymentInternalId' => $deployment->getSequence(),
|
'deploymentInternalId' => $deployment->getSequence(),
|
||||||
'deploymentScreenshotDark' => $deployment->getAttribute('screenshotDark', ''),
|
|
||||||
'deploymentScreenshotLight' => $deployment->getAttribute('screenshotLight', ''),
|
|
||||||
'deploymentCreatedAt' => $deployment->getCreatedAt(),
|
'deploymentCreatedAt' => $deployment->getCreatedAt(),
|
||||||
]));
|
]));
|
||||||
$queries = [
|
$queries = [
|
||||||
|
|
@ -1265,9 +1092,10 @@ class Builds extends Action
|
||||||
|
|
||||||
$endTime = DateTime::now();
|
$endTime = DateTime::now();
|
||||||
$durationEnd = \microtime(true);
|
$durationEnd = \microtime(true);
|
||||||
$deployment->setAttribute('buildEndedAt', $endTime);
|
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), new Document([
|
||||||
$deployment->setAttribute('buildDuration', \intval(\ceil($durationEnd - $durationStart)));
|
'buildEndedAt' => $endTime,
|
||||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
'buildDuration' => \intval(\ceil($durationEnd - $durationStart)),
|
||||||
|
]));
|
||||||
$queueForRealtime
|
$queueForRealtime
|
||||||
->setPayload($deployment->getArrayCopy())
|
->setPayload($deployment->getArrayCopy())
|
||||||
->trigger();
|
->trigger();
|
||||||
|
|
|
||||||
281
src/Appwrite/Platform/Modules/Functions/Workers/Screenshots.php
Normal file
281
src/Appwrite/Platform/Modules/Functions/Workers/Screenshots.php
Normal file
|
|
@ -0,0 +1,281 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Appwrite\Platform\Modules\Functions\Workers;
|
||||||
|
|
||||||
|
use Ahc\Jwt\JWT;
|
||||||
|
use Appwrite\Event\Realtime;
|
||||||
|
use Appwrite\Permission;
|
||||||
|
use Appwrite\Role;
|
||||||
|
use Exception;
|
||||||
|
use Utopia\CLI\Console;
|
||||||
|
use Utopia\Config\Config;
|
||||||
|
use Utopia\Database\Database;
|
||||||
|
use Utopia\Database\Document;
|
||||||
|
use Utopia\Database\Helpers\ID;
|
||||||
|
use Utopia\Database\Query;
|
||||||
|
use Utopia\Fetch\Client as FetchClient;
|
||||||
|
use Utopia\Platform\Action;
|
||||||
|
use Utopia\Queue\Message;
|
||||||
|
use Utopia\Storage\Compression\Compression;
|
||||||
|
use Utopia\Storage\Device;
|
||||||
|
use Utopia\System\System;
|
||||||
|
|
||||||
|
use function Swoole\Coroutine\batch;
|
||||||
|
|
||||||
|
class Screenshots extends Action
|
||||||
|
{
|
||||||
|
public static function getName(): string
|
||||||
|
{
|
||||||
|
return 'screenshots';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->desc('Screenshots worker')
|
||||||
|
->groups(['screenshots'])
|
||||||
|
->inject('message')
|
||||||
|
->inject('queueForRealtime')
|
||||||
|
->inject('dbForPlatform')
|
||||||
|
->inject('dbForProject')
|
||||||
|
->inject('project')
|
||||||
|
->inject('deviceForFiles')
|
||||||
|
->callback($this->action(...));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function action(
|
||||||
|
Message $message,
|
||||||
|
Realtime $queueForRealtime,
|
||||||
|
Database $dbForPlatform,
|
||||||
|
Database $dbForProject,
|
||||||
|
Document $project,
|
||||||
|
Device $deviceForFiles
|
||||||
|
): void {
|
||||||
|
Console::log('Screenshot action started');
|
||||||
|
|
||||||
|
$payload = $message->getPayload() ?? [];
|
||||||
|
|
||||||
|
if (empty($payload)) {
|
||||||
|
throw new \Exception('Missing payload');
|
||||||
|
}
|
||||||
|
|
||||||
|
Console::log('Site screenshot started');
|
||||||
|
|
||||||
|
$deploymentId = $payload['deploymentId'] ?? null;
|
||||||
|
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||||
|
|
||||||
|
if ($deployment->isEmpty()) {
|
||||||
|
throw new \Exception('Deployment not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
$siteId = $deployment->getAttribute('resourceId');
|
||||||
|
$site = $dbForProject->getDocument('sites', $siteId);
|
||||||
|
|
||||||
|
if ($site->isEmpty()) {
|
||||||
|
throw new \Exception('Site not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Realtime preparation
|
||||||
|
$event = "sites.[siteId].deployments.[deploymentId].update";
|
||||||
|
$queueForRealtime
|
||||||
|
->setSubscribers(['console'])
|
||||||
|
->setProject($project)
|
||||||
|
->setEvent($event)
|
||||||
|
->setParam('siteId', $site->getId())
|
||||||
|
->setParam('deploymentId', $deployment->getId());
|
||||||
|
|
||||||
|
$date = \date('H:i:s');
|
||||||
|
$this->appendToLogs($dbForProject, $deployment->getId(), $queueForRealtime, "[90m[$date] [90m[[0mappwrite[90m][97m Screenshot capturing started. [0m\n");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$rule = $dbForPlatform->findOne('rules', [
|
||||||
|
Query::equal("projectInternalId", [$project->getSequence()]),
|
||||||
|
Query::equal("type", ["deployment"]),
|
||||||
|
Query::equal('deploymentInternalId', [$deployment->getSequence()]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($rule->isEmpty()) {
|
||||||
|
throw new \Exception("Rule for deployment not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
$client = new FetchClient();
|
||||||
|
$client->setTimeout(\intval($site->getAttribute('timeout', '15')) * 1000);
|
||||||
|
$client->addHeader('content-type', FetchClient::CONTENT_TYPE_APPLICATION_JSON);
|
||||||
|
|
||||||
|
$bucket = $dbForPlatform->getDocument('buckets', 'screenshots');
|
||||||
|
|
||||||
|
if ($bucket->isEmpty()) {
|
||||||
|
throw new \Exception('Bucket not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
$configs = [
|
||||||
|
'screenshotLight' => [
|
||||||
|
'headers' => [ 'x-appwrite-hostname' => $rule->getAttribute('domain') ],
|
||||||
|
'url' => 'http://appwrite/?appwrite-preview=1&appwrite-theme=light',
|
||||||
|
'theme' => 'light'
|
||||||
|
],
|
||||||
|
'screenshotDark' => [
|
||||||
|
'headers' => [ 'x-appwrite-hostname' => $rule->getAttribute('domain') ],
|
||||||
|
'url' => 'http://appwrite/?appwrite-preview=1&appwrite-theme=dark',
|
||||||
|
'theme' => 'dark'
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 0);
|
||||||
|
$apiKey = $jwtObj->encode([
|
||||||
|
'hostnameOverride' => true,
|
||||||
|
'disabledMetrics' => [
|
||||||
|
METRIC_EXECUTIONS,
|
||||||
|
METRIC_EXECUTIONS_COMPUTE,
|
||||||
|
METRIC_EXECUTIONS_MB_SECONDS,
|
||||||
|
METRIC_NETWORK_REQUESTS,
|
||||||
|
METRIC_NETWORK_INBOUND,
|
||||||
|
METRIC_NETWORK_OUTBOUND,
|
||||||
|
str_replace(["{resourceType}"], [RESOURCE_TYPE_SITES], METRIC_RESOURCE_TYPE_EXECUTIONS),
|
||||||
|
str_replace(["{resourceType}"], [RESOURCE_TYPE_SITES], METRIC_RESOURCE_TYPE_EXECUTIONS_COMPUTE),
|
||||||
|
str_replace(["{resourceType}"], [RESOURCE_TYPE_SITES], METRIC_RESOURCE_TYPE_EXECUTIONS_MB_SECONDS),
|
||||||
|
str_replace(["{resourceType}", "{resourceInternalId}"], [RESOURCE_TYPE_SITES, $site->getSequence()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS),
|
||||||
|
str_replace(["{resourceType}", "{resourceInternalId}"], [RESOURCE_TYPE_SITES, $site->getSequence()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS_COMPUTE),
|
||||||
|
str_replace(["{resourceType}", "{resourceInternalId}"], [RESOURCE_TYPE_SITES, $site->getSequence()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS_MB_SECONDS),
|
||||||
|
],
|
||||||
|
'bannerDisabled' => true,
|
||||||
|
'projectCheckDisabled' => true,
|
||||||
|
'previewAuthDisabled' => true,
|
||||||
|
'deploymentStatusIgnored' => true
|
||||||
|
]);
|
||||||
|
|
||||||
|
$screenshotError = null;
|
||||||
|
$screenshots = batch(\array_map(function ($key) use ($configs, $apiKey, $site, $client, &$screenshotError) {
|
||||||
|
return function () use ($key, $configs, $apiKey, $site, $client, &$screenshotError) {
|
||||||
|
try {
|
||||||
|
$config = $configs[$key];
|
||||||
|
|
||||||
|
$config['headers'] = \array_merge($config['headers'] ?? [], [
|
||||||
|
'x-appwrite-key' => API_KEY_DYNAMIC . '_' . $apiKey
|
||||||
|
]);
|
||||||
|
$config['sleep'] = 3000;
|
||||||
|
|
||||||
|
$frameworks = Config::getParam('frameworks', []);
|
||||||
|
$framework = $frameworks[$site->getAttribute('framework', '')] ?? null;
|
||||||
|
if (!is_null($framework)) {
|
||||||
|
$config['sleep'] = $framework['screenshotSleep'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$browserEndpoint = System::getEnv('_APP_BROWSER_HOST', 'http://appwrite-browser:3000/v1');
|
||||||
|
$fetchResponse = $client->fetch(
|
||||||
|
url: $browserEndpoint . '/screenshots',
|
||||||
|
method: 'POST',
|
||||||
|
body: $config
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($fetchResponse->getStatusCode() >= 400) {
|
||||||
|
throw new \Exception($fetchResponse->getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
$screenshot = $fetchResponse->getBody();
|
||||||
|
|
||||||
|
return ['key' => $key, 'screenshot' => $screenshot];
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
$screenshotError = $th->getMessage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, \array_keys($configs)));
|
||||||
|
|
||||||
|
if (!\is_null($screenshotError)) {
|
||||||
|
throw new \Exception($screenshotError);
|
||||||
|
}
|
||||||
|
|
||||||
|
$mimeType = "image/png";
|
||||||
|
$updates = new Document([]);
|
||||||
|
|
||||||
|
foreach ($screenshots as $data) {
|
||||||
|
$key = $data['key'];
|
||||||
|
$screenshot = $data['screenshot'];
|
||||||
|
|
||||||
|
$fileId = ID::unique();
|
||||||
|
$fileName = $fileId . '.png';
|
||||||
|
$path = $deviceForFiles->getPath($fileName);
|
||||||
|
$path = str_ireplace($deviceForFiles->getRoot(), $deviceForFiles->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $path); // Add bucket id to path after root
|
||||||
|
$success = $deviceForFiles->write($path, $screenshot, $mimeType);
|
||||||
|
|
||||||
|
if (!$success) {
|
||||||
|
throw new \Exception("Screenshot failed to save");
|
||||||
|
}
|
||||||
|
|
||||||
|
$teamId = $project->getAttribute('teamId', '');
|
||||||
|
$file = new Document([
|
||||||
|
'$id' => $fileId,
|
||||||
|
'$permissions' => [
|
||||||
|
Permission::read(Role::team(ID::custom($teamId))),
|
||||||
|
],
|
||||||
|
'bucketId' => $bucket->getId(),
|
||||||
|
'bucketInternalId' => $bucket->getSequence(),
|
||||||
|
'name' => $fileName,
|
||||||
|
'path' => $path,
|
||||||
|
'signature' => $deviceForFiles->getFileHash($path),
|
||||||
|
'mimeType' => $mimeType,
|
||||||
|
'sizeOriginal' => \strlen($screenshot),
|
||||||
|
'sizeActual' => $deviceForFiles->getFileSize($path),
|
||||||
|
'algorithm' => Compression::NONE,
|
||||||
|
'comment' => '',
|
||||||
|
'chunksTotal' => 1,
|
||||||
|
'chunksUploaded' => 1,
|
||||||
|
'openSSLVersion' => null,
|
||||||
|
'openSSLCipher' => null,
|
||||||
|
'openSSLTag' => null,
|
||||||
|
'openSSLIV' => null,
|
||||||
|
'search' => implode(' ', [$fileId, $fileName]),
|
||||||
|
'metadata' => ['content_type' => $mimeType],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$dbForPlatform->createDocument('bucket_' . $bucket->getSequence(), $file);
|
||||||
|
|
||||||
|
$updates->setAttribute($key, $fileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$date = \date('H:i:s');
|
||||||
|
$this->appendToLogs($dbForProject, $deployment->getId(), $queueForRealtime, "[90m[$date] [90m[[0mappwrite[90m][97m Screenshot capturing finished. [0m\n");
|
||||||
|
|
||||||
|
// Apply screenshot properties
|
||||||
|
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $updates);
|
||||||
|
|
||||||
|
$queueForRealtime
|
||||||
|
->setPayload($deployment->getArrayCopy())
|
||||||
|
->trigger();
|
||||||
|
|
||||||
|
$site = $dbForProject->updateDocument('sites', $site->getId(), new Document([
|
||||||
|
'deploymentScreenshotDark' => $deployment->getAttribute('screenshotDark', ''),
|
||||||
|
'deploymentScreenshotLight' => $deployment->getAttribute('screenshotLight', ''),
|
||||||
|
]));
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
Console::warning("Screenshot failed to generate:");
|
||||||
|
Console::warning($th->getMessage());
|
||||||
|
Console::warning($th->getTraceAsString());
|
||||||
|
|
||||||
|
$date = \date('H:i:s');
|
||||||
|
$this->appendToLogs($dbForProject, $deployment->getId(), $queueForRealtime, "[90m[$date] [90m[[0mappwrite[90m][33m Screenshot capturing failed. Deployment will continue. [0m\n");
|
||||||
|
|
||||||
|
throw $th;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function appendToLogs(Database $dbForProject, string $deploymentId, Realtime $queueForRealtime, string $logs)
|
||||||
|
{
|
||||||
|
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||||
|
|
||||||
|
$buildLogs = $deployment->getAttribute('buildLogs', '');
|
||||||
|
$buildLogs .= $logs;
|
||||||
|
|
||||||
|
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), new Document([
|
||||||
|
'buildLogs' => $buildLogs
|
||||||
|
]));
|
||||||
|
|
||||||
|
$queueForRealtime
|
||||||
|
->setPayload($deployment->getArrayCopy())
|
||||||
|
->trigger();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -385,6 +385,9 @@ class Create extends Action
|
||||||
}
|
}
|
||||||
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file));
|
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trigger after create success hook
|
||||||
|
$this->afterCreateSuccess($file);
|
||||||
} else {
|
} else {
|
||||||
if ($file->isEmpty()) {
|
if ($file->isEmpty()) {
|
||||||
$doc = new Document([
|
$doc = new Document([
|
||||||
|
|
@ -448,4 +451,17 @@ class Create extends Action
|
||||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||||
->dynamic($file, Response::MODEL_FILE);
|
->dynamic($file, Response::MODEL_FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to run after file is created successfully
|
||||||
|
*
|
||||||
|
* @param Document $file
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function afterCreateSuccess(Document $file)
|
||||||
|
{
|
||||||
|
if (!($file instanceof Document)) {
|
||||||
|
throw new Exception('file must be an instance of document');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,15 +54,22 @@ class SitesConsoleClientTest extends Scope
|
||||||
$this->assertStringContainsString("Themed website", $response['body']);
|
$this->assertStringContainsString("Themed website", $response['body']);
|
||||||
$this->assertStringContainsString("@media (prefers-color-scheme: dark)", $response['body']);
|
$this->assertStringContainsString("@media (prefers-color-scheme: dark)", $response['body']);
|
||||||
|
|
||||||
$deployment = $this->getDeployment($siteId, $deploymentId);
|
$deployment = null;
|
||||||
$this->assertEquals(200, $deployment['headers']['status-code']);
|
$site = null;
|
||||||
$this->assertNotEmpty($deployment['body']['screenshotLight']);
|
$this->assertEventually(function () use ($siteId, $deploymentId, &$deployment, &$site) {
|
||||||
$this->assertNotEmpty($deployment['body']['screenshotDark']);
|
$deployment = $this->getDeployment($siteId, $deploymentId);
|
||||||
|
$this->assertEquals(200, $deployment['headers']['status-code']);
|
||||||
|
$this->assertNotEmpty($deployment['body']['screenshotLight']);
|
||||||
|
$this->assertNotEmpty($deployment['body']['screenshotDark']);
|
||||||
|
|
||||||
$site = $this->getSite($siteId);
|
$site = $this->getSite($siteId);
|
||||||
$this->assertEquals(200, $site['headers']['status-code']);
|
$this->assertEquals(200, $site['headers']['status-code']);
|
||||||
$this->assertEquals($deployment['body']['screenshotLight'], $site['body']['deploymentScreenshotLight']);
|
$this->assertEquals($deployment['body']['screenshotLight'], $site['body']['deploymentScreenshotLight']);
|
||||||
$this->assertEquals($deployment['body']['screenshotDark'], $site['body']['deploymentScreenshotDark']);
|
$this->assertEquals($deployment['body']['screenshotDark'], $site['body']['deploymentScreenshotDark']);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->assertNotNull($site);
|
||||||
|
$this->assertNotNull($deployment);
|
||||||
|
|
||||||
$screenshotId = $deployment['body']['screenshotLight'];
|
$screenshotId = $deployment['body']['screenshotLight'];
|
||||||
$file = $this->client->call(Client::METHOD_GET, "/storage/buckets/screenshots/files/$screenshotId/view?project=console", array_merge($this->getHeaders(), [
|
$file = $this->client->call(Client::METHOD_GET, "/storage/buckets/screenshots/files/$screenshotId/view?project=console", array_merge($this->getHeaders(), [
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue