Merge branch '1.8.x' of https://github.com/appwrite/appwrite into migration-endpoint

This commit is contained in:
fogelito 2026-02-01 08:49:01 +02:00
commit d89539b56a
15 changed files with 185 additions and 85 deletions

View file

@ -50,17 +50,12 @@
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Poppins';
src: url('https://assets.appwrite.io/fonts/poppins/poppins-v23-latin-regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
</style>
<style>
body {
.main {
max-width: 650px;
margin: 0 auto;
margin-top: 32px;
padding: 32px;
line-height: 1.5;
color: #616b7c;
@ -68,15 +63,12 @@
font-weight: 400;
font-family: "Inter", sans-serif;
background-color: #ffffff;
margin: 0;
padding: 0;
line-height: 150%;
}
a {
.main a {
color: currentColor;
word-break: break-all;
}
a.button {
.main a.button {
box-sizing: border-box;
display: inline-block;
text-align: center;
@ -87,8 +79,8 @@
border: 1px solid #414146;
border-radius: 8px;
}
a.button:hover,
a.button:focus {
.main a.button:hover,
.main a.button:focus {
opacity: 0.8;
}
table {
@ -102,11 +94,6 @@
td {
vertical-align: top;
}
.main {
max-width: 650px;
margin: 0 auto;
margin-top: 32px;
}
h1 {
font-size: 22px;
margin-bottom: 0px;
@ -138,9 +125,6 @@
border: none;
border-top: 1px solid #e8e9f0;
}
h* {
font-family: 'Poppins', sans-serif;
}
p {
margin-bottom: 10px;
}
@ -155,21 +139,20 @@
<body style="direction: {{direction}}">
<div style="display: none; overflow: hidden; max-height: 0; max-width: 0; opacity: 0; line-height: 1px;">
{{preview}}
<div>{{previewWhitespace}}</div>
</div>
<div style="display: none; overflow: hidden; max-height: 0; max-width: 0; opacity: 0; line-height: 1px;">
{{preview}}
<div>{{previewWhitespace}}</div>
</div>
<div style="max-width:650px; word-wrap: break-word; overflow-wrap: break-word;
word-break: normal; margin:0 auto;">
<table style="margin-top: 32px">
<tr>
<td>
{{body}}
</td>
</tr>
</table>
</div>
<div class="main" style="max-width:650px; word-wrap: break-word; overflow-wrap: break-word; word-break: normal; margin:0 auto;">
<table style="margin-top: 32px">
<tr>
<td>
{{body}}
</td>
</tr>
</table>
</div>
</body>
</html>

View file

@ -23,4 +23,5 @@ return [
'privacyUrl' => APP_EMAIL_PRIVACY_URL,
'websiteUrl' => 'https://' . APP_DOMAIN,
'emailSenderName' => APP_EMAIL_PLATFORM_NAME,
'sitePreviewDomain' => System::getEnv('_APP_DOMAIN_SITES', ''),
];

View file

@ -195,7 +195,7 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, arr
->setPreview($preview)
->setBody($body)
->setBodyTemplate($bodyTemplate)
->setVariables($emailVariables)
->appendVariables($emailVariables)
->setRecipient($email);
// since this is console project, set email sender name!
@ -2301,7 +2301,7 @@ App::post('/v1/account/tokens/magic-url')
->setSubject($subject)
->setPreview($preview)
->setBody($body)
->setVariables($emailVariables)
->appendVariables($emailVariables)
->setRecipient($email);
if ($project->getId() === 'console') {
@ -2602,7 +2602,7 @@ App::post('/v1/account/tokens/email')
->setPreview($preview)
->setBody($body)
->setBodyTemplate($bodyTemplate)
->setVariables($emailVariables)
->appendVariables($emailVariables)
->setRecipient($email);
// since this is console project, set email sender name!
@ -3678,7 +3678,7 @@ App::post('/v1/account/recovery')
->setRecipient($profile->getAttribute('email', ''))
->setName($profile->getAttribute('name', ''))
->setBody($body)
->setVariables($emailVariables)
->appendVariables($emailVariables)
->setSubject($subject)
->setPreview($preview);
@ -4009,7 +4009,7 @@ App::post('/v1/account/verifications/email')
->setPreview($preview)
->setBody($body)
->setBodyTemplate($bodyTemplate)
->setVariables($emailVariables)
->appendVariables($emailVariables)
->setRecipient($user->getAttribute('email'))
->setName($user->getAttribute('name') ?? '');

View file

@ -779,7 +779,7 @@ App::post('/v1/teams/:teamId/memberships')
->setPreview($preview)
->setRecipient($invitee->getAttribute('email'))
->setName($invitee->getAttribute('name', ''))
->setVariables($emailVariables)
->appendVariables($emailVariables)
->trigger();
} elseif (!empty($phone)) {
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {

View file

@ -341,7 +341,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$projectId = $project->getId();
// Deployment preview
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$sitesDomain = $platform['sitePreviewDomain'];
$domain = ID::unique() . "." . $sitesDomain;
$ruleId = md5($domain);
$previewRuleId = $ruleId;

View file

@ -364,6 +364,18 @@ class Mail extends Event
return $this;
}
/**
* Append variables to the email event.
*
* @param array $variables
* @return self
*/
public function appendVariables(array $variables): self
{
$this->variables = \array_merge($this->variables, $variables);
return $this;
}
/**
* Set attachment
* @param string $content

View file

@ -39,38 +39,34 @@ class EventProcessor
return \json_decode($cachedFunctionEvents, true) ?? [];
}
try {
$events = [];
$limit = 100;
$sum = 100;
$offset = 0;
$events = [];
$limit = 100;
$sum = 100;
$offset = 0;
while ($sum >= $limit) {
$functions = $dbForProject->find('functions', [
Query::select(['$id', 'events']),
Query::limit($limit),
Query::offset($offset),
Query::orderAsc('$sequence'),
]);
while ($sum >= $limit) {
$functions = $dbForProject->getAuthorization()->skip(fn () => $dbForProject->find('functions', [
Query::select(['$id', 'events']),
Query::limit($limit),
Query::offset($offset),
Query::orderAsc('$sequence'),
]));
$sum = \count($functions);
$offset = $offset + $limit;
$sum = \count($functions);
$offset = $offset + $limit;
foreach ($functions as $function) {
$functionEvents = $function->getAttribute('events', []);
if (!empty($functionEvents)) {
$events = array_merge($events, $functionEvents);
}
foreach ($functions as $function) {
$functionEvents = $function->getAttribute('events', []);
if (!empty($functionEvents)) {
\array_push($events, ...$functionEvents);
}
}
$uniqueEvents = \array_flip(\array_unique($events));
$dbForProject->getCache()->save($cacheKey, \json_encode($uniqueEvents));
return $uniqueEvents;
} catch (\Throwable $e) {
return [];
}
$uniqueEvents = \array_flip(\array_unique($events));
$dbForProject->getCache()->save($cacheKey, \json_encode($uniqueEvents));
return $uniqueEvents;
}
/**
@ -97,7 +93,7 @@ class EventProcessor
$webhookEvents = $webhook->getAttribute('events', []);
if (!empty($webhookEvents)) {
$events = array_merge($events, $webhookEvents);
\array_push($events, ...$webhookEvents);
}
}

View file

@ -327,7 +327,7 @@ class Create extends Action
->setPreview($preview)
->setBody($body)
->setBodyTemplate($bodyTemplate)
->setVariables($emailVariables)
->appendVariables($emailVariables)
->setRecipient($user->getAttribute('email'));
// since this is console project, set email sender name!

View file

@ -144,7 +144,7 @@ class Base extends Action
return $deployment;
}
public function redeployVcsSite(Request $request, Document $site, Document $project, Document $installation, Database $dbForProject, Database $dbForPlatform, Build $queueForBuilds, Document $template, GitHub $github, bool $activate, Authorization $authorization, string $referenceType = 'branch', string $reference = ''): Document
public function redeployVcsSite(Request $request, Document $site, Document $project, Document $installation, Database $dbForProject, Database $dbForPlatform, Build $queueForBuilds, Document $template, GitHub $github, bool $activate, Authorization $authorization, string $referenceType = 'branch', string $reference = '', array $platform): Document
{
$deploymentId = ID::unique();
$providerInstallationId = $installation->getAttribute('providerInstallationId', '');
@ -235,7 +235,7 @@ class Base extends Action
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
$dbForProject->updateDocument('sites', $site->getId(), $site);
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$sitesDomain = $platform['sitePreviewDomain'];
$domain = ID::unique() . "." . $sitesDomain;
// TODO: (@Meldiron) Remove after 1.7.x migration

View file

@ -1037,7 +1037,7 @@ class Builds extends Action
// VCS branch
$branchName = $deployment->getAttribute('providerBranch');
if (!empty($branchName)) {
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$sitesDomain = $platform['sitePreviewDomain'];
$branchPrefix = substr($branchName, 0, 16);
if (strlen($branchName) > 16) {
$remainingChars = substr($branchName, 16);

View file

@ -88,6 +88,7 @@ class Create extends Action
->inject('queueForBuilds')
->inject('plan')
->inject('authorization')
->inject('platform')
->callback($this->action(...));
}
@ -108,7 +109,8 @@ class Create extends Action
Device $deviceForLocal,
Build $queueForBuilds,
array $plan,
Authorization $authorization
Authorization $authorization,
array $platform,
) {
$activate = \strval($activate) === 'true' || \strval($activate) === '1';
@ -272,7 +274,7 @@ class Create extends Action
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
$dbForProject->updateDocument('sites', $site->getId(), $site);
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$sitesDomain = $platform['sitePreviewDomain'];
$domain = ID::unique() . "." . $sitesDomain;
// TODO: (@Meldiron) Remove after 1.7.x migration
@ -342,7 +344,7 @@ class Create extends Action
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
$dbForProject->updateDocument('sites', $site->getId(), $site);
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$sitesDomain = $platform['sitePreviewDomain'];
$domain = ID::unique() . "." . $sitesDomain;
$ruleId = md5($domain);
$authorization->skip(

View file

@ -66,6 +66,7 @@ class Create extends Action
->inject('queueForBuilds')
->inject('deviceForSites')
->inject('authorization')
->inject('platform')
->callback($this->action(...));
}
@ -80,7 +81,8 @@ class Create extends Action
Event $queueForEvents,
Build $queueForBuilds,
Device $deviceForSites,
Authorization $authorization
Authorization $authorization,
array $platform
) {
$site = $dbForProject->getDocument('sites', $siteId);
@ -143,7 +145,7 @@ class Create extends Action
$dbForProject->updateDocument('sites', $site->getId(), $site);
// Preview deployments for sites
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$sitesDomain = $platform['sitePreviewDomain'];
$domain = ID::unique() . "." . $sitesDomain;
// TODO: (@Meldiron) Remove after 1.7.x migration

View file

@ -80,6 +80,7 @@ class Create extends Base
->inject('queueForBuilds')
->inject('gitHub')
->inject('authorization')
->inject('platform')
->callback($this->action(...));
}
@ -99,7 +100,8 @@ class Create extends Base
Event $queueForEvents,
Build $queueForBuilds,
GitHub $github,
Authorization $authorization
Authorization $authorization,
array $platform
) {
$site = $dbForProject->getDocument('sites', $siteId);
@ -133,6 +135,7 @@ class Create extends Base
github: $github,
activate: $activate,
authorization: $authorization,
platform: $platform
);
$queueForEvents
@ -186,7 +189,7 @@ class Create extends Base
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
$dbForProject->updateDocument('sites', $site->getId(), $site);
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$sitesDomain = $platform['sitePreviewDomain'];
$domain = ID::unique() . "." . $sitesDomain;
// TODO: (@Meldiron) Remove after 1.7.x migration

View file

@ -74,6 +74,7 @@ class Create extends Base
->inject('queueForBuilds')
->inject('gitHub')
->inject('authorization')
->inject('platform')
->callback($this->action(...));
}
@ -90,7 +91,8 @@ class Create extends Base
Event $queueForEvents,
Build $queueForBuilds,
GitHub $github,
Authorization $authorization
Authorization $authorization,
array $platform
) {
$site = $dbForProject->getDocument('sites', $siteId);
@ -115,7 +117,8 @@ class Create extends Base
activate: $activate,
authorization: $authorization,
reference: $reference,
referenceType: $type
referenceType: $type,
platform: $platform
);
$queueForEvents

View file

@ -7,6 +7,7 @@ use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideClient;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\System\System;
@ -510,4 +511,101 @@ class FunctionsCustomClientTest extends Scope
$template = $this->getTemplate('invalid-template-id');
$this->assertEquals(404, $template['headers']['status-code']);
}
/**
* Test that event-triggered functions work when the triggering request
* comes from a client SDK (session auth) that doesn't have permission
* to read the functions collection.
*/
public function testEventTriggerWithClientAuth()
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test Client Event Trigger',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'events' => [
'databases.*.collections.*.documents.*.create',
],
'timeout' => 15,
]);
$this->setupDeployment($functionId, [
'code' => $this->packageFunction('event-handler'),
'activate' => true
]);
$database = $this->client->call(Client::METHOD_POST, '/databases', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'databaseId' => ID::unique(),
'name' => 'Test Database',
]);
$this->assertEquals(201, $database['headers']['status-code']);
$databaseId = $database['body']['$id'];
$collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'collectionId' => ID::unique(),
'name' => 'Test Collection',
'permissions' => [
Permission::create(Role::users()),
],
'documentSecurity' => false,
]);
$this->assertEquals(201, $collection['headers']['status-code']);
$collectionId = $collection['body']['$id'];
$attribute = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'key' => 'name',
'size' => 255,
'required' => false,
]);
$this->assertEquals(202, $attribute['headers']['status-code']);
sleep(2);
$document = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documentId' => ID::unique(),
'data' => ['name' => 'Test Document'],
]);
$this->assertEquals(201, $document['headers']['status-code']);
$documentId = $document['body']['$id'];
$this->assertEventually(function () use ($functionId, $documentId) {
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $executions['headers']['status-code']);
$this->assertGreaterThan(0, count($executions['body']['executions']), 'Function should have been triggered by document creation');
$lastExecution = $executions['body']['executions'][0];
$this->assertEquals('completed', $lastExecution['status']);
$this->assertEquals(204, $lastExecution['responseStatusCode']);
$this->assertStringContainsString($documentId, $lastExecution['logs']);
}, 20000, 500);
$this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->cleanupFunction($functionId);
}
}