mirror of
https://github.com/appwrite/appwrite
synced 2026-05-23 00:49:02 +00:00
Merge pull request #9366 from appwrite/feat-site-screenshots
Feat: Site screenshots
This commit is contained in:
commit
c8f4667b6a
20 changed files with 552 additions and 104 deletions
|
|
@ -1528,7 +1528,29 @@ return [
|
|||
'default' => false,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
]
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('screenshotLight'), // File ID from 'screenshots' Console bucket
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 32,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('screenshotDark'), // File ID from 'screenshots' Console bucket
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 32,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
|
|
|
|||
|
|
@ -539,4 +539,49 @@ return [
|
|||
'providerVersion' => '0.2.*',
|
||||
'variables' => [],
|
||||
],
|
||||
[
|
||||
'key' => 'nextjs-starter',
|
||||
'name' => 'Next.js starter website',
|
||||
'useCases' => ['starter'],
|
||||
'frameworks' => [
|
||||
getFramework('NEXTJS', [
|
||||
'providerRootDirectory' => './nextjs/starter',
|
||||
]),
|
||||
],
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.2.*',
|
||||
'variables' => [],
|
||||
],
|
||||
[
|
||||
'key' => 'nuxt-starter',
|
||||
'name' => 'Nuxt starter website',
|
||||
'useCases' => ['starter'],
|
||||
'frameworks' => [
|
||||
getFramework('NUXT', [
|
||||
'providerRootDirectory' => './nuxt/starter',
|
||||
]),
|
||||
],
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.2.*',
|
||||
'variables' => [],
|
||||
],
|
||||
[
|
||||
'key' => 'sveltekit-starter',
|
||||
'name' => 'SvelteKit starter website',
|
||||
'useCases' => ['starter'],
|
||||
'frameworks' => [
|
||||
getFramework('SVELTEKIT', [
|
||||
'providerRootDirectory' => './sveltekit/starter',
|
||||
]),
|
||||
],
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.2.*',
|
||||
'variables' => [],
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -236,10 +236,9 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
|||
$projectId = $project->getId();
|
||||
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$domain = "{$deploymentId}-{$projectId}.{$sitesDomain}";
|
||||
$domain = ID::unique() . "." . $sitesDomain;
|
||||
$ruleId = md5($domain);
|
||||
|
||||
$rule = Authorization::skip(
|
||||
Authorization::skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ require_once __DIR__ . '/../init.php';
|
|||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Key;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
|
|
@ -54,7 +55,7 @@ Config::setParam('domainVerification', false);
|
|||
Config::setParam('cookieDomain', 'localhost');
|
||||
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
|
||||
|
||||
function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked, string $previewHostname)
|
||||
function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey)
|
||||
{
|
||||
$utopia->getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml');
|
||||
|
||||
|
|
@ -269,11 +270,11 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
if ($type === 'function') {
|
||||
$jwtExpiry = $resource->getAttribute('timeout', 900);
|
||||
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0);
|
||||
$apiKey = $jwtObj->encode([
|
||||
$jwtKey = $jwtObj->encode([
|
||||
'projectId' => $project->getId(),
|
||||
'scopes' => $resource->getAttribute('scopes', [])
|
||||
]);
|
||||
$headers['x-appwrite-key'] = API_KEY_DYNAMIC . '_' . $apiKey;
|
||||
$headers['x-appwrite-key'] = API_KEY_DYNAMIC . '_' . $jwtKey;
|
||||
$headers['x-appwrite-trigger'] = 'http';
|
||||
$headers['x-appwrite-user-jwt'] = '';
|
||||
}
|
||||
|
|
@ -448,18 +449,19 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
requestTimeout: 30
|
||||
);
|
||||
|
||||
|
||||
// Branded banner for previews
|
||||
$transformation = new Transformation();
|
||||
$transformation->addAdapter(new Preview());
|
||||
$transformation->setInput($executionResponse['body']);
|
||||
$transformation->setTraits($executionResponse['headers']);
|
||||
if ($type === 'deployment' && $transformation->transform()) {
|
||||
$executionResponse['body'] = $transformation->getOutput();
|
||||
if (\is_null($apiKey) || $apiKey->isBannerDisabled() === false) {
|
||||
$transformation = new Transformation();
|
||||
$transformation->addAdapter(new Preview());
|
||||
$transformation->setInput($executionResponse['body']);
|
||||
$transformation->setTraits($executionResponse['headers']);
|
||||
if ($type === 'deployment' && $transformation->transform()) {
|
||||
$executionResponse['body'] = $transformation->getOutput();
|
||||
|
||||
foreach ($executionResponse['headers'] as $key => $value) {
|
||||
if (\strtolower($key) === 'content-length') {
|
||||
$executionResponse['headers'][$key] = \strlen($executionResponse['body']);
|
||||
foreach ($executionResponse['headers'] as $key => $value) {
|
||||
if (\strtolower($key) === 'content-length') {
|
||||
$executionResponse['headers'][$key] = \strlen($executionResponse['body']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -585,7 +587,7 @@ App::init()
|
|||
*/
|
||||
|
||||
App::init()
|
||||
->groups(['database', 'functions', 'storage', 'messaging'])
|
||||
->groups(['database', 'functions', 'messaging'])
|
||||
->inject('project')
|
||||
->inject('request')
|
||||
->action(function (Document $project, Request $request) {
|
||||
|
|
@ -617,7 +619,8 @@ App::init()
|
|||
->inject('queueForFunctions')
|
||||
->inject('isResourceBlocked')
|
||||
->inject('previewHostname')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, callable $isResourceBlocked, string $previewHostname) {
|
||||
->inject('apiKey')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
|
|
@ -625,7 +628,7 @@ App::init()
|
|||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
// Only run Router when external domain
|
||||
if ($host !== $mainDomain || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
|
|
@ -865,7 +868,8 @@ App::options()
|
|||
->inject('geodb')
|
||||
->inject('isResourceBlocked')
|
||||
->inject('previewHostname')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked, string $previewHostname) {
|
||||
->inject('apiKey')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
|
|
@ -873,7 +877,7 @@ App::options()
|
|||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
// Only run Router when external domain
|
||||
if ($host !== $mainDomain || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
|
|
@ -1173,7 +1177,8 @@ App::get('/robots.txt')
|
|||
->inject('geodb')
|
||||
->inject('isResourceBlocked')
|
||||
->inject('previewHostname')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked, string $previewHostname) {
|
||||
->inject('apiKey')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey) {
|
||||
$host = $request->getHostname() ?? '';
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
|
||||
|
|
@ -1181,7 +1186,7 @@ App::get('/robots.txt')
|
|||
$template = new View(__DIR__ . '/../views/general/robots.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
|
|
@ -1203,7 +1208,8 @@ App::get('/humans.txt')
|
|||
->inject('geodb')
|
||||
->inject('isResourceBlocked')
|
||||
->inject('previewHostname')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked, string $previewHostname) {
|
||||
->inject('apiKey')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey) {
|
||||
$host = $request->getHostname() ?? '';
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
|
||||
|
|
@ -1211,7 +1217,7 @@ App::get('/humans.txt')
|
|||
$template = new View(__DIR__ . '/../views/general/humans.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
57
app/http.php
57
app/http.php
|
|
@ -13,6 +13,7 @@ use Swoole\Table;
|
|||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Compression\Compression;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
|
|
@ -249,8 +250,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
|||
$audit->setup();
|
||||
}
|
||||
|
||||
if ($dbForPlatform->getDocument('buckets', 'default')->isEmpty() &&
|
||||
!$dbForPlatform->exists($dbForPlatform->getDatabase(), 'bucket_1')) {
|
||||
if ($dbForPlatform->getDocument('buckets', 'default')->isEmpty()) {
|
||||
Console::info(" └── Creating default bucket...");
|
||||
$dbForPlatform->createDocument('buckets', new Document([
|
||||
'$id' => ID::custom('default'),
|
||||
|
|
@ -302,6 +302,59 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
|||
|
||||
$dbForPlatform->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes);
|
||||
}
|
||||
|
||||
if ($dbForPlatform->getDocument('buckets', 'screenshots')->isEmpty()) {
|
||||
Console::info(" └── Creating screenshots bucket...");
|
||||
$dbForPlatform->createDocument('buckets', new Document([
|
||||
'$id' => ID::custom('screenshots'),
|
||||
'$collection' => ID::custom('buckets'),
|
||||
'name' => 'Screenshots',
|
||||
'maximumFileSize' => 5000000, // ~5MB
|
||||
'allowedFileExtensions' => [ 'png' ],
|
||||
'enabled' => true,
|
||||
'compression' => Compression::GZIP,
|
||||
'encryption' => false,
|
||||
'antivirus' => false,
|
||||
'fileSecurity' => true,
|
||||
'$permissions' => [
|
||||
Permission::create(Role::any()),
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'search' => 'buckets Screenshots',
|
||||
]));
|
||||
|
||||
$bucket = $dbForPlatform->getDocument('buckets', 'screenshots');
|
||||
|
||||
Console::info(" └── Creating files collection for screenshots bucket...");
|
||||
$files = $collections['buckets']['files'] ?? [];
|
||||
if (empty($files)) {
|
||||
throw new Exception('Files collection is not configured.');
|
||||
}
|
||||
|
||||
$attributes = array_map(fn ($attr) => new Document([
|
||||
'$id' => ID::custom($attr['$id']),
|
||||
'type' => $attr['type'],
|
||||
'size' => $attr['size'],
|
||||
'required' => $attr['required'],
|
||||
'signed' => $attr['signed'],
|
||||
'array' => $attr['array'],
|
||||
'filters' => $attr['filters'],
|
||||
'default' => $attr['default'] ?? null,
|
||||
'format' => $attr['format'] ?? ''
|
||||
]), $files['attributes']);
|
||||
|
||||
$indexes = array_map(fn ($index) => new Document([
|
||||
'$id' => ID::custom($index['$id']),
|
||||
'type' => $index['type'],
|
||||
'attributes' => $index['attributes'],
|
||||
'lengths' => $index['lengths'],
|
||||
'orders' => $index['orders'],
|
||||
]), $files['indexes']);
|
||||
|
||||
$dbForPlatform->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes);
|
||||
}
|
||||
});
|
||||
|
||||
$pools->reclaim();
|
||||
|
|
|
|||
14
app/init.php
14
app/init.php
|
|
@ -1961,16 +1961,24 @@ App::setResource(
|
|||
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
|
||||
);
|
||||
|
||||
App::setResource('previewHostname', function (Request $request) {
|
||||
App::setResource('previewHostname', function (Request $request, ?Key $apiKey) {
|
||||
$allowed = false;
|
||||
|
||||
if (App::isDevelopment()) {
|
||||
$host = $request->getQuery('appwrite-hostname') ?? '';
|
||||
$allowed = true;
|
||||
} elseif (!\is_null($apiKey) && $apiKey->getHostnameOverride() === true) {
|
||||
$allowed = true;
|
||||
}
|
||||
|
||||
if ($allowed) {
|
||||
$host = $request->getQuery('appwrite-hostname', $request->getHeader('x-appwrite-hostname', ''));
|
||||
if (!empty($host)) {
|
||||
return $host;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}, ['request']);
|
||||
}, ['request', 'apiKey']);
|
||||
|
||||
App::setResource('apiKey', function (Request $request, Document $project): ?Key {
|
||||
$key = $request->getHeader('x-appwrite-key');
|
||||
|
|
|
|||
12
composer.lock
generated
12
composer.lock
generated
|
|
@ -6190,16 +6190,16 @@
|
|||
},
|
||||
{
|
||||
"name": "phpstan/phpdoc-parser",
|
||||
"version": "2.0.2",
|
||||
"version": "2.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpdoc-parser.git",
|
||||
"reference": "51087f87dcce2663e1fed4dfd4e56eccd580297e"
|
||||
"reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/51087f87dcce2663e1fed4dfd4e56eccd580297e",
|
||||
"reference": "51087f87dcce2663e1fed4dfd4e56eccd580297e",
|
||||
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9b30d6fd026b2c132b3985ce6b23bec09ab3aa68",
|
||||
"reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -6231,9 +6231,9 @@
|
|||
"description": "PHPDoc parser with support for nullable, intersection and generic types",
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
|
||||
"source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.2"
|
||||
"source": "https://github.com/phpstan/phpdoc-parser/tree/2.1.0"
|
||||
},
|
||||
"time": "2025-02-17T20:25:51+00:00"
|
||||
"time": "2025-02-19T13:28:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
|
|
|
|||
|
|
@ -435,6 +435,7 @@ services:
|
|||
volumes:
|
||||
- appwrite-functions:/storage/functions:rw
|
||||
- appwrite-builds:/storage/builds:rw
|
||||
- appwrite-uploads:/storage/uploads:rw
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
depends_on:
|
||||
|
|
@ -947,6 +948,12 @@ services:
|
|||
environment:
|
||||
- _APP_ASSISTANT_OPENAI_API_KEY
|
||||
|
||||
appwrite-browser:
|
||||
container_name: appwrite-browser
|
||||
image: appwrite/browser:0.2.0
|
||||
networks:
|
||||
- appwrite
|
||||
|
||||
openruntimes-executor:
|
||||
container_name: openruntimes-executor
|
||||
hostname: exc1
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ class Key
|
|||
protected string $name,
|
||||
protected bool $expired = false,
|
||||
protected array $disabledMetrics = [],
|
||||
protected bool $hostnameOverride = false,
|
||||
protected bool $bannerDisabled = false,
|
||||
protected bool $projectCheckDisabled = false,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -58,6 +61,23 @@ class Key
|
|||
return $this->disabledMetrics;
|
||||
}
|
||||
|
||||
|
||||
public function getHostnameOverride(): bool
|
||||
{
|
||||
return $this->hostnameOverride;
|
||||
}
|
||||
|
||||
|
||||
public function isBannerDisabled(): bool
|
||||
{
|
||||
return $this->bannerDisabled;
|
||||
}
|
||||
|
||||
public function isProjectCheckDisabled(): bool
|
||||
{
|
||||
return $this->projectCheckDisabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the given secret key into a Key object, containing the project ID, type, role, scopes, and name.
|
||||
* Can be a stored API key or a dynamic key (JWT).
|
||||
|
|
@ -109,9 +129,12 @@ class Key
|
|||
$name = $payload['name'] ?? 'Dynamic Key';
|
||||
$projectId = $payload['projectId'] ?? '';
|
||||
$disabledMetrics = $payload['disabledMetrics'] ?? [];
|
||||
$hostnameOverride = $payload['hostnameOverride'] ?? false;
|
||||
$bannerDisabled = $payload['bannerDisabled'] ?? false;
|
||||
$projectCheckDisabled = $payload['projectCheckDisabled'] ?? false;
|
||||
$scopes = \array_merge($payload['scopes'] ?? [], $scopes);
|
||||
|
||||
if ($projectId !== $project->getId()) {
|
||||
if (!$projectCheckDisabled && $projectId !== $project->getId()) {
|
||||
return $guestKey;
|
||||
}
|
||||
|
||||
|
|
@ -122,7 +145,10 @@ class Key
|
|||
$scopes,
|
||||
$name,
|
||||
$expired,
|
||||
$disabledMetrics
|
||||
$disabledMetrics,
|
||||
$hostnameOverride,
|
||||
$bannerDisabled,
|
||||
$projectCheckDisabled
|
||||
);
|
||||
case API_KEY_STANDARD:
|
||||
$key = $project->find(
|
||||
|
|
|
|||
|
|
@ -172,14 +172,10 @@ class Base extends Action
|
|||
'activate' => $activate,
|
||||
]));
|
||||
|
||||
// Preview deployments for sites
|
||||
$projectId = $project->getId();
|
||||
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$domain = "{$deploymentId}-{$projectId}.{$sitesDomain}";
|
||||
$domain = ID::unique() . "." . $sitesDomain;
|
||||
$ruleId = md5($domain);
|
||||
|
||||
$rule = Authorization::skip(
|
||||
Authorization::skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ use Appwrite\Event\Event;
|
|||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Permission;
|
||||
use Appwrite\Role;
|
||||
use Appwrite\Utopia\Response\Model\Deployment;
|
||||
use Appwrite\Vcs\Comment;
|
||||
use Exception;
|
||||
|
|
@ -24,9 +26,11 @@ use Utopia\Database\Exception\Structure;
|
|||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Fetch\Client as FetchClient;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Message;
|
||||
use Utopia\Storage\Compression\Compression;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Storage\Device\Local;
|
||||
use Utopia\System\System;
|
||||
|
|
@ -56,9 +60,10 @@ class Builds extends Action
|
|||
->inject('dbForProject')
|
||||
->inject('deviceForFunctions')
|
||||
->inject('isResourceBlocked')
|
||||
->inject('deviceForFiles')
|
||||
->inject('log')
|
||||
->callback(fn ($message, Document $project, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions, StatsUsage $usage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log) =>
|
||||
$this->action($message, $project, $dbForPlatform, $queueForEvents, $queueForFunctions, $usage, $cache, $dbForProject, $deviceForFunctions, $isResourceBlocked, $log));
|
||||
->callback(fn ($message, Document $project, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions, StatsUsage $usage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Device $deviceForFiles, Log $log) =>
|
||||
$this->action($message, $project, $dbForPlatform, $queueForEvents, $queueForFunctions, $usage, $cache, $dbForProject, $deviceForFunctions, $isResourceBlocked, $deviceForFiles, $log));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -71,11 +76,12 @@ class Builds extends Action
|
|||
* @param Cache $cache
|
||||
* @param Database $dbForProject
|
||||
* @param Device $deviceForFunctions
|
||||
* @param Device $deviceForFiles
|
||||
* @param Log $log
|
||||
* @return void
|
||||
* @throws \Utopia\Database\Exception
|
||||
*/
|
||||
public function action(Message $message, Document $project, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions, StatsUsage $queueForStatsUsage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log): void
|
||||
public function action(Message $message, Document $project, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions, StatsUsage $queueForStatsUsage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Device $deviceForFiles, Log $log): void
|
||||
{
|
||||
$payload = $message->getPayload() ?? [];
|
||||
|
||||
|
|
@ -96,7 +102,7 @@ class Builds extends Action
|
|||
case BUILD_TYPE_RETRY:
|
||||
Console::info('Creating build for deployment: ' . $deployment->getId());
|
||||
$github = new GitHub($cache);
|
||||
$this->buildDeployment($deviceForFunctions, $queueForFunctions, $queueForEvents, $queueForStatsUsage, $dbForPlatform, $dbForProject, $github, $project, $resource, $deployment, $template, $isResourceBlocked, $log);
|
||||
$this->buildDeployment($deviceForFunctions, $deviceForFiles, $queueForFunctions, $queueForEvents, $queueForStatsUsage, $dbForPlatform, $dbForProject, $github, $project, $resource, $deployment, $template, $isResourceBlocked, $log);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
@ -106,6 +112,7 @@ class Builds extends Action
|
|||
|
||||
/**
|
||||
* @param Device $deviceForFunctions
|
||||
* @param Device $deviceForFiles
|
||||
* @param Func $queueForFunctions
|
||||
* @param Event $queueForEvents
|
||||
* @param StatsUsage $queueForStatsUsage
|
||||
|
|
@ -122,7 +129,7 @@ class Builds extends Action
|
|||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function buildDeployment(Device $deviceForFunctions, Func $queueForFunctions, Event $queueForEvents, StatsUsage $queueForStatsUsage, Database $dbForPlatform, Database $dbForProject, GitHub $github, Document $project, Document $resource, Document $deployment, Document $template, callable $isResourceBlocked, Log $log): void
|
||||
protected function buildDeployment(Device $deviceForFunctions, Device $deviceForFiles, Func $queueForFunctions, Event $queueForEvents, StatsUsage $queueForStatsUsage, Database $dbForPlatform, Database $dbForProject, GitHub $github, Document $project, Document $resource, Document $deployment, Document $template, callable $isResourceBlocked, Log $log): void
|
||||
{
|
||||
$resourceKey = match($resource->getCollection()) {
|
||||
'functions' => 'functionId',
|
||||
|
|
@ -713,6 +720,109 @@ class Builds extends Action
|
|||
|
||||
Console::success("Build id: $buildId created");
|
||||
|
||||
if ($resource->getCollection() === 'sites') {
|
||||
try {
|
||||
$rule = Authorization::skip(fn () => $dbForPlatform->findOne('rules', [
|
||||
Query::equal("projectInternalId", [$project->getInternalId()]),
|
||||
Query::equal("resourceType", ["deployment"]),
|
||||
Query::equal("resourceInternalId", [$deployment->getInternalId()])
|
||||
]));
|
||||
|
||||
if ($rule->isEmpty()) {
|
||||
throw new \Exception("Rule for build not found");
|
||||
}
|
||||
|
||||
$client = new FetchClient();
|
||||
$client->addHeader('content-type', FetchClient::CONTENT_TYPE_APPLICATION_JSON);
|
||||
|
||||
$bucket = $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,
|
||||
'bannerDisabled' => true,
|
||||
'projectCheckDisabled' => true
|
||||
]);
|
||||
|
||||
// TODO: @Meldiron if becomes too slow, do concurrently
|
||||
foreach ($configs as $key => $config) {
|
||||
$config['headers'] = \array_merge($config['headers'] ?? [], [
|
||||
'x-appwrite-key' => API_KEY_DYNAMIC . '_' . $apiKey
|
||||
]);
|
||||
|
||||
$response = $client->fetch(
|
||||
url: 'http://appwrite-browser:3000/v1/screenshots',
|
||||
method: 'POST',
|
||||
body: $config
|
||||
);
|
||||
|
||||
if ($response->getStatusCode() >= 400) {
|
||||
throw new \Exception("Screenshot failed to generate: " . $response->getBody());
|
||||
}
|
||||
|
||||
$screenshot = $response->getBody();
|
||||
|
||||
$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, "image/png");
|
||||
|
||||
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->getInternalId(),
|
||||
'name' => $fileName,
|
||||
'path' => $path,
|
||||
'signature' => $deviceForFiles->getFileHash($path),
|
||||
'mimeType' => $deviceForFiles->getFileMimeType($path),
|
||||
'sizeOriginal' => \strlen($screenshot),
|
||||
'sizeActual' => $deviceForFiles->getFileSize($path),
|
||||
'algorithm' => Compression::GZIP,
|
||||
'comment' => '',
|
||||
'chunksTotal' => 1,
|
||||
'chunksUploaded' => 1,
|
||||
'openSSLVersion' => null,
|
||||
'openSSLCipher' => null,
|
||||
'openSSLTag' => null,
|
||||
'openSSLIV' => null,
|
||||
'search' => implode(' ', [$fileId, $fileName]),
|
||||
'metadata' => ['content_type' => $deviceForFiles->getFileMimeType($path)],
|
||||
]);
|
||||
$file = Authorization::skip(fn () => $dbForPlatform->createDocument('bucket_' . $bucket->getInternalId(), $file));
|
||||
|
||||
$deployment->setAttribute($key, $fileId);
|
||||
}
|
||||
|
||||
$dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("Screenshot failed to generate:");
|
||||
Console::warning($th->getMessage());
|
||||
Console::warning($th->getTraceAsString());
|
||||
}
|
||||
}
|
||||
|
||||
/** Set auto deploy */
|
||||
if ($deployment->getAttribute('activate') === true) {
|
||||
$resource->setAttribute('deploymentInternalId', $deployment->getInternalId());
|
||||
|
|
|
|||
|
|
@ -10,11 +10,14 @@ use Appwrite\SDK\Method;
|
|||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
|
|
@ -53,7 +56,9 @@ class Create extends Action
|
|||
->param('siteId', '', new UID(), 'Site ID.')
|
||||
->param('deploymentId', '', new UID(), 'Deployment ID.')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForBuilds')
|
||||
->inject('deviceForSites')
|
||||
|
|
@ -61,7 +66,7 @@ class Create extends Action
|
|||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $siteId, string $deploymentId, Response $response, Database $dbForProject, Event $queueForEvents, Build $queueForBuilds, Device $deviceForSites, Device $deviceForFunctions)
|
||||
public function action(string $siteId, string $deploymentId, Response $response, Document $project, Database $dbForProject, Database $dbForPlatform, Event $queueForEvents, Build $queueForBuilds, Device $deviceForSites, Device $deviceForFunctions)
|
||||
{
|
||||
$site = $dbForProject->getDocument('sites', $siteId);
|
||||
|
||||
|
|
@ -97,6 +102,24 @@ class Create extends Action
|
|||
'search' => implode(' ', [$deploymentId]),
|
||||
]));
|
||||
|
||||
// Preview deployments for sites
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$domain = ID::unique() . "." . $sitesDomain;
|
||||
$ruleId = md5($domain);
|
||||
Authorization::skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'domain' => $domain,
|
||||
'resourceType' => 'deployment',
|
||||
'resourceId' => $deploymentId,
|
||||
'resourceInternalId' => $deployment->getInternalId(),
|
||||
'status' => 'verified',
|
||||
'certificateId' => '',
|
||||
]))
|
||||
);
|
||||
|
||||
$queueForBuilds
|
||||
->setType(BUILD_TYPE_DEPLOYMENT)
|
||||
->setResource($site)
|
||||
|
|
|
|||
|
|
@ -228,14 +228,10 @@ class Create extends Action
|
|||
'type' => $type
|
||||
]));
|
||||
|
||||
// Preview deployments for sites
|
||||
$projectId = $project->getId();
|
||||
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$domain = "{$deploymentId}-{$projectId}.{$sitesDomain}";
|
||||
$domain = ID::unique() . "." . $sitesDomain;
|
||||
$ruleId = md5($domain);
|
||||
|
||||
$rule = Authorization::skip(
|
||||
Authorization::skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
|
|
@ -283,14 +279,10 @@ class Create extends Action
|
|||
'type' => $type
|
||||
]));
|
||||
|
||||
// Preview deployments for sites
|
||||
$projectId = $project->getId();
|
||||
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$domain = "{$deploymentId}-{$projectId}.{$sitesDomain}";
|
||||
$domain = ID::unique() . "." . $sitesDomain;
|
||||
$ruleId = md5($domain);
|
||||
|
||||
$rule = Authorization::skip(
|
||||
Authorization::skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
|
|
|
|||
|
|
@ -139,18 +139,15 @@ class Create extends Base
|
|||
'activate' => $activate,
|
||||
]));
|
||||
|
||||
// Preview deployments url
|
||||
$projectId = $project->getId();
|
||||
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$previewDomain = "{$deploymentId}-{$projectId}.{$sitesDomain}";
|
||||
|
||||
$rule = Authorization::skip(
|
||||
$domain = ID::unique() . "." . $sitesDomain;
|
||||
$ruleId = md5($domain);
|
||||
Authorization::skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
'$id' => \md5($previewDomain),
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'domain' => $previewDomain,
|
||||
'domain' => $domain,
|
||||
'resourceType' => 'deployment',
|
||||
'resourceId' => $deploymentId,
|
||||
'resourceInternalId' => $deployment->getInternalId(),
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ use Utopia\Database\Exception\Conflict;
|
|||
use Utopia\Database\Exception\Restricted;
|
||||
use Utopia\Database\Exception\Structure;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization as ValidatorAuthorization;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Platform\Action;
|
||||
|
|
@ -92,13 +93,13 @@ class Deletes extends Action
|
|||
$this->deleteProject($dbForPlatform, $getProjectDB, $deviceForFiles, $deviceForSites, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $document);
|
||||
break;
|
||||
case DELETE_TYPE_SITES:
|
||||
$this->deleteSite($dbForPlatform, $getProjectDB, $deviceForSites, $deviceForFunctions, $deviceForBuilds, $document, $certificates, $project);
|
||||
$this->deleteSite($dbForPlatform, $getProjectDB, $deviceForSites, $deviceForFunctions, $deviceForBuilds, $deviceForFiles, $document, $certificates, $project);
|
||||
break;
|
||||
case DELETE_TYPE_FUNCTIONS:
|
||||
$this->deleteFunction($dbForPlatform, $getProjectDB, $deviceForFunctions, $deviceForBuilds, $certificates, $document, $project);
|
||||
break;
|
||||
case DELETE_TYPE_DEPLOYMENTS:
|
||||
$this->deleteDeployment($dbForPlatform, $getProjectDB, $deviceForFunctions, $deviceForBuilds, $document, $certificates, $project);
|
||||
$this->deleteDeployment($dbForPlatform, $getProjectDB, $deviceForFunctions, $deviceForBuilds, $deviceForFiles, $document, $certificates, $project);
|
||||
break;
|
||||
case DELETE_TYPE_USERS:
|
||||
$this->deleteUser($getProjectDB, $document, $project);
|
||||
|
|
@ -749,7 +750,7 @@ class Deletes extends Action
|
|||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function deleteSite(Database $dbForPlatform, callable $getProjectDB, Device $deviceForSites, Device $deviceForFunctions, Device $deviceForBuilds, Document $document, CertificatesAdapter $certificates, Document $project): void
|
||||
private function deleteSite(Database $dbForPlatform, callable $getProjectDB, Device $deviceForSites, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForFiles, Document $document, CertificatesAdapter $certificates, Document $project): void
|
||||
{
|
||||
$dbForProject = $getProjectDB($project);
|
||||
$siteId = $document->getId();
|
||||
|
|
@ -783,9 +784,10 @@ class Deletes extends Action
|
|||
$deploymentInternalIds = [];
|
||||
$this->deleteByGroup('deployments', [
|
||||
Query::equal('resourceInternalId', [$siteInternalId])
|
||||
], $dbForProject, function (Document $document) use ($deviceForFunctions, &$deploymentInternalIds) {
|
||||
], $dbForProject, function (Document $document) use ($deviceForFunctions, $deviceForFiles, $dbForPlatform, &$deploymentInternalIds) {
|
||||
$deploymentInternalIds[] = $document->getInternalId();
|
||||
$this->deleteDeploymentFiles($deviceForFunctions, $document);
|
||||
$this->deleteDeploymentScreenshots($deviceForFiles, $dbForPlatform, $document);
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
@ -928,6 +930,53 @@ class Deletes extends Action
|
|||
$this->deleteRuntimes($getProjectDB, $document, $project);
|
||||
}
|
||||
|
||||
private function deleteDeploymentScreenshots(Device $deviceForFiles, Database $dbForPlatform, Document $deployment): void
|
||||
{
|
||||
$screenshotIds = [];
|
||||
if (!empty($deployment->getAttribute('screenshotLight', ''))) {
|
||||
$screenshotIds[] = $deployment->getAttribute('screenshotLight', '');
|
||||
}
|
||||
if (!empty($deployment->getAttribute('screenshotDark', ''))) {
|
||||
$screenshotIds[] = $deployment->getAttribute('screenshotDark', '');
|
||||
}
|
||||
|
||||
if (empty($screenshotIds)) {
|
||||
return;
|
||||
}
|
||||
Console::info("Deleting screenshots for deployment " . $deployment->getId());
|
||||
|
||||
$bucket = ValidatorAuthorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots'));
|
||||
if ($bucket->isEmpty()) {
|
||||
Console::error('Failed to get bucket for deployment screenshots');
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($screenshotIds as $id) {
|
||||
$file = ValidatorAuthorization::skip(fn () => $dbForPlatform->getDocument('bucket_' . $bucket->getInternalId(), $id));
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
Console::error('Failed to get deployment screenshot: ' . $id);
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = $file->getAttribute('path', '');
|
||||
|
||||
try {
|
||||
if ($deviceForFiles->delete($path, true)) {
|
||||
Console::success('Deleted deployment screenshot: ' . $path);
|
||||
} else {
|
||||
Console::error('Failed to delete deployment screenshot: ' . $path);
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('Failed to delete deployment screenshot: ' . $path);
|
||||
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 Device $device
|
||||
* @param Document $deployment
|
||||
|
|
@ -999,7 +1048,7 @@ class Deletes extends Action
|
|||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function deleteDeployment(Database $dbForPlatform, callable $getProjectDB, Device $deviceForFunctions, Device $deviceForBuilds, Document $document, CertificatesAdapter $certificates, Document $project): void
|
||||
private function deleteDeployment(Database $dbForPlatform, callable $getProjectDB, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForFiles, Document $document, CertificatesAdapter $certificates, Document $project): void
|
||||
{
|
||||
$projectId = $project->getId();
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
|
@ -1011,6 +1060,11 @@ class Deletes extends Action
|
|||
*/
|
||||
$this->deleteDeploymentFiles($deviceForFunctions, $document); //TODO: For sites, this should be deviceForSites
|
||||
|
||||
/**
|
||||
* Delete deployment screenshots
|
||||
*/
|
||||
$this->deleteDeploymentScreenshots($deviceForFiles, $dbForPlatform, $document);
|
||||
|
||||
/**
|
||||
* Delete builds
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -76,6 +76,18 @@ class Deployment extends Model
|
|||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('screenshotLight', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Screenshot with light theme preference file ID.',
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('screenshotDark', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Screenshot with dark theme preference file ID.',
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('status', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The deployment status. Possible values are "processing", "building", "waiting", "ready", and "failed".',
|
||||
|
|
|
|||
|
|
@ -51,6 +51,18 @@ trait FunctionsBase
|
|||
$this->assertEquals('ready', $deployment['body']['status'], 'Deployment status is not ready, deployment: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT));
|
||||
}, 50000, 500);
|
||||
|
||||
// Not === so multipart/form-data works fine too
|
||||
if (($params['activate'] ?? false) == true) {
|
||||
$this->assertEventually(function () use ($functionId, $deploymentId) {
|
||||
$function = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]));
|
||||
$this->assertEquals($deploymentId, $function['body']['deployment'], 'Deployment is not activated, deployment: ' . json_encode($function['body'], JSON_PRETTY_PRINT));
|
||||
}, 100000, 500);
|
||||
}
|
||||
|
||||
return $deploymentId;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,18 @@ trait SitesBase
|
|||
$this->assertEquals('ready', $deployment['body']['status'], 'Deployment status is not ready, deployment: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT));
|
||||
}, 100000, 500);
|
||||
|
||||
// Not === so multipart/form-data works fine too
|
||||
if (($params['activate'] ?? false) == true) {
|
||||
$this->assertEventually(function () use ($siteId, $deploymentId) {
|
||||
$site = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]));
|
||||
$this->assertEquals($deploymentId, $site['body']['deploymentId'], 'Deployment is not activated, deployment: ' . json_encode($site['body'], JSON_PRETTY_PRINT));
|
||||
}, 100000, 500);
|
||||
}
|
||||
|
||||
return $deploymentId;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -313,10 +313,7 @@ class SitesCustomServerTest extends Scope
|
|||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]));
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/');
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Env variable is Appwrite", $response['body']);
|
||||
|
|
@ -1249,19 +1246,13 @@ class SitesCustomServerTest extends Scope
|
|||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]));
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/');
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Astro Blog", $response['body']);
|
||||
$this->assertStringContainsString("Hello, Astronaut!", $response['body']);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/about', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]));
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/about');
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Astro Blog", $response['body']);
|
||||
|
|
@ -1300,10 +1291,7 @@ class SitesCustomServerTest extends Scope
|
|||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]));
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/');
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringNotContainsString("This domain is not connected to any Appwrite resource yet", $response['body']);
|
||||
|
|
@ -1350,10 +1338,7 @@ class SitesCustomServerTest extends Scope
|
|||
$this->assertEquals(0, $rules['body']['total']);
|
||||
}, 50000, 500);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]));
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/');
|
||||
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("This domain is not connected to any Appwrite resource yet", $response['body']);
|
||||
|
|
@ -1416,10 +1401,7 @@ class SitesCustomServerTest extends Scope
|
|||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]));
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/');
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Hello Appwrite", $response['body']);
|
||||
|
|
@ -1430,10 +1412,7 @@ class SitesCustomServerTest extends Scope
|
|||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $previewDomain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]));
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/');
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Hello Appwrite", $response['body']);
|
||||
|
|
@ -1500,5 +1479,77 @@ class SitesCustomServerTest extends Scope
|
|||
$this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin']);
|
||||
}
|
||||
|
||||
public function testSiteScreenshot(): void
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Themed site',
|
||||
'framework' => 'other',
|
||||
'adapter' => 'static',
|
||||
'buildRuntime' => 'static-1',
|
||||
'outputDirectory' => './',
|
||||
'buildCommand' => '',
|
||||
'installCommand' => '',
|
||||
'fallbackFile' => '',
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($siteId);
|
||||
|
||||
$domain = $this->setupSiteDomain($siteId);
|
||||
|
||||
$deploymentId = $this->setupDeployment($siteId, [
|
||||
'code' => $this->packageSite('static-themed'),
|
||||
'activate' => 'true'
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($deploymentId);
|
||||
|
||||
$domain = $this->getSiteDomain($siteId);
|
||||
$this->assertNotEmpty($domain);
|
||||
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/');
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Themed website", $response['body']);
|
||||
$this->assertStringContainsString("@media (prefers-color-scheme: dark)", $response['body']);
|
||||
|
||||
$deployment = $this->getDeployment($siteId, $deploymentId);
|
||||
|
||||
$this->assertEquals(200, $deployment['headers']['status-code']);
|
||||
$this->assertNotEmpty($deployment['body']['screenshotLight']);
|
||||
$this->assertNotEmpty($deployment['body']['screenshotDark']);
|
||||
|
||||
$screenshotId = $deployment['body']['screenshotLight'];
|
||||
$file = $this->client->call(Client::METHOD_GET, "/storage/buckets/screenshots/files/$screenshotId/view?project=console&mode=admin", array_merge([
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $file['headers']['status-code']);
|
||||
$this->assertNotEmpty(200, $file['body']);
|
||||
$this->assertGreaterThan(1, $file['headers']['content-length']);
|
||||
$this->assertEquals('image/png', $file['headers']['content-type']);
|
||||
|
||||
$screenshotHash = \md5($file['body']);
|
||||
$this->assertNotEmpty($screenshotHash);
|
||||
|
||||
$screenshotId = $deployment['body']['screenshotDark'];
|
||||
$file = $this->client->call(Client::METHOD_GET, "/storage/buckets/screenshots/files/$screenshotId/view?project=console&mode=admin", array_merge([
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $file['headers']['status-code']);
|
||||
$this->assertNotEmpty(200, $file['body']);
|
||||
$this->assertGreaterThan(1, $file['headers']['content-length']);
|
||||
$this->assertEquals('image/png', $file['headers']['content-type']);
|
||||
|
||||
$screenshotDarkHash = \md5($file['body']);
|
||||
$this->assertNotEmpty($screenshotDarkHash);
|
||||
|
||||
$this->assertNotEquals($screenshotDarkHash, $screenshotHash);
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
// TODO: Add tests for deletion of resources when site is deleted
|
||||
}
|
||||
|
|
|
|||
23
tests/resources/sites/static-themed/index.html
Normal file
23
tests/resources/sites/static-themed/index.html
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Themed website</title>
|
||||
<style>
|
||||
/* Light theme */
|
||||
body {
|
||||
background-color: #ffffff;
|
||||
color: #000000;
|
||||
}
|
||||
/* Dark theme */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #000000;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
Loading…
Reference in a new issue