$headers
*/
diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php
index 890c8572d9..67bea01a28 100644
--- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php
+++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php
@@ -444,7 +444,7 @@ class Builds extends Action
Console::execute('rsync -av --exclude \'.git\' ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory . '/') . ' ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $stdout, $stderr);
// Commit and push
- $exit = Console::execute('git config --global user.email "team@appwrite.io" && git config --global user.name "Appwrite" && cd ' . \escapeshellarg($tmpDirectory) . ' && git checkout -b ' . \escapeshellarg($branchName) . ' && git add . && git commit -m "Create ' . \escapeshellarg($resource->getAttribute('name', '')) . ' function" && git push origin ' . \escapeshellarg($branchName), '', $stdout, $stderr);
+ $exit = Console::execute('git config --global user.email '. \escapeshellarg(APP_VCS_GITHUB_EMAIL) .' && git config --global user.name '. \escapeshellarg(APP_VCS_GITHUB_USERNAME) .' && cd ' . \escapeshellarg($tmpDirectory) . ' && git checkout -b ' . \escapeshellarg($branchName) . ' && git add . && git commit -m "Create ' . \escapeshellarg($resource->getAttribute('name', '')) . ' function" && git push origin ' . \escapeshellarg($branchName), '', $stdout, $stderr);
if ($exit !== 0) {
throw new \Exception('Unable to push code repository: ' . $stderr);
@@ -516,7 +516,7 @@ class Builds extends Action
->setPayload($deployment->getArrayCopy())
->trigger();
- $this->runGitAction('processing', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform);
+ $this->runGitAction('processing', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime);
}
/** Request the executor to build the code... */
@@ -532,7 +532,7 @@ class Builds extends Action
->trigger();
if ($isVcsEnabled) {
- $this->runGitAction('building', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform);
+ $this->runGitAction('building', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime);
}
$deploymentModel = new Deployment();
@@ -980,6 +980,8 @@ class Builds extends Action
throw new \Exception($screenshotError);
}
+ $mimeType = "image/png";
+
foreach ($screenshots as $data) {
$key = $data['key'];
$screenshot = $data['screenshot'];
@@ -988,7 +990,7 @@ class Builds extends Action
$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");
+ $success = $deviceForFiles->write($path, $screenshot, $mimeType);
if (!$success) {
throw new \Exception("Screenshot failed to save");
@@ -1005,10 +1007,10 @@ class Builds extends Action
'name' => $fileName,
'path' => $path,
'signature' => $deviceForFiles->getFileHash($path),
- 'mimeType' => $deviceForFiles->getFileMimeType($path),
+ 'mimeType' => $mimeType,
'sizeOriginal' => \strlen($screenshot),
'sizeActual' => $deviceForFiles->getFileSize($path),
- 'algorithm' => Compression::GZIP,
+ 'algorithm' => Compression::NONE,
'comment' => '',
'chunksTotal' => 1,
'chunksUploaded' => 1,
@@ -1017,7 +1019,7 @@ class Builds extends Action
'openSSLTag' => null,
'openSSLIV' => null,
'search' => implode(' ', [$fileId, $fileName]),
- 'metadata' => ['content_type' => $deviceForFiles->getFileMimeType($path)],
+ 'metadata' => ['content_type' => $mimeType],
]);
Authorization::skip(fn () => $dbForPlatform->createDocument('bucket_' . $bucket->getSequence(), $file));
@@ -1067,7 +1069,7 @@ class Builds extends Action
->trigger();
if ($isVcsEnabled) {
- $this->runGitAction('ready', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform);
+ $this->runGitAction('ready', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime);
}
Console::success("Build id: $deploymentId created");
@@ -1285,7 +1287,7 @@ class Builds extends Action
->trigger();
if ($isVcsEnabled) {
- $this->runGitAction('failed', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform);
+ $this->runGitAction('failed', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime);
}
} finally {
$queueForRealtime
@@ -1439,98 +1441,116 @@ class Builds extends Action
Document $resource,
string $deploymentId,
Database $dbForProject,
- Database $dbForPlatform
+ Database $dbForPlatform,
+ Realtime $queueForRealtime,
): void {
- if ($resource->getAttribute('providerSilentMode', false) === true) {
- return;
- }
-
- $deployment = $dbForProject->getDocument('deployments', $deploymentId);
- $commentId = $deployment->getAttribute('providerCommentId', '');
-
- if (!empty($providerCommitHash)) {
- $message = match ($status) {
- 'ready' => 'Build succeeded.',
- 'failed' => 'Build failed.',
- 'processing' => 'Building...',
- default => $status
- };
-
- $state = match ($status) {
- 'ready' => 'success',
- 'failed' => 'failure',
- 'processing' => 'pending',
- default => $status
- };
-
- $resourceName = $resource->getAttribute('name');
- $projectName = $project->getAttribute('name');
-
- $name = "{$resourceName} ({$projectName})";
-
- $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
- $hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
-
- $projectId = $project->getId();
- $region = $project->getAttribute('region', 'default');
- $resourceId = $resource->getId();
- $providerTargetUrl = match ($resource->getCollection()) {
- 'functions' => "{$protocol}://{$hostname}/console/project-{$region}-{$projectId}/functions/function-{$resourceId}",
- 'sites' => "{$protocol}://{$hostname}/console/project-{$region}-{$projectId}/sites/site-{$resourceId}",
- default => throw new \Exception('Invalid resource type')
- };
-
- $github->updateCommitStatus($repositoryName, $providerCommitHash, $owner, $state, $message, $providerTargetUrl, $name);
- }
-
- if (!empty($commentId)) {
- $retries = 0;
-
- while (true) {
- $retries++;
-
- try {
- $dbForPlatform->createDocument('vcsCommentLocks', new Document([
- '$id' => $commentId
- ]));
- break;
- } catch (\Throwable $err) {
- if ($retries >= 9) {
- throw $err;
- }
-
- \sleep(1);
- }
+ try {
+ if ($resource->getAttribute('providerSilentMode', false) === true) {
+ return;
}
- // Wrap in try/finally to ensure lock file gets deleted
- try {
- $resourceType = match($resource->getCollection()) {
- 'functions' => 'function',
- 'sites' => 'site',
- default => throw new \Exception('Invalid resource type')
+ $deployment = $dbForProject->getDocument('deployments', $deploymentId);
+ $commentId = $deployment->getAttribute('providerCommentId', '');
+
+ if (!empty($providerCommitHash)) {
+ $message = match ($status) {
+ 'ready' => 'Build succeeded.',
+ 'failed' => 'Build failed.',
+ 'processing' => 'Building...',
+ default => $status
};
- $rule = Authorization::skip(fn () => $dbForPlatform->findOne('rules', [
- Query::equal("projectInternalId", [$project->getSequence()]),
- Query::equal("type", ["deployment"]),
- Query::equal("deploymentInternalId", [$deployment->getSequence()]),
- ]));
+ $state = match ($status) {
+ 'ready' => 'success',
+ 'failed' => 'failure',
+ 'processing' => 'pending',
+ default => $status
+ };
+
+ $resourceName = $resource->getAttribute('name');
+ $projectName = $project->getAttribute('name');
+
+ $name = "{$resourceName} ({$projectName})";
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
- $previewUrl = match($resource->getCollection()) {
- 'functions' => '',
- 'sites' => !empty($rule) ? ("{$protocol}://" . $rule->getAttribute('domain', '')) : '',
+ $hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
+
+ $projectId = $project->getId();
+ $region = $project->getAttribute('region', 'default');
+ $resourceId = $resource->getId();
+ $providerTargetUrl = match ($resource->getCollection()) {
+ 'functions' => "{$protocol}://{$hostname}/console/project-{$region}-{$projectId}/functions/function-{$resourceId}",
+ 'sites' => "{$protocol}://{$hostname}/console/project-{$region}-{$projectId}/sites/site-{$resourceId}",
default => throw new \Exception('Invalid resource type')
};
- $comment = new Comment();
- $comment->parseComment($github->getComment($owner, $repositoryName, $commentId));
- $comment->addBuild($project, $resource, $resourceType, $status, $deployment->getId(), ['type' => 'logs'], $previewUrl);
- $github->updateComment($owner, $repositoryName, $commentId, $comment->generateComment());
- } finally {
- $dbForPlatform->deleteDocument('vcsCommentLocks', $commentId);
+ $github->updateCommitStatus($repositoryName, $providerCommitHash, $owner, $state, $message, $providerTargetUrl, $name);
}
+
+ if (!empty($commentId)) {
+ $retries = 0;
+
+ while (true) {
+ $retries++;
+
+ try {
+ $dbForPlatform->createDocument('vcsCommentLocks', new Document([
+ '$id' => $commentId
+ ]));
+ break;
+ } catch (\Throwable $err) {
+ if ($retries >= 9) {
+ throw $err;
+ }
+
+ \sleep(1);
+ }
+ }
+
+ // Wrap in try/finally to ensure lock file gets deleted
+ try {
+ $resourceType = match($resource->getCollection()) {
+ 'functions' => 'function',
+ 'sites' => 'site',
+ default => throw new \Exception('Invalid resource type')
+ };
+
+ $rule = Authorization::skip(fn () => $dbForPlatform->findOne('rules', [
+ Query::equal("projectInternalId", [$project->getSequence()]),
+ Query::equal("type", ["deployment"]),
+ Query::equal("deploymentInternalId", [$deployment->getSequence()]),
+ ]));
+
+ $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
+ $previewUrl = match($resource->getCollection()) {
+ 'functions' => '',
+ 'sites' => !empty($rule) ? ("{$protocol}://" . $rule->getAttribute('domain', '')) : '',
+ default => throw new \Exception('Invalid resource type')
+ };
+
+ $comment = new Comment();
+ $comment->parseComment($github->getComment($owner, $repositoryName, $commentId));
+ $comment->addBuild($project, $resource, $resourceType, $status, $deployment->getId(), ['type' => 'logs'], $previewUrl);
+ $github->updateComment($owner, $repositoryName, $commentId, $comment->generateComment());
+ } finally {
+ $dbForPlatform->deleteDocument('vcsCommentLocks', $commentId);
+ }
+ }
+ } catch (\Throwable $th) {
+ Console::warning("Git action failed:");
+ Console::warning($th->getMessage());
+ Console::warning($th->getTraceAsString());
+
+ $logs = $deployment->getAttribute('buildLogs', '');
+ $date = \date('H:i:s');
+ $logs .= "[90m[$date] [90m[[0mappwrite[90m][33m Git action failed. Deployment will continue. [0m\n";
+
+ $deployment->setAttribute('buildLogs', $logs);
+ $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
+
+ $queueForRealtime
+ ->setPayload($deployment->getArrayCopy())
+ ->trigger();
}
}
diff --git a/src/Appwrite/Platform/Tasks/ScheduleBase.php b/src/Appwrite/Platform/Tasks/ScheduleBase.php
index 7686815868..222051a67f 100644
--- a/src/Appwrite/Platform/Tasks/ScheduleBase.php
+++ b/src/Appwrite/Platform/Tasks/ScheduleBase.php
@@ -11,7 +11,6 @@ use Utopia\Database\Exception;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Action;
-use Utopia\Pools\Group;
use Utopia\Queue\Broker\Pool as BrokerPool;
use Utopia\System\System;
use Utopia\Telemetry\Adapter as Telemetry;
@@ -26,7 +25,7 @@ abstract class ScheduleBase extends Action
protected array $schedules = [];
protected BrokerPool $publisher;
- protected ?BrokerPool $publisherRedis = null;
+ protected BrokerPool $publisherMigrations;
private ?Histogram $collectSchedulesTelemetryDuration = null;
private ?Gauge $collectSchedulesTelemetryCount = null;
@@ -36,7 +35,7 @@ abstract class ScheduleBase extends Action
abstract public static function getName(): string;
abstract public static function getSupportedResource(): string;
abstract public static function getCollectionId(): string;
- abstract protected function enqueueResources(Group $pools, Database $dbForPlatform, callable $getProjectDB): void;
+ abstract protected function enqueueResources(Database $dbForPlatform, callable $getProjectDB): void;
public function __construct()
{
@@ -44,7 +43,8 @@ abstract class ScheduleBase extends Action
$this
->desc("Execute {$type}s scheduled in Appwrite")
- ->inject('pools')
+ ->inject('publisher')
+ ->inject('publisherMigrations')
->inject('dbForPlatform')
->inject('getProjectDB')
->inject('telemetry')
@@ -67,18 +67,13 @@ abstract class ScheduleBase extends Action
* 2. Create timer that sync all changes from 'schedules' collection to local copy. Only reading changes thanks to 'resourceUpdatedAt' attribute
* 3. Create timer that prepares coroutines for soon-to-execute schedules. When it's ready, coroutine sleeps until exact time before sending request to worker.
*/
- public function action(Group $pools, Database $dbForPlatform, callable $getProjectDB, Telemetry $telemetry): void
+ public function action(BrokerPool $publisher, BrokerPool $publisherMigrations, Database $dbForPlatform, callable $getProjectDB, Telemetry $telemetry): void
{
Console::title(\ucfirst(static::getSupportedResource()) . ' scheduler V1');
Console::success(APP_NAME . ' ' . \ucfirst(static::getSupportedResource()) . ' scheduler v1 has started');
- $this->publisher = new BrokerPool($pools->get('publisher'));
-
- try {
- $this->publisherRedis = new BrokerPool($pools->get('publisherRedis'));
- } catch (\Throwable) {
- $this->publisherRedis = null;
- }
+ $this->publisher = $publisher;
+ $this->publisherMigrations = $publisherMigrations;
$this->scheduleTelemetryCount = $telemetry->createGauge('task.schedule.count');
$this->collectSchedulesTelemetryDuration = $telemetry->createHistogram('task.schedule.collect_schedules.duration', 's');
@@ -101,7 +96,7 @@ abstract class ScheduleBase extends Action
while (true) {
try {
- go(fn () => $this->enqueueResources($pools, $dbForPlatform, $getProjectDB));
+ go(fn () => $this->enqueueResources($dbForPlatform, $getProjectDB));
$this->scheduleTelemetryCount->record(count($this->schedules), ['resourceType' => static::getSupportedResource()]);
sleep(static::ENQUEUE_TIMER);
} catch (\Throwable $th) {
diff --git a/src/Appwrite/Platform/Tasks/ScheduleExecutions.php b/src/Appwrite/Platform/Tasks/ScheduleExecutions.php
index acb2dd3070..96a5a05f0e 100644
--- a/src/Appwrite/Platform/Tasks/ScheduleExecutions.php
+++ b/src/Appwrite/Platform/Tasks/ScheduleExecutions.php
@@ -5,8 +5,6 @@ namespace Appwrite\Platform\Tasks;
use Appwrite\Event\Func;
use Swoole\Coroutine as Co;
use Utopia\Database\Database;
-use Utopia\Pools\Group;
-use Utopia\System\System;
class ScheduleExecutions extends ScheduleBase
{
@@ -28,17 +26,11 @@ class ScheduleExecutions extends ScheduleBase
return 'executions';
}
- protected function enqueueResources(Group $pools, Database $dbForPlatform, callable $getProjectDB): void
+ protected function enqueueResources(Database $dbForPlatform, callable $getProjectDB): void
{
$intervalEnd = (new \DateTime())->modify('+' . self::ENQUEUE_TIMER . ' seconds');
- $isRedisFallback = \str_contains(System::getEnv('_APP_WORKER_REDIS_FALLBACK', ''), 'functions');
-
- $queueForFunctions = new Func(
- $isRedisFallback
- ? $this->publisherRedis
- : $this->publisher
- );
+ $queueForFunctions = new Func($this->publisher);
foreach ($this->schedules as $schedule) {
if (!$schedule['active']) {
diff --git a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php
index 7a3363d74d..43f1025c08 100644
--- a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php
+++ b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php
@@ -8,7 +8,6 @@ use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Pools\Group;
-use Utopia\System\System;
class ScheduleFunctions extends ScheduleBase
{
@@ -32,7 +31,7 @@ class ScheduleFunctions extends ScheduleBase
return 'functions';
}
- protected function enqueueResources(Group $pools, Database $dbForPlatform, callable $getProjectDB): void
+ protected function enqueueResources(Database $dbForPlatform, callable $getProjectDB): void
{
$timerStart = \microtime(true);
$time = DateTime::now();
@@ -91,13 +90,7 @@ class ScheduleFunctions extends ScheduleBase
$this->updateProjectAccess($schedule['project'], $dbForPlatform);
- $isRedisFallback = \str_contains(System::getEnv('_APP_WORKER_REDIS_FALLBACK', ''), 'functions');
-
- $queueForFunctions = new Func(
- $isRedisFallback
- ? $this->publisherRedis
- : $this->publisher
- );
+ $queueForFunctions = new Func($this->publisher);
$queueForFunctions
->setType('schedule')
diff --git a/src/Appwrite/Platform/Tasks/ScheduleMessages.php b/src/Appwrite/Platform/Tasks/ScheduleMessages.php
index af15f9583f..c4e1376ff9 100644
--- a/src/Appwrite/Platform/Tasks/ScheduleMessages.php
+++ b/src/Appwrite/Platform/Tasks/ScheduleMessages.php
@@ -4,7 +4,6 @@ namespace Appwrite\Platform\Tasks;
use Appwrite\Event\Messaging;
use Utopia\Database\Database;
-use Utopia\Pools\Group;
class ScheduleMessages extends ScheduleBase
{
@@ -26,7 +25,7 @@ class ScheduleMessages extends ScheduleBase
return 'messages';
}
- protected function enqueueResources(Group $pools, Database $dbForPlatform, callable $getProjectDB): void
+ protected function enqueueResources(Database $dbForPlatform, callable $getProjectDB): void
{
foreach ($this->schedules as $schedule) {
if (!$schedule['active']) {
diff --git a/src/Appwrite/Platform/Workers/Certificates.php b/src/Appwrite/Platform/Workers/Certificates.php
index a9104e0017..207f95ff7d 100644
--- a/src/Appwrite/Platform/Workers/Certificates.php
+++ b/src/Appwrite/Platform/Workers/Certificates.php
@@ -382,12 +382,14 @@ class Certificates extends Action
];
$subject = \sprintf($locale->getText("emails.certificate.subject"), $domain);
+ $preview = \sprintf($locale->getText("emails.certificate.preview"), $domain);
$queueForMails
->setSubject($subject)
+ ->setPreview($preview)
->setBody($body)
->setName('Appwrite Administrator')
- ->setbodyTemplate(__DIR__ . '/../../../../app/config/locale/templates/email-base-styled.tpl')
+ ->setBodyTemplate(__DIR__ . '/../../../../app/config/locale/templates/email-base-styled.tpl')
->setVariables($emailVariables)
->setRecipient(System::getEnv('_APP_EMAIL_CERTIFICATES', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS')))
->trigger();
diff --git a/src/Appwrite/Platform/Workers/Mails.php b/src/Appwrite/Platform/Workers/Mails.php
index 4e8b5e085c..117b689863 100644
--- a/src/Appwrite/Platform/Workers/Mails.php
+++ b/src/Appwrite/Platform/Workers/Mails.php
@@ -14,6 +14,11 @@ use Utopia\System\System;
class Mails extends Action
{
+ protected int $previewMaxLen = 150;
+
+ protected string $whitespaceCodes = ' ';
+
+
public static function getName(): string
{
return 'mails';
@@ -74,6 +79,7 @@ class Mails extends Action
$variables['host'] = $protocol . '://' . $hostname;
$name = $payload['name'];
$body = $payload['body'];
+ $preview = $payload['preview'] ?? '';
$variables['subject'] = $subject;
$variables['year'] = date("Y");
@@ -92,6 +98,27 @@ class Mails extends Action
foreach ($this->richTextParams as $key => $value) {
$bodyTemplate->setParam('{{' . $key . '}}', $value, escapeHtml: false);
}
+
+ $previewWhitespace = '';
+
+ if (!empty($preview)) {
+ $previewTemplate = Template::fromString($preview);
+ foreach ($variables as $key => $value) {
+ $previewTemplate->setParam('{{' . $key . '}}', $value);
+ }
+ // render() will return the subject in tags, so use strip_tags() to remove them
+ $preview = \strip_tags($previewTemplate->render());
+
+ $previewLen = strlen($preview);
+ if ($previewLen < $this->previewMaxLen) {
+ $previewWhitespace = str_repeat($this->whitespaceCodes, $this->previewMaxLen - $previewLen);
+ }
+ }
+
+
+ $bodyTemplate->setParam('{{preview}}', $preview);
+ $bodyTemplate->setParam('{{previewWhitespace}}', $previewWhitespace, false);
+
$body = $bodyTemplate->render();
$subjectTemplate = Template::fromString($subject);
diff --git a/src/Appwrite/Platform/Workers/Webhooks.php b/src/Appwrite/Platform/Workers/Webhooks.php
index 3ffc3f0aad..394eae5fb8 100644
--- a/src/Appwrite/Platform/Workers/Webhooks.php
+++ b/src/Appwrite/Platform/Workers/Webhooks.php
@@ -241,6 +241,7 @@ class Webhooks extends Action
// TODO: Use setbodyTemplate once #7307 is merged
$subject = 'Webhook deliveries have been paused';
+ $preview = 'Webhook deliveries to your endpoint have been paused.';
$body = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-base-styled.tpl');
$body
@@ -250,6 +251,7 @@ class Webhooks extends Action
$queueForMails
->setSubject($subject)
+ ->setPreview($preview)
->setBody($body->render());
foreach ($users as $user) {
diff --git a/src/Appwrite/Template/Template.php b/src/Appwrite/Template/Template.php
index c01d54389b..e0568c98e9 100644
--- a/src/Appwrite/Template/Template.php
+++ b/src/Appwrite/Template/Template.php
@@ -63,7 +63,7 @@ class Template extends View
*
* @throws Exception
*/
- public function render($minify = true): string
+ public function render($minify = true, $useContent = false): string
{
if ($this->rendered) { // Don't render any template
return '';
@@ -72,7 +72,7 @@ class Template extends View
if (\is_readable($this->path)) {
$template = \file_get_contents($this->path); // Include template file
} elseif (!empty($this->content)) {
- $template = $this->print($this->content, self::FILTER_NL2P);
+ $template = !$useContent ? $this->print($this->content, self::FILTER_NL2P) : $this->content;
} else {
throw new Exception('"' . $this->path . '" template is not readable or not found');
}
diff --git a/src/Appwrite/Utopia/Response/Model/ProviderRepository.php b/src/Appwrite/Utopia/Response/Model/ProviderRepository.php
index 518b5de9ee..eee058a05b 100644
--- a/src/Appwrite/Utopia/Response/Model/ProviderRepository.php
+++ b/src/Appwrite/Utopia/Response/Model/ProviderRepository.php
@@ -41,6 +41,12 @@ class ProviderRepository extends Model
'default' => false,
'example' => true,
])
+ ->addRule('defaultBranch', [
+ 'type' => self::TYPE_STRING,
+ 'description' => "VCS (Version Control System) repository's default branch name.",
+ 'default' => '',
+ 'example' => 'main',
+ ])
->addRule('pushedAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Last commit date in ISO 8601 format.',
diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php
index 1a77cccb18..7c83edf3e3 100644
--- a/tests/e2e/Services/Account/AccountBase.php
+++ b/tests/e2e/Services/Account/AccountBase.php
@@ -170,6 +170,7 @@ trait AccountBase
$userId = $response['body']['userId'];
$lastEmail = $this->getLastEmail();
+
$this->assertEquals('otpuser@appwrite.io', $lastEmail['to'][0]['address']);
$this->assertEquals('OTP for ' . $this->getProject()['name'] . ' Login', $lastEmail['subject']);
@@ -178,6 +179,7 @@ trait AccountBase
$code = ($matches[0] ?? [])[0] ?? '';
$this->assertNotEmpty($code);
+ $this->assertStringContainsStringIgnoringCase('Use OTP ' . $code . ' to sign in to '. $this->getProject()['name'] . '. Expires in 15 minutes.', $lastEmail['text']);
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/token', array_merge([
'origin' => 'http://localhost',
diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php
index bccc51cb8a..9c5d976476 100644
--- a/tests/e2e/Services/Account/AccountCustomClientTest.php
+++ b/tests/e2e/Services/Account/AccountCustomClientTest.php
@@ -779,6 +779,7 @@ class AccountCustomClientTest extends Scope
$this->assertEquals($email, $lastEmail['to'][0]['address']);
$this->assertEquals($name, $lastEmail['to'][0]['name']);
$this->assertEquals('Account Verification', $lastEmail['subject']);
+ $this->assertStringContainsStringIgnoringCase('Verify your email to activate your ' . $this->getProject()['name'] . ' account.', $lastEmail['text']);
$tokens = $this->extractQueryParamsFromEmailLink($lastEmail['html']);
$verification = $tokens['secret'];
@@ -1082,6 +1083,8 @@ class AccountCustomClientTest extends Scope
$this->assertEquals($email, $lastEmail['to'][0]['address']);
$this->assertEquals($name, $lastEmail['to'][0]['name']);
$this->assertEquals('Password Reset', $lastEmail['subject']);
+ $this->assertStringContainsStringIgnoringCase('Reset your ' . $this->getProject()['name'] . ' password using the link.', $lastEmail['text']);
+
$tokens = $this->extractQueryParamsFromEmailLink($lastEmail['html']);
@@ -1286,6 +1289,7 @@ class AccountCustomClientTest extends Scope
$this->assertNotEmpty($response['body']['expire']);
$this->assertEmpty($response['body']['secret']);
$this->assertEmpty($response['body']['phrase']);
+ $this->assertStringContainsStringIgnoringCase('New login detected on '. $this->getProject()['name'], $lastEmail['text']);
$userId = $response['body']['userId'];
@@ -2545,6 +2549,7 @@ class AccountCustomClientTest extends Scope
$lastEmail = $this->getLastEmail();
$this->assertEquals($email, $lastEmail['to'][0]['address']);
$this->assertEquals($this->getProject()['name'] . ' Login', $lastEmail['subject']);
+ $this->assertStringContainsStringIgnoringCase('Sign in to '. $this->getProject()['name'] . ' with your secure link. Expires in 1 hour.', $lastEmail['text']);
$this->assertStringNotContainsStringIgnoringCase('security phrase', $lastEmail['text']);
$token = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 64);
diff --git a/tests/e2e/Services/Sites/SitesConsoleClientTest.php b/tests/e2e/Services/Sites/SitesConsoleClientTest.php
index 28ce2a35ec..227e36a50e 100644
--- a/tests/e2e/Services/Sites/SitesConsoleClientTest.php
+++ b/tests/e2e/Services/Sites/SitesConsoleClientTest.php
@@ -92,12 +92,51 @@ class SitesConsoleClientTest extends Scope
$this->assertNotEquals($screenshotDarkHash, $screenshotHash);
+ $screenshotId = $deployment['body']['screenshotLight'];
$file = $this->client->call(Client::METHOD_GET, "/storage/buckets/screenshots/files/$screenshotId/view?project=console");
$this->assertEquals(404, $file['headers']['status-code']);
+ $screenshotId = $deployment['body']['screenshotDark'];
$file = $this->client->call(Client::METHOD_GET, "/storage/buckets/screenshots/files/$screenshotId/view?project=console");
$this->assertEquals(404, $file['headers']['status-code']);
+ // Verify previews
+ $screenshotId = $deployment['body']['screenshotLight'];
+ $file = $this->client->call(Client::METHOD_GET, "/storage/buckets/screenshots/files/$screenshotId/preview?project=console", array_merge($this->getHeaders(), [
+ 'x-appwrite-mode' => 'default' // NOT ADMIN!
+ ]));
+
+ $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/preview?project=console", array_merge($this->getHeaders(), [
+ 'x-appwrite-mode' => 'default' // NOT ADMIN!
+ ]));
+
+ $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);
+
+ $screenshotId = $deployment['body']['screenshotLight'];
+ $file = $this->client->call(Client::METHOD_GET, "/storage/buckets/screenshots/files/$screenshotId/preview?project=console");
+ $this->assertEquals(404, $file['headers']['status-code']);
+
+ $screenshotId = $deployment['body']['screenshotDark'];
+ $file = $this->client->call(Client::METHOD_GET, "/storage/buckets/screenshots/files/$screenshotId/preview?project=console");
+ $this->assertEquals(404, $file['headers']['status-code']);
+
$this->cleanupSite($siteId);
}
}