From bbfd22ce2e68c86ee862ff450ee294d442554dec Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 17 Mar 2025 17:05:40 +0200 Subject: [PATCH 01/80] Index check for delete worker --- src/Appwrite/Platform/Workers/Deletes.php | 147 +++++++++++++++++----- 1 file changed, 113 insertions(+), 34 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 9b0590181a..3c8bcdd635 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -248,7 +248,8 @@ class Deletes extends Action $this->deleteByGroup( 'subscribers', [ - Query::equal('topicInternalId', [$topic->getInternalId()]) + Query::equal('topicInternalId', [$topic->getInternalId()]), + Query::orderAsc(), ], $getProjectDB($project) ); @@ -269,7 +270,8 @@ class Deletes extends Action $this->deleteByGroup( 'subscribers', [ - Query::equal('targetInternalId', [$target->getInternalId()]) + Query::equal('targetInternalId', [$target->getInternalId()]), + Query::orderAsc(), ], $dbForProject, function (Document $subscriber) use ($dbForProject, $target) { @@ -303,10 +305,14 @@ class Deletes extends Action */ private function deleteExpiredTargets(Document $project, callable $getProjectDB): void { + /** + * todo: No index found for `expired` attribute + */ $this->deleteByGroup( 'targets', [ - Query::equal('expired', [true]) + Query::equal('expired', [true]), + Query::orderAsc(), ], $getProjectDB($project), function (Document $target) use ($getProjectDB, $project) { @@ -317,10 +323,14 @@ class Deletes extends Action private function deleteSessionTargets(Document $project, callable $getProjectDB, Document $session): void { + /** + * todo: No index found for `sessionInternalId` attribute + */ $this->deleteByGroup( 'targets', [ - Query::equal('sessionInternalId', [$session->getInternalId()]) + Query::equal('sessionInternalId', [$session->getInternalId()]), + Query::orderAsc(), ], $getProjectDB($project), function (Document $target) use ($getProjectDB, $project) { @@ -348,10 +358,17 @@ class Deletes extends Action ); $query[] = Query::equal('resource', [$resource]); + if (!empty($resourceType)) { $query[] = Query::equal('resourceType', [$resourceType]); } + $query[] = Query::orderAsc(); + + /** + * todo: missing index on `resource`, `resourceType` + */ + $this->deleteByGroup( 'cache', $query, @@ -388,7 +405,7 @@ class Deletes extends Action $query = [ Query::lessThan('accessedAt', $datetime), Query::orderDesc('accessedAt'), - Query::orderDesc('$internalId'), + Query::orderDesc(), ]; $this->deleteByGroup( @@ -421,10 +438,10 @@ class Deletes extends Action // Delete Usage stats from projectDB $this->deleteByGroup('stats', [ + Query::equal('period', ['1h']), Query::lessThan('time', $hourlyUsageRetentionDatetime), Query::orderDesc('time'), - Query::orderDesc('$internalId'), - Query::equal('period', ['1h']), + Query::orderDesc(), // _key_period_time `period` ASC, `time `ASC ], $dbForProject); if ($project->getId() !== 'console') { @@ -433,10 +450,10 @@ class Deletes extends Action // Delete Usage stats from logsDB $this->deleteByGroup('stats', [ + Query::equal('period', ['1h']), Query::lessThan('time', $hourlyUsageRetentionDatetime), Query::orderDesc('time'), - Query::orderDesc('$internalId'), - Query::equal('period', ['1h']), + Query::orderDesc(), // _key_period_time `period` ASC, `time `ASC ], $dbForLogs); } } @@ -457,7 +474,8 @@ class Deletes extends Action $this->deleteByGroup( 'memberships', [ - Query::equal('teamInternalId', [$teamInternalId]) + Query::equal('teamInternalId', [$teamInternalId]), + Query::orderAsc() ], $dbForProject, function (Document $membership) use ($dbForProject) { @@ -547,7 +565,13 @@ class Deletes extends Action if ($projectTables || !\in_array($collection->getId(), $projectCollectionIds)) { $dbForProject->deleteCollection($collection->getId()); } else { - $this->deleteByGroup($collection->getId(), [], database: $dbForProject); + $this->deleteByGroup( + $collection->getId(), + [ + Query::orderAsc() + ], + database: $dbForProject + ); } } catch (Throwable $e) { Console::error('Error deleting '.$collection->getId().' '.$e->getMessage()); @@ -567,58 +591,82 @@ class Deletes extends Action // Delete Platforms $this->deleteByGroup('platforms', [ - Query::equal('projectInternalId', [$projectInternalId]) + Query::equal('projectInternalId', [$projectInternalId]), + Query::orderAsc() ], $dbForPlatform); // Delete project and function rules $this->deleteByGroup('rules', [ - Query::equal('projectInternalId', [$projectInternalId]) + Query::equal('projectInternalId', [$projectInternalId]), + Query::orderAsc() ], $dbForPlatform, function (Document $document) use ($dbForPlatform, $certificates) { $this->deleteRule($dbForPlatform, $document, $certificates); }); // Delete Keys $this->deleteByGroup('keys', [ - Query::equal('projectInternalId', [$projectInternalId]) + Query::equal('projectInternalId', [$projectInternalId]), + Query::orderAsc() ], $dbForPlatform); // Delete Webhooks $this->deleteByGroup('webhooks', [ - Query::equal('projectInternalId', [$projectInternalId]) + Query::equal('projectInternalId', [$projectInternalId]), + Query::orderAsc() ], $dbForPlatform); // Delete VCS Installations $this->deleteByGroup('installations', [ - Query::equal('projectInternalId', [$projectInternalId]) + Query::equal('projectInternalId', [$projectInternalId]), + Query::orderAsc() ], $dbForPlatform); // Delete VCS Repositories $this->deleteByGroup('repositories', [ Query::equal('projectInternalId', [$projectInternalId]), + Query::orderAsc() ], $dbForPlatform); // Delete VCS comments $this->deleteByGroup('vcsComments', [ Query::equal('projectInternalId', [$projectInternalId]), + Query::orderAsc() ], $dbForPlatform); - // Delete Schedules (No projectInternalId in this collection) + /** + * No projectInternalId in this collection + * todo: No index for `projectId` attribute + */ + // Delete Schedules () $this->deleteByGroup('schedules', [ Query::equal('projectId', [$projectId]), + Query::orderAsc() ], $dbForPlatform); // Delete metadata table if ($projectTables) { $dbForProject->deleteCollection(Database::METADATA); } elseif ($sharedTablesV1) { - $this->deleteByGroup(Database::METADATA, [], $dbForProject); + $this->deleteByGroup( + Database::METADATA, + [ + Query::orderAsc() + ], + $dbForProject + ); } elseif ($sharedTablesV2) { $queries = \array_map( fn ($id) => Query::notEqual('$id', $id), $projectCollectionIds ); - $this->deleteByGroup(Database::METADATA, $queries, $dbForProject); + $queries[] = Query::orderAsc(); + + $this->deleteByGroup( + Database::METADATA, + $queries, + $dbForProject + ); } // Delete all storage directories @@ -643,14 +691,16 @@ class Deletes extends Action // Delete all sessions of this user from the sessions table and update the sessions field of the user record $this->deleteByGroup('sessions', [ - Query::equal('userInternalId', [$userInternalId]) + Query::equal('userInternalId', [$userInternalId]), + Query::orderAsc() ], $dbForProject); $dbForProject->purgeCachedDocument('users', $userId); // Delete Memberships and decrement team membership counts $this->deleteByGroup('memberships', [ - Query::equal('userInternalId', [$userInternalId]) + Query::equal('userInternalId', [$userInternalId]), + Query::orderAsc() ], $dbForProject, function (Document $document) use ($dbForProject) { if ($document->getAttribute('confirm')) { // Count only confirmed members $teamId = $document->getAttribute('teamId'); @@ -663,19 +713,25 @@ class Deletes extends Action // Delete tokens $this->deleteByGroup('tokens', [ - Query::equal('userInternalId', [$userInternalId]) + Query::equal('userInternalId', [$userInternalId]), + Query::orderAsc() ], $dbForProject); // Delete identities + /** + * todo: Remove Duplication index `_key_userInternalId` + */ $this->deleteByGroup('identities', [ - Query::equal('userInternalId', [$userInternalId]) + Query::equal('userInternalId', [$userInternalId]), + Query::orderAsc() ], $dbForProject); // Delete targets $this->deleteByGroup( 'targets', [ - Query::equal('userInternalId', [$userInternalId]) + Query::equal('userInternalId', [$userInternalId]), + Query::orderAsc() ], $dbForProject, function (Document $target) use ($getProjectDB, $project) { @@ -699,7 +755,7 @@ class Deletes extends Action $this->deleteByGroup('executions', [ Query::lessThan('$createdAt', $datetime), Query::orderDesc('$createdAt'), - Query::orderDesc('$internalId'), + Query::orderDesc(), ], $dbForProject); } @@ -719,7 +775,7 @@ class Deletes extends Action $this->deleteByGroup('sessions', [ Query::lessThan('$createdAt', $expired), Query::orderDesc('$createdAt'), - Query::orderDesc('$internalId'), + Query::orderDesc(), ], $dbForProject); } @@ -735,7 +791,7 @@ class Deletes extends Action $this->deleteByGroup('realtime', [ Query::lessThan('timestamp', $datetime), Query::orderDesc('timestamp'), - Query::orderDesc('$internalId'), + Query::orderAsc(), // KEY "_key_timestamp" ("timestamp" DESC), ], $dbForPlatform); } @@ -755,7 +811,7 @@ class Deletes extends Action $this->deleteByGroup(Audit::COLLECTION, [ Query::lessThan('time', $auditRetention), Query::orderDesc('time'), - Query::orderDesc('$internalId'), + Query::orderAsc(), // KEY "index-time" ("time" DESC) ], $dbForProject); } catch (DatabaseException $e) { Console::error('Failed to delete audit logs for project ' . $projectId . ': ' . $e->getMessage()); @@ -780,33 +836,39 @@ class Deletes extends Action /** * Delete rules + * todo: No index for this query */ Console::info("Deleting rules for function " . $functionId); $this->deleteByGroup('rules', [ Query::equal('resourceType', ['function']), Query::equal('resourceInternalId', [$functionInternalId]), - Query::equal('projectInternalId', [$project->getInternalId()]) + Query::equal('projectInternalId', [$project->getInternalId()]), + Query::orderAsc() ], $dbForPlatform, function (Document $document) use ($project, $dbForPlatform, $certificates) { $this->deleteRule($dbForPlatform, $document, $certificates); }); /** * Delete Variables + * todo: No index for this query */ Console::info("Deleting variables for function " . $functionId); $this->deleteByGroup('variables', [ Query::equal('resourceType', ['function']), - Query::equal('resourceInternalId', [$functionInternalId]) + Query::equal('resourceInternalId', [$functionInternalId]), + Query::orderAsc() ], $dbForProject); /** * Delete Deployments + * todo: No index for `resourceInternalId`, perhaps use resourceId until fixed */ Console::info("Deleting deployments for function " . $functionId); $deploymentInternalIds = []; $this->deleteByGroup('deployments', [ - Query::equal('resourceInternalId', [$functionInternalId]) + Query::equal('resourceInternalId', [$functionInternalId]), + Query::orderAsc() ], $dbForProject, function (Document $document) use ($deviceForFunctions, &$deploymentInternalIds) { $deploymentInternalIds[] = $document->getInternalId(); $this->deleteDeploymentFiles($deviceForFunctions, $document); @@ -814,12 +876,14 @@ class Deletes extends Action /** * Delete builds + * todo: No index for `deploymentInternalId`, perhaps use deploymentId until fixed */ Console::info("Deleting builds for function " . $functionId); foreach ($deploymentInternalIds as $deploymentInternalId) { $this->deleteByGroup('builds', [ - Query::equal('deploymentInternalId', [$deploymentInternalId]) + Query::equal('deploymentInternalId', [$deploymentInternalId]), + Query::orderAsc() ], $dbForProject, function (Document $document) use ($deviceForBuilds) { $this->deleteBuildFiles($deviceForBuilds, $document); }); @@ -827,26 +891,35 @@ class Deletes extends Action /** * Delete Executions + * todo: No index for `functionInternalId` , perhaps use functionId until fixed */ Console::info("Deleting executions for function " . $functionId); $this->deleteByGroup('executions', [ - Query::equal('functionInternalId', [$functionInternalId]) + Query::equal('functionInternalId', [$functionInternalId]), + Query::orderAsc() ], $dbForProject); /** * Delete VCS Repositories and VCS Comments + * todo: no index for this query */ Console::info("Deleting VCS repositories and comments linked to function " . $functionId); $this->deleteByGroup('repositories', [ Query::equal('projectInternalId', [$project->getInternalId()]), Query::equal('resourceInternalId', [$functionInternalId]), Query::equal('resourceType', ['function']), + Query::orderAsc() ], $dbForPlatform, function (Document $document) use ($dbForPlatform) { $providerRepositoryId = $document->getAttribute('providerRepositoryId', ''); $projectInternalId = $document->getAttribute('projectInternalId', ''); + + /** + * todo: add index to this query + */ $this->deleteByGroup('vcsComments', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::equal('projectInternalId', [$projectInternalId]), + Query::orderAsc() ], $dbForPlatform); }); @@ -942,11 +1015,13 @@ class Deletes extends Action /** * Delete builds + * todo: no index for `deploymentInternalId` change to temporary to deploymentId? */ Console::info("Deleting builds for deployment " . $deploymentId); $this->deleteByGroup('builds', [ - Query::equal('deploymentInternalId', [$deploymentInternalId]) + Query::equal('deploymentInternalId', [$deploymentInternalId]), + Query::orderAsc() ], $dbForProject, function (Document $document) use ($deviceForBuilds) { $this->deleteBuildFiles($deviceForBuilds, $document); }); @@ -974,6 +1049,10 @@ class Deletes extends Action ): void { $start = \microtime(true); + /** + * deleteDocuments uses a cursor, we need to add a unique order by field or use default + */ + try { $documents = $database->deleteDocuments($collection, $queries); } catch (Throwable $th) { From 5f7ceecb30e64f11fa6bb0a5cb746007cc983b8d Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 18 Mar 2025 03:46:14 +0000 Subject: [PATCH 02/80] chore: stop tests on failure --- phpunit.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index 4c4e55ea4e..598b730908 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,7 +6,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false" + stopOnFailure="true" > From 20e3597a273635284a9c99d51297f2aff9e8014b Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Tue, 18 Mar 2025 13:04:44 +0530 Subject: [PATCH 03/80] WIP: Add test for empty source --- .../Services/Sites/SitesCustomServerTest.php | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index 6ecb35d5a9..e6321bad32 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -2243,4 +2243,40 @@ class SitesCustomServerTest extends Scope $this->cleanupSite($siteId); } + + public function testEmptySiteSource(): void + { + $siteId = $this->setupSite([ + 'siteId' => ID::unique(), + 'name' => 'Empty source site', + 'framework' => 'other', + 'buildRuntime' => 'node-22', + 'outputDirectory' => './', + ]); + $this->assertNotEmpty($siteId); + + $site = $this->getSite($siteId); + $this->assertEquals('200', $site['headers']['status-code']); + $this->assertArrayHasKey('adapter', $site['body']); + $this->assertEmpty($site['body']['adapter']); + + $domain = $this->setupSiteDomain($siteId); + $this->assertNotEmpty($domain); + + $deploymentId = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('empty'), + 'activate' => 'true' + ]); + $this->assertNotEmpty($deploymentId); + + $site = $this->getSite($siteId); + $this->assertEquals('200', $site['headers']['status-code']); + + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + $response = $proxyClient->call(Client::METHOD_GET, '/'); + $this->assertEquals(200, $response['headers']['status-code']); + + $this->cleanupSite($siteId); + } } From 795928a037644ca8aca6a3ad6fd07b2f6ffffef5 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 18 Mar 2025 16:59:05 +0200 Subject: [PATCH 04/80] Fix $id --- src/Appwrite/Platform/Workers/Deletes.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 3c8bcdd635..a14e54caab 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -1045,7 +1045,8 @@ class Deletes extends Action string $collection, array $queries, Database $database, - ?callable $callback = null + ?callable $callback = null, + bool $shortSelect = false ): void { $start = \microtime(true); @@ -1053,6 +1054,12 @@ class Deletes extends Action * deleteDocuments uses a cursor, we need to add a unique order by field or use default */ + if (!\is_callable($callback) && $shortSelect) { + $queries = array_merge($queries, [ + Query::select(['$internalId', '$id', '$permissions', '$updatedAt']) + ]); + } + try { $documents = $database->deleteDocuments($collection, $queries); } catch (Throwable $th) { From 18022d680ba32c348e1895e8ba469a4257cc2dad Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Wed, 19 Mar 2025 16:22:38 +0530 Subject: [PATCH 05/80] Assert exception --- .../Services/Sites/SitesCustomServerTest.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index 8c2a1c3970..ac6a74aeb5 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -2320,19 +2320,20 @@ class SitesCustomServerTest extends Scope $domain = $this->setupSiteDomain($siteId); $this->assertNotEmpty($domain); - $deploymentId = $this->setupDeployment($siteId, [ + $deployment = $this->createDeployment($siteId, [ 'code' => $this->packageSite('empty'), - 'activate' => 'true' + 'activate' => true ]); + $this->assertEquals(202, $deployment['headers']['status-code']); + + $deploymentId = $deployment['body']['$id']; $this->assertNotEmpty($deploymentId); - $site = $this->getSite($siteId); - $this->assertEquals('200', $site['headers']['status-code']); - - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEventually(function () use ($siteId, $deploymentId) { + $deployment = $this->getDeployment($siteId, $deploymentId); + $this->assertEquals('failed', $deployment['body']['status'], 'Deployment status is failed, deployment: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); + $this->assertStringContainsString("ERROR: No source code found. Please ensure your source directory exists and isn't empty.", $deployment['body']['buildLogs']); + }, 100000, 500); $this->cleanupSite($siteId); } From cc0a97b10e30290fce85fc1a84cf5f9948c8f391 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 19 Mar 2025 16:37:15 +0200 Subject: [PATCH 06/80] Fixes --- app/config/collections/common.php | 6 +++--- src/Appwrite/Platform/Workers/Deletes.php | 24 ++++++++++++----------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/app/config/collections/common.php b/app/config/collections/common.php index f68400e226..3f6245dfdd 100644 --- a/app/config/collections/common.php +++ b/app/config/collections/common.php @@ -1038,7 +1038,7 @@ return [ '$id' => ID::custom('providerUid'), 'type' => Database::VAR_STRING, 'format' => '', - 'size' => 2048, + 'size' => 2048, // Decrease to 128 as in index length? 'signed' => true, 'required' => false, 'default' => null, @@ -1107,14 +1107,14 @@ return [ '$id' => ID::custom('_key_userInternalId_provider_providerUid'), 'type' => Database::INDEX_UNIQUE, 'attributes' => ['userInternalId', 'provider', 'providerUid'], - 'lengths' => [11, 128, 128], + 'lengths' => [11, 128, 128], // providerUid is length 2000! 'orders' => [Database::ORDER_ASC, Database::ORDER_ASC], ], [ '$id' => ID::custom('_key_provider_providerUid'), 'type' => Database::INDEX_UNIQUE, 'attributes' => ['provider', 'providerUid'], - 'lengths' => [128, 128], + 'lengths' => [128, 128], // providerUid is length 2000! 'orders' => [Database::ORDER_ASC, Database::ORDER_ASC], ], [ diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index a14e54caab..27ff27fe31 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -357,21 +357,23 @@ class Deletes extends Action new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId) ); - $query[] = Query::equal('resource', [$resource]); + $queries = [ + Query::equal('resource', [$resource]) + ]; if (!empty($resourceType)) { - $query[] = Query::equal('resourceType', [$resourceType]); + $queries[] = Query::equal('resourceType', [$resourceType]); } - $query[] = Query::orderAsc(); + $queries[] = Query::orderAsc(); /** - * todo: missing index on `resource`, `resourceType` + * todo: No index on `resource`, `resourceType`, But it is fine since resource index is a strong index! */ $this->deleteByGroup( 'cache', - $query, + $queries, $dbForProject, function (Document $document) use ($cache, $projectId) { $path = APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId . DIRECTORY_SEPARATOR . $document->getId(); @@ -719,7 +721,7 @@ class Deletes extends Action // Delete identities /** - * todo: Remove Duplication index `_key_userInternalId` + * todo: Remove Duplication index `_key_userInternalId` , But lets leave because of index length issue in unique index */ $this->deleteByGroup('identities', [ Query::equal('userInternalId', [$userInternalId]), @@ -836,13 +838,13 @@ class Deletes extends Action /** * Delete rules - * todo: No index for this query + * todo: No index for this query, drop _key_projectInternalId and create _key_projectInternalId, resourceInternalId, resourceType */ Console::info("Deleting rules for function " . $functionId); $this->deleteByGroup('rules', [ - Query::equal('resourceType', ['function']), - Query::equal('resourceInternalId', [$functionInternalId]), Query::equal('projectInternalId', [$project->getInternalId()]), + Query::equal('resourceInternalId', [$functionInternalId]), + Query::equal('resourceType', ['function']), Query::orderAsc() ], $dbForPlatform, function (Document $document) use ($project, $dbForPlatform, $certificates) { $this->deleteRule($dbForPlatform, $document, $certificates); @@ -850,12 +852,12 @@ class Deletes extends Action /** * Delete Variables - * todo: No index for this query + * todo: No index for this query , drop _key_resourceInternalId and create new one with {resourceInternalId, resourceType} */ Console::info("Deleting variables for function " . $functionId); $this->deleteByGroup('variables', [ - Query::equal('resourceType', ['function']), Query::equal('resourceInternalId', [$functionInternalId]), + Query::equal('resourceType', ['function']), Query::orderAsc() ], $dbForProject); From 9c54cce5bd8c61d26a549273dfe30b6bcfd94751 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 20 Mar 2025 09:07:32 +0200 Subject: [PATCH 07/80] Queries fixes --- src/Appwrite/Platform/Workers/Deletes.php | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 27ff27fe31..b7fce0405d 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -365,6 +365,7 @@ class Deletes extends Action $queries[] = Query::equal('resourceType', [$resourceType]); } + $queries[] = Query::select(['$internalId', '$id', '$updatedAt']); $queries[] = Query::orderAsc(); /** @@ -404,7 +405,8 @@ class Deletes extends Action new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId) ); - $query = [ + $queries = [ + Query::select(['$internalId', '$id', '$updatedAt']), Query::lessThan('accessedAt', $datetime), Query::orderDesc('accessedAt'), Query::orderDesc(), @@ -412,7 +414,7 @@ class Deletes extends Action $this->deleteByGroup( 'cache', - $query, + $queries, $dbForProject, function (Document $document) use ($cache, $projectId) { $path = APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId . DIRECTORY_SEPARATOR . $document->getId(); @@ -440,6 +442,7 @@ class Deletes extends Action // Delete Usage stats from projectDB $this->deleteByGroup('stats', [ + Query::select(['$internalId', '$id', '$updatedAt']), Query::equal('period', ['1h']), Query::lessThan('time', $hourlyUsageRetentionDatetime), Query::orderDesc('time'), @@ -452,6 +455,7 @@ class Deletes extends Action // Delete Usage stats from logsDB $this->deleteByGroup('stats', [ + Query::select(['$internalId', '$id', '$updatedAt']), Query::equal('period', ['1h']), Query::lessThan('time', $hourlyUsageRetentionDatetime), Query::orderDesc('time'), @@ -755,6 +759,7 @@ class Deletes extends Action // Delete Executions $this->deleteByGroup('executions', [ + Query::select(['$internalId', '$id', '$updatedAt']), Query::lessThan('$createdAt', $datetime), Query::orderDesc('$createdAt'), Query::orderDesc(), @@ -775,6 +780,7 @@ class Deletes extends Action // Delete Sessions $this->deleteByGroup('sessions', [ + Query::select(['$internalId', '$id', '$updatedAt']), Query::lessThan('$createdAt', $expired), Query::orderDesc('$createdAt'), Query::orderDesc(), @@ -811,6 +817,7 @@ class Deletes extends Action try { $this->deleteByGroup(Audit::COLLECTION, [ + Query::select(['$internalId', '$id', '$updatedAt']), Query::lessThan('time', $auditRetention), Query::orderDesc('time'), Query::orderAsc(), // KEY "index-time" ("time" DESC) @@ -897,6 +904,7 @@ class Deletes extends Action */ Console::info("Deleting executions for function " . $functionId); $this->deleteByGroup('executions', [ + Query::select(['$internalId', '$id', '$updatedAt']), Query::equal('functionInternalId', [$functionInternalId]), Query::orderAsc() ], $dbForProject); @@ -1017,7 +1025,7 @@ class Deletes extends Action /** * Delete builds - * todo: no index for `deploymentInternalId` change to temporary to deploymentId? + * todo: no index for `deploymentInternalId` Same as above index , no need to handle again... */ Console::info("Deleting builds for deployment " . $deploymentId); @@ -1047,8 +1055,7 @@ class Deletes extends Action string $collection, array $queries, Database $database, - ?callable $callback = null, - bool $shortSelect = false + ?callable $callback = null ): void { $start = \microtime(true); @@ -1056,12 +1063,6 @@ class Deletes extends Action * deleteDocuments uses a cursor, we need to add a unique order by field or use default */ - if (!\is_callable($callback) && $shortSelect) { - $queries = array_merge($queries, [ - Query::select(['$internalId', '$id', '$permissions', '$updatedAt']) - ]); - } - try { $documents = $database->deleteDocuments($collection, $queries); } catch (Throwable $th) { From 5eb7c36f50e2713aee76beaff1bb207eca130116 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Thu, 20 Mar 2025 10:32:36 +0000 Subject: [PATCH 08/80] chore: add console audit retention --- .env | 2 +- app/worker.php | 2 +- docker-compose.yml | 1 + src/Appwrite/Platform/Workers/Deletes.php | 9 ++++++++- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.env b/.env index 1893e023ba..c68f0a0d52 100644 --- a/.env +++ b/.env @@ -85,7 +85,7 @@ _APP_MAINTENANCE_DELAY= _APP_MAINTENANCE_RETENTION_CACHE=2592000 _APP_MAINTENANCE_RETENTION_EXECUTION=1209600 _APP_MAINTENANCE_RETENTION_ABUSE=86400 -_APP_MAINTENANCE_RETENTION_AUDIT=1209600 +_APP_MAINTENANCE_RETENTION_AUDIT=project=1209600,console=15778800 _APP_USAGE_AGGREGATION_INTERVAL=30 _APP_STATS_RESOURCES_INTERVAL=3600 _APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000 diff --git a/app/worker.php b/app/worker.php index eeefe80000..5d7bd4de41 100644 --- a/app/worker.php +++ b/app/worker.php @@ -219,7 +219,7 @@ Server::setResource('abuseRetention', function () { }); Server::setResource('auditRetention', function () { - return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600)); + return System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 'project=1209600,console=15778800'); // project = 14 days, console = 6 months }); Server::setResource('executionRetention', function () { diff --git a/docker-compose.yml b/docker-compose.yml index b6dc80df6a..af6a323ec8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -389,6 +389,7 @@ services: - _APP_DATABASE_SHARED_TABLES - _APP_DATABASE_SHARED_TABLES_V1 - _APP_EMAIL_CERTIFICATES + - _APP_MAINTENANCE_RETENTION_AUDIT appwrite-worker-databases: entrypoint: worker-databases diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 9b0590181a..75380ed3fe 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -751,9 +751,16 @@ class Deletes extends Action $projectId = $project->getId(); $dbForProject = $getProjectDB($project); + [$projectAuditRetention, $consoleAuditRetention] = array_map( + function ($part) { + return DateTime::addSeconds(new \DateTime(), -1 * (int)explode('=', $part)[1]); + }, + explode(',', $auditRetention) + ); + try { $this->deleteByGroup(Audit::COLLECTION, [ - Query::lessThan('time', $auditRetention), + Query::lessThan('time', ($projectId === 'console' ? $consoleAuditRetention : $projectAuditRetention)), Query::orderDesc('time'), Query::orderDesc('$internalId'), ], $dbForProject); From 9d8289221925ddf7eeebbd42f56ba9be311dd2a0 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Thu, 20 Mar 2025 11:32:06 +0000 Subject: [PATCH 09/80] chore: update logic --- app/worker.php | 10 +++++++++- src/Appwrite/Platform/Workers/Deletes.php | 15 ++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/app/worker.php b/app/worker.php index 5d7bd4de41..18b51eda25 100644 --- a/app/worker.php +++ b/app/worker.php @@ -219,7 +219,15 @@ Server::setResource('abuseRetention', function () { }); Server::setResource('auditRetention', function () { - return System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 'project=1209600,console=15778800'); // project = 14 days, console = 6 months + return array_map( + function ($part) { + [$key, $value] = explode('=', $part); + return [ + $key => DateTime::addSeconds(new \DateTime(), -1 * (int)$value) + ]; + }, + explode(',', System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 'project=1209600,console=15778800')) + ); }); Server::setResource('executionRetention', function () { diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 75380ed3fe..93f8748e49 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -57,7 +57,7 @@ class Deletes extends Action ->inject('auditRetention') ->inject('log') ->callback( - fn ($message, Document $project, Database $dbForPlatform, callable $getProjectDB, callable $getLogsDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, string $executionRetention, string $auditRetention, Log $log) => + fn ($message, Document $project, Database $dbForPlatform, callable $getProjectDB, callable $getLogsDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, string $executionRetention, array $auditRetention, Log $log) => $this->action($message, $project, $dbForPlatform, $getProjectDB, $getLogsDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $executionRetention, $auditRetention, $log) ); } @@ -66,7 +66,7 @@ class Deletes extends Action * @throws Exception * @throws Throwable */ - public function action(Message $message, Document $project, Database $dbForPlatform, callable $getProjectDB, callable $getLogsDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, string $executionRetention, string $auditRetention, Log $log): void + public function action(Message $message, Document $project, Database $dbForPlatform, callable $getProjectDB, callable $getLogsDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, string $executionRetention, array $auditRetention, Log $log): void { $payload = $message->getPayload() ?? []; @@ -746,21 +746,14 @@ class Deletes extends Action * @return void * @throws Exception */ - private function deleteAuditLogs(Document $project, callable $getProjectDB, string $auditRetention): void + private function deleteAuditLogs(Document $project, callable $getProjectDB, array $auditRetention): void { $projectId = $project->getId(); $dbForProject = $getProjectDB($project); - [$projectAuditRetention, $consoleAuditRetention] = array_map( - function ($part) { - return DateTime::addSeconds(new \DateTime(), -1 * (int)explode('=', $part)[1]); - }, - explode(',', $auditRetention) - ); - try { $this->deleteByGroup(Audit::COLLECTION, [ - Query::lessThan('time', ($projectId === 'console' ? $consoleAuditRetention : $projectAuditRetention)), + Query::lessThan('time', ($projectId === 'console' ? $auditRetention['console'] : $auditRetention['project'])), Query::orderDesc('time'), Query::orderDesc('$internalId'), ], $dbForProject); From 7c3f8504bb65e598f50d07912b5d753c86f5e1f6 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Thu, 20 Mar 2025 13:15:14 +0000 Subject: [PATCH 10/80] chore: fix initialization --- app/worker.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/worker.php b/app/worker.php index 18b51eda25..e305ec42d6 100644 --- a/app/worker.php +++ b/app/worker.php @@ -219,14 +219,14 @@ Server::setResource('abuseRetention', function () { }); Server::setResource('auditRetention', function () { - return array_map( - function ($part) { + return array_reduce( + explode(',', System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 'project=1209600,console=15778800')), + function ($carry, $part) { [$key, $value] = explode('=', $part); - return [ - $key => DateTime::addSeconds(new \DateTime(), -1 * (int)$value) - ]; + $carry[$key] = DateTime::addSeconds(new \DateTime(), -1 * (int)$value); + return $carry; }, - explode(',', System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 'project=1209600,console=15778800')) + [] ); }); From b0b57935da0e54074766a3f9a144f41fe2081a55 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Thu, 20 Mar 2025 13:42:19 +0000 Subject: [PATCH 11/80] chore: add comments --- app/worker.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/worker.php b/app/worker.php index e305ec42d6..d0fa5cf4f8 100644 --- a/app/worker.php +++ b/app/worker.php @@ -215,12 +215,12 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache) { }, ['pools', 'cache']); Server::setResource('abuseRetention', function () { - return time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400); + return time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400); // 1 day }); Server::setResource('auditRetention', function () { return array_reduce( - explode(',', System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 'project=1209600,console=15778800')), + explode(',', System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 'project=1209600,console=15778800')), // project = 14 days, console = 6 months function ($carry, $part) { [$key, $value] = explode('=', $part); $carry[$key] = DateTime::addSeconds(new \DateTime(), -1 * (int)$value); @@ -231,7 +231,7 @@ Server::setResource('auditRetention', function () { }); Server::setResource('executionRetention', function () { - return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600)); + return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600)); // 14 days }); Server::setResource('cache', function (Registry $register) { From 21f12879ef65960c557742c6e93b4ab962c8b669 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Fri, 21 Mar 2025 12:11:29 +0530 Subject: [PATCH 12/80] Remove extra check --- tests/e2e/Services/Sites/SitesCustomServerTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index a6669d1e38..30bb73c40c 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -2363,7 +2363,6 @@ class SitesCustomServerTest extends Scope $this->assertEventually(function () use ($siteId, $deploymentId) { $deployment = $this->getDeployment($siteId, $deploymentId); $this->assertEquals('failed', $deployment['body']['status'], 'Deployment status is failed, deployment: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); - $this->assertStringContainsString("ERROR: No source code found. Please ensure your source directory exists and isn't empty.", $deployment['body']['buildLogs']); }, 100000, 500); $this->cleanupSite($siteId); From 13750f7dbd5b5850075eefab4bacf3e782752004 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 21 Mar 2025 07:42:10 +0000 Subject: [PATCH 13/80] feat: introduce new env --- .env | 3 ++- app/config/variables.php | 11 ++++++++++- app/views/install/compose.phtml | 3 +++ app/worker.php | 13 ++++--------- docker-compose.yml | 3 +++ 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/.env b/.env index c68f0a0d52..c10c12613b 100644 --- a/.env +++ b/.env @@ -85,7 +85,8 @@ _APP_MAINTENANCE_DELAY= _APP_MAINTENANCE_RETENTION_CACHE=2592000 _APP_MAINTENANCE_RETENTION_EXECUTION=1209600 _APP_MAINTENANCE_RETENTION_ABUSE=86400 -_APP_MAINTENANCE_RETENTION_AUDIT=project=1209600,console=15778800 +_APP_MAINTENANCE_RETENTION_AUDIT=1209600 +_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE=15778800 _APP_USAGE_AGGREGATION_INTERVAL=30 _APP_STATS_RESOURCES_INTERVAL=3600 _APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000 diff --git a/app/config/variables.php b/app/config/variables.php index 98dd9ffec1..27463d2fee 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -1048,13 +1048,22 @@ return [ ], [ 'name' => '_APP_MAINTENANCE_RETENTION_AUDIT', - 'description' => 'IThe maximum duration (in seconds) upto which to retain audit logs. The default value is 1209600 seconds (14 days).', + 'description' => 'The maximum duration (in seconds) upto which to retain audit logs. The default value is 1209600 seconds (14 days).', 'introduction' => '0.7.0', 'default' => '1209600', 'required' => false, 'question' => '', 'filter' => '' ], + [ + 'name' => '_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE', + 'description' => 'The maximum duration (in seconds) upto which to retain console audit logs. The default value is 15778800 seconds (6 months).', + 'introduction' => '1.6.2', + 'default' => '15778800', + 'required' => false, + 'question' => '', + 'filter' => '' + ], [ 'name' => '_APP_MAINTENANCE_RETENTION_ABUSE', 'description' => 'The maximum duration (in seconds) upto which to retain abuse logs. The default value is 86400 seconds (1 day).', diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml index fd05d2a0b6..7dfe14fcef 100644 --- a/app/views/install/compose.phtml +++ b/app/views/install/compose.phtml @@ -148,6 +148,7 @@ $image = $this->getParam('image', ''); - _APP_MAINTENANCE_RETENTION_CACHE - _APP_MAINTENANCE_RETENTION_ABUSE - _APP_MAINTENANCE_RETENTION_AUDIT + - _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE - _APP_MAINTENANCE_RETENTION_USAGE_HOURLY - _APP_MAINTENANCE_RETENTION_SCHEDULES - _APP_SMS_PROVIDER @@ -340,6 +341,7 @@ $image = $this->getParam('image', ''); - _APP_EXECUTOR_HOST - _APP_MAINTENANCE_RETENTION_ABUSE - _APP_MAINTENANCE_RETENTION_AUDIT + - _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE - _APP_MAINTENANCE_RETENTION_EXECUTION - _APP_SYSTEM_SECURITY_EMAIL_ADDRESS - _APP_EMAIL_CERTIFICATES @@ -651,6 +653,7 @@ $image = $this->getParam('image', ''); - _APP_MAINTENANCE_RETENTION_CACHE - _APP_MAINTENANCE_RETENTION_ABUSE - _APP_MAINTENANCE_RETENTION_AUDIT + - _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE - _APP_MAINTENANCE_RETENTION_USAGE_HOURLY - _APP_MAINTENANCE_RETENTION_SCHEDULES diff --git a/app/worker.php b/app/worker.php index d0fa5cf4f8..3f99089eef 100644 --- a/app/worker.php +++ b/app/worker.php @@ -219,15 +219,10 @@ Server::setResource('abuseRetention', function () { }); Server::setResource('auditRetention', function () { - return array_reduce( - explode(',', System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 'project=1209600,console=15778800')), // project = 14 days, console = 6 months - function ($carry, $part) { - [$key, $value] = explode('=', $part); - $carry[$key] = DateTime::addSeconds(new \DateTime(), -1 * (int)$value); - return $carry; - }, - [] - ); + return [ + 'project' => DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('APP_MAINTENANCE_RETENTION_AUDIT', '1209600')), + 'console' => DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE', '15778800')) + ]; }); Server::setResource('executionRetention', function () { diff --git a/docker-compose.yml b/docker-compose.yml index af6a323ec8..05ddba967a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -171,6 +171,7 @@ services: - _APP_MAINTENANCE_RETENTION_CACHE - _APP_MAINTENANCE_RETENTION_ABUSE - _APP_MAINTENANCE_RETENTION_AUDIT + - _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE - _APP_MAINTENANCE_RETENTION_USAGE_HOURLY - _APP_MAINTENANCE_RETENTION_SCHEDULES - _APP_SMS_PROVIDER @@ -390,6 +391,7 @@ services: - _APP_DATABASE_SHARED_TABLES_V1 - _APP_EMAIL_CERTIFICATES - _APP_MAINTENANCE_RETENTION_AUDIT + - _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE appwrite-worker-databases: entrypoint: worker-databases @@ -722,6 +724,7 @@ services: - _APP_MAINTENANCE_RETENTION_CACHE - _APP_MAINTENANCE_RETENTION_ABUSE - _APP_MAINTENANCE_RETENTION_AUDIT + - _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE - _APP_MAINTENANCE_RETENTION_USAGE_HOURLY - _APP_MAINTENANCE_RETENTION_SCHEDULES - _APP_MAINTENANCE_DELAY From 7c795da72fd7aa05b280f27012f87357a08d2814 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 21 Mar 2025 07:48:50 +0000 Subject: [PATCH 14/80] chore: fix type --- app/worker.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/worker.php b/app/worker.php index 3f99089eef..744c758c69 100644 --- a/app/worker.php +++ b/app/worker.php @@ -220,8 +220,8 @@ Server::setResource('abuseRetention', function () { Server::setResource('auditRetention', function () { return [ - 'project' => DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('APP_MAINTENANCE_RETENTION_AUDIT', '1209600')), - 'console' => DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE', '15778800')) + 'project' => DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('APP_MAINTENANCE_RETENTION_AUDIT', 1209600)), + 'console' => DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE', 15778800)) ]; }); From c0c0c921dc249f2f235a9bd7e8efdce66ae3c137 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 21 Mar 2025 08:08:30 +0000 Subject: [PATCH 15/80] chore: fix naming --- app/worker.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/worker.php b/app/worker.php index 744c758c69..491f266a18 100644 --- a/app/worker.php +++ b/app/worker.php @@ -220,8 +220,8 @@ Server::setResource('abuseRetention', function () { Server::setResource('auditRetention', function () { return [ - 'project' => DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('APP_MAINTENANCE_RETENTION_AUDIT', 1209600)), - 'console' => DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE', 15778800)) + 'project' => DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600)), // 14 days + 'console' => DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE', 15778800)) // 6 months ]; }); From d661195a5f4bf375f7b72e5629e1a1dfd3fe8d3d Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 21 Mar 2025 15:59:30 +0000 Subject: [PATCH 16/80] chore: only update total count for prviledged users --- app/controllers/api/teams.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index b45c9fd3b9..dfdccf5e99 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -615,7 +615,10 @@ App::post('/v1/teams/:teamId/memberships') $membership = ($isPrivilegedUser || $isAppUser) ? Authorization::skip(fn () => $dbForProject->createDocument('memberships', $membership)) : $dbForProject->createDocument('memberships', $membership); - Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1)); + + if ($isPrivilegedUser || $isAppUser) { + Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1)); + } } elseif ($membership->getAttribute('confirm') === false) { $membership->setAttribute('secret', Auth::hash($secret)); From 7c2ab4fcf652b2823a42c04daf8ce4c0233eb609 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sat, 22 Mar 2025 13:47:09 +0530 Subject: [PATCH 17/80] update: change sample rate for 4xx via env. --- app/controllers/general.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index a374aa3c39..1423763cec 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -871,17 +871,18 @@ App::error() if (!empty($providerConfig) && $error->getCode() >= 400 && $error->getCode() < 500) { // Register error logger try { - $loggingProvider = new DSN($providerConfig ?? ''); + $loggingProvider = new DSN($providerConfig); $providerName = $loggingProvider->getScheme(); if (!empty($providerName) && $providerName === 'sentry') { $key = $loggingProvider->getPassword(); $projectId = $loggingProvider->getUser() ?? ''; $host = 'https://' . $loggingProvider->getHost(); + $sampleRate = $loggingProvider->getParam('sample', 0.01); $adapter = new Sentry($projectId, $key, $host); $logger = new Logger($adapter); - $logger->setSample(0.01); + $logger->setSample($sampleRate); $publish = true; } else { throw new \Exception('Invalid experimental logging provider'); From 86856bf87d1afa58543411eda105fb975009e5db Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Sat, 22 Mar 2025 13:55:23 +0000 Subject: [PATCH 18/80] chore: update gitpod.yml config --- .gitpod.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.gitpod.yml b/.gitpod.yml index 478b62fc8d..e9c0928229 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,6 +1,7 @@ tasks: - name: Run Appwrite Docker Stack init: | + git config --global --add safe.directory /app git submodule update --init docker compose build docker compose pull @@ -23,11 +24,3 @@ vscode: extensions: - ms-azuretools.vscode-docker - zobo.php-intellisense - -github: - # https://www.gitpod.io/docs/prebuilds#github-specific-configuration - prebuilds: - # enable for pull requests coming from forks (defaults to false) - pullRequestsFromForks: true - # add a check to pull requests (defaults to true) - addCheck: false From 37dfe0d719b49f32b2540b3d6b3310fcd110e976 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Sat, 22 Mar 2025 14:05:39 +0000 Subject: [PATCH 19/80] refactor: initialization of audit retention --- app/worker.php | 14 ++++++-------- src/Appwrite/Platform/Workers/Deletes.php | 10 +++++----- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/app/worker.php b/app/worker.php index 491f266a18..90496c0430 100644 --- a/app/worker.php +++ b/app/worker.php @@ -16,8 +16,6 @@ use Appwrite\Event\Migration; use Appwrite\Event\Realtime; use Appwrite\Event\StatsUsage; use Appwrite\Event\StatsUsageDump; -/** remove */ -/** /remove */ use Appwrite\Event\Webhook; use Appwrite\Platform\Appwrite; use Swoole\Runtime; @@ -218,12 +216,12 @@ Server::setResource('abuseRetention', function () { return time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400); // 1 day }); -Server::setResource('auditRetention', function () { - return [ - 'project' => DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600)), // 14 days - 'console' => DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE', 15778800)) // 6 months - ]; -}); +Server::setResource('auditRetention', function (Document $project) { + if ($project->getId() === 'console') { + return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE', 15778800)); // 6 months + } + return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600)); // 14 days +}, ['project']); Server::setResource('executionRetention', function () { return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600)); // 14 days diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 93f8748e49..bb5cc81fcc 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -57,7 +57,7 @@ class Deletes extends Action ->inject('auditRetention') ->inject('log') ->callback( - fn ($message, Document $project, Database $dbForPlatform, callable $getProjectDB, callable $getLogsDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, string $executionRetention, array $auditRetention, Log $log) => + fn ($message, Document $project, Database $dbForPlatform, callable $getProjectDB, callable $getLogsDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, string $executionRetention, string $auditRetention, Log $log) => $this->action($message, $project, $dbForPlatform, $getProjectDB, $getLogsDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $executionRetention, $auditRetention, $log) ); } @@ -66,7 +66,7 @@ class Deletes extends Action * @throws Exception * @throws Throwable */ - public function action(Message $message, Document $project, Database $dbForPlatform, callable $getProjectDB, callable $getLogsDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, string $executionRetention, array $auditRetention, Log $log): void + public function action(Message $message, Document $project, Database $dbForPlatform, callable $getProjectDB, callable $getLogsDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, string $executionRetention, string $auditRetention, Log $log): void { $payload = $message->getPayload() ?? []; @@ -742,18 +742,18 @@ class Deletes extends Action /** * @param Database $dbForPlatform * @param callable $getProjectDB - * @param string $datetime + * @param string $auditRetention * @return void * @throws Exception */ - private function deleteAuditLogs(Document $project, callable $getProjectDB, array $auditRetention): void + private function deleteAuditLogs(Document $project, callable $getProjectDB, string $auditRetention): void { $projectId = $project->getId(); $dbForProject = $getProjectDB($project); try { $this->deleteByGroup(Audit::COLLECTION, [ - Query::lessThan('time', ($projectId === 'console' ? $auditRetention['console'] : $auditRetention['project'])), + Query::lessThan('time', $auditRetention), Query::orderDesc('time'), Query::orderDesc('$internalId'), ], $dbForProject); From 1580bac1fc68157eefa9c66371bc39dcd95a141e Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Mon, 24 Mar 2025 13:30:21 +0530 Subject: [PATCH 20/80] Add tests for empty output directory --- .../Services/Sites/SitesCustomServerTest.php | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index a95568c7ce..861eeb7dc9 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -2331,4 +2331,82 @@ class SitesCustomServerTest extends Scope $this->cleanupSite($siteId); } + + public function testEmptyOutputDirectoryExists(): void + { + $siteId = $this->setupSite([ + 'siteId' => ID::unique(), + 'name' => 'Astro empty output directory', + 'framework' => 'astro', + 'buildRuntime' => 'node-22', + 'outputDirectory' => './random', + 'buildCommand' => 'mkdir random && npm run build', + 'installCommand' => 'npm install', + ]); + $this->assertNotEmpty($siteId); + + $site = $this->getSite($siteId); + $this->assertEquals('200', $site['headers']['status-code']); + $this->assertArrayHasKey('adapter', $site['body']); + $this->assertEmpty($site['body']['adapter']); + + $domain = $this->setupSiteDomain($siteId); + $this->assertNotEmpty($domain); + + $deployment = $this->createDeployment($siteId, [ + 'code' => $this->packageSite('astro'), + 'activate' => true + ]); + $this->assertEquals(202, $deployment['headers']['status-code']); + + $deploymentId = $deployment['body']['$id']; + $this->assertNotEmpty($deploymentId); + + $this->assertEventually(function () use ($siteId, $deploymentId) { + $deployment = $this->getDeployment($siteId, $deploymentId); + $this->assertEquals('failed', $deployment['body']['status'], 'Deployment status is failed, deployment: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); + $this->assertStringContainsString('Error: Build output directory is empty.', $deployment['body']['buildLogs']); + }, 100000, 500); + + $this->cleanupSite($siteId); + } + + public function testEmptyOutputDirectoryDoesNotExist(): void + { + $siteId = $this->setupSite([ + 'siteId' => ID::unique(), + 'name' => 'Astro empty output directory', + 'framework' => 'astro', + 'buildRuntime' => 'node-22', + 'outputDirectory' => './random', + 'buildCommand' => 'npm run build', + 'installCommand' => 'npm install', + ]); + $this->assertNotEmpty($siteId); + + $site = $this->getSite($siteId); + $this->assertEquals('200', $site['headers']['status-code']); + $this->assertArrayHasKey('adapter', $site['body']); + $this->assertEmpty($site['body']['adapter']); + + $domain = $this->setupSiteDomain($siteId); + $this->assertNotEmpty($domain); + + $deployment = $this->createDeployment($siteId, [ + 'code' => $this->packageSite('astro'), + 'activate' => true + ]); + $this->assertEquals(202, $deployment['headers']['status-code']); + + $deploymentId = $deployment['body']['$id']; + $this->assertNotEmpty($deploymentId); + + $this->assertEventually(function () use ($siteId, $deploymentId) { + $deployment = $this->getDeployment($siteId, $deploymentId); + $this->assertEquals('failed', $deployment['body']['status'], 'Deployment status is failed, deployment: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); + $this->assertStringContainsString('Error: Output directory is either not set or does not exist.', $deployment['body']['buildLogs']); + }, 100000, 500); + + $this->cleanupSite($siteId); + } } From 26821c83726db7506fbf2d0828f34a908cb5d1d3 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 24 Mar 2025 11:08:39 +0200 Subject: [PATCH 21/80] Bump database 0.62 --- composer.json | 2 +- composer.lock | 311 ++++---------------------------------------------- 2 files changed, 22 insertions(+), 291 deletions(-) diff --git a/composer.json b/composer.json index d7b8505b5c..b1b6aed539 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "utopia-php/cache": "0.12.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.61.*", + "utopia-php/database": "0.62.*", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", diff --git a/composer.lock b/composer.lock index cdba756c72..1ea887fdbc 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e0d7f21b681e4591144fec16c4f0d6aa", + "content-hash": "d2bec8137dcd84994121f89a29932d31", "packages": [ { "name": "adhocore/jwt", @@ -751,65 +751,6 @@ }, "time": "2025-03-13T21:08:17+00:00" }, - { - "name": "jean85/pretty-package-versions", - "version": "2.1.0", - "source": { - "type": "git", - "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", - "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2.1.0", - "php": "^7.4|^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.2", - "jean85/composer-provided-replaced-stub-package": "^1.0", - "phpstan/phpstan": "^1.4", - "phpunit/phpunit": "^7.5|^8.5|^9.6", - "vimeo/psalm": "^4.3 || ^5.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Jean85\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Alessandro Lai", - "email": "alessandro.lai85@gmail.com" - } - ], - "description": "A library to get pretty versions strings of installed dependencies", - "keywords": [ - "composer", - "package", - "release", - "versions" - ], - "support": { - "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.0" - }, - "time": "2024-11-18T16:19:46+00:00" - }, { "name": "league/csv", "version": "9.14.0", @@ -968,75 +909,6 @@ }, "time": "2023-10-02T10:01:54+00:00" }, - { - "name": "mongodb/mongodb", - "version": "1.10.0", - "source": { - "type": "git", - "url": "https://github.com/mongodb/mongo-php-library.git", - "reference": "b0bbd657f84219212487d01a8ffe93a789e1e488" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/b0bbd657f84219212487d01a8ffe93a789e1e488", - "reference": "b0bbd657f84219212487d01a8ffe93a789e1e488", - "shasum": "" - }, - "require": { - "ext-hash": "*", - "ext-json": "*", - "ext-mongodb": "^1.11.0", - "jean85/pretty-package-versions": "^1.2 || ^2.0.1", - "php": "^7.1 || ^8.0", - "symfony/polyfill-php80": "^1.19" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0", - "squizlabs/php_codesniffer": "^3.6", - "symfony/phpunit-bridge": "^5.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10.x-dev" - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "MongoDB\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Andreas Braun", - "email": "andreas.braun@mongodb.com" - }, - { - "name": "Jeremy Mikola", - "email": "jmikola@gmail.com" - } - ], - "description": "MongoDB driver library", - "homepage": "https://jira.mongodb.org/browse/PHPLIB", - "keywords": [ - "database", - "driver", - "mongodb", - "persistence" - ], - "support": { - "issues": "https://github.com/mongodb/mongo-php-library/issues", - "source": "https://github.com/mongodb/mongo-php-library/tree/1.10.0" - }, - "time": "2021-10-20T22:22:37+00:00" - }, { "name": "mustangostang/spyc", "version": "0.6.3", @@ -2371,16 +2243,16 @@ }, { "name": "ramsey/collection", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109" + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", - "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", "shasum": "" }, "require": { @@ -2441,9 +2313,9 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.1.0" + "source": "https://github.com/ramsey/collection/tree/2.1.1" }, - "time": "2025-03-02T04:48:29+00:00" + "time": "2025-03-22T05:38:12+00:00" }, { "name": "ramsey/uuid", @@ -2932,86 +2804,6 @@ ], "time": "2024-09-09T11:45:10+00:00" }, - { - "name": "symfony/polyfill-php80", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, { "name": "symfony/polyfill-php82", "version": "v1.31.0", @@ -3705,16 +3497,16 @@ }, { "name": "utopia-php/database", - "version": "0.61.2", + "version": "0.62.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "349fbdf4bc088f7775c7dfb8b80239a617a88436" + "reference": "65dc51466c12552add10395900cdbb4728da4068" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/349fbdf4bc088f7775c7dfb8b80239a617a88436", - "reference": "349fbdf4bc088f7775c7dfb8b80239a617a88436", + "url": "https://api.github.com/repos/utopia-php/database/zipball/65dc51466c12552add10395900cdbb4728da4068", + "reference": "65dc51466c12552add10395900cdbb4728da4068", "shasum": "" }, "require": { @@ -3722,8 +3514,7 @@ "ext-pdo": "*", "php": ">=8.1", "utopia-php/cache": "0.12.*", - "utopia-php/framework": "0.33.*", - "utopia-php/mongo": "0.3.*" + "utopia-php/framework": "0.33.*" }, "require-dev": { "fakerphp/faker": "1.23.*", @@ -3755,9 +3546,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.61.2" + "source": "https://github.com/utopia-php/database/tree/0.62.1" }, - "time": "2025-03-15T11:47:42+00:00" + "time": "2025-03-24T08:27:18+00:00" }, { "name": "utopia-php/domains", @@ -4159,16 +3950,16 @@ }, { "name": "utopia-php/migration", - "version": "0.8.1", + "version": "0.8.2", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "36ec7af2e6bf78de5d86e1b0a953fd7dcdf69dab" + "reference": "aa3b7a508feb7090f487e7bf9cd71f5c92fbc7c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/36ec7af2e6bf78de5d86e1b0a953fd7dcdf69dab", - "reference": "36ec7af2e6bf78de5d86e1b0a953fd7dcdf69dab", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/aa3b7a508feb7090f487e7bf9cd71f5c92fbc7c1", + "reference": "aa3b7a508feb7090f487e7bf9cd71f5c92fbc7c1", "shasum": "" }, "require": { @@ -4176,7 +3967,7 @@ "ext-curl": "*", "ext-openssl": "*", "php": ">=8.1", - "utopia-php/database": "0.61.*", + "utopia-php/database": "0.62.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*", "utopia-php/storage": "0.18.*" @@ -4209,69 +4000,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/0.8.1" + "source": "https://github.com/utopia-php/migration/tree/0.8.2" }, - "time": "2025-03-18T07:48:08+00:00" - }, - { - "name": "utopia-php/mongo", - "version": "0.3.1", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/mongo.git", - "reference": "52326a9a43e2d27ff0c15c48ba746dacbe9a7aee" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/mongo/zipball/52326a9a43e2d27ff0c15c48ba746dacbe9a7aee", - "reference": "52326a9a43e2d27ff0c15c48ba746dacbe9a7aee", - "shasum": "" - }, - "require": { - "ext-mongodb": "*", - "mongodb/mongodb": "1.10.0", - "php": ">=8.0" - }, - "require-dev": { - "fakerphp/faker": "^1.14", - "laravel/pint": "1.2.*", - "phpstan/phpstan": "1.8.*", - "phpunit/phpunit": "^9.4", - "swoole/ide-helper": "4.8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\Mongo\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Eldad Fux", - "email": "eldad@appwrite.io" - }, - { - "name": "Wess", - "email": "wess@appwrite.io" - } - ], - "description": "A simple library to manage Mongo database", - "keywords": [ - "database", - "mongo", - "php", - "upf", - "utopia" - ], - "support": { - "issues": "https://github.com/utopia-php/mongo/issues", - "source": "https://github.com/utopia-php/mongo/tree/0.3.1" - }, - "time": "2023-09-01T17:25:28+00:00" + "time": "2025-03-24T09:05:31+00:00" }, { "name": "utopia-php/orchestration", From 5479e6f8f1f51691e168564b2592d15df917a421 Mon Sep 17 00:00:00 2001 From: Fabian Gruber Date: Tue, 18 Mar 2025 12:29:32 +0100 Subject: [PATCH 22/80] chore(audits): return queue pre-fetch results --- src/Appwrite/Platform/Workers/Audits.php | 49 +++++++++++++----------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Audits.php b/src/Appwrite/Platform/Workers/Audits.php index ed5ff8010a..962bc622d1 100644 --- a/src/Appwrite/Platform/Workers/Audits.php +++ b/src/Appwrite/Platform/Workers/Audits.php @@ -12,12 +12,12 @@ use Utopia\Database\Exception\Authorization; use Utopia\Database\Exception\Structure; use Utopia\Platform\Action; use Utopia\Queue\Message; +use Utopia\Queue\Result\Commit; +use Utopia\Queue\Result\NoCommit; use Utopia\System\System; class Audits extends Action { - protected const BATCH_SIZE_DEVELOPMENT = 1; // smaller batch size for development - protected const BATCH_SIZE_PRODUCTION = 5_000; protected const BATCH_AGGREGATION_INTERVAL = 60; // in seconds private int $lastTriggeredTime = 0; @@ -27,9 +27,7 @@ class Audits extends Action protected function getBatchSize(): int { - return System::getEnv('_APP_ENV', 'development') === 'development' - ? self::BATCH_SIZE_DEVELOPMENT - : self::BATCH_SIZE_PRODUCTION; + return intval(System::getEnv('_APP_QUEUE_PREFETCH_COUNT', 1)); } public static function getName(): string @@ -57,13 +55,13 @@ class Audits extends Action * @param Message $message * @param callable $getProjectDB * @param Document $project - * @return void + * @return Commit|NoCommit * @throws Throwable * @throws \Utopia\Database\Exception * @throws Authorization * @throws Structure */ - public function action(Message $message, callable $getProjectDB, Document $project): void + public function action(Message $message, callable $getProjectDB, Document $project): Commit|NoCommit { $payload = $message->getPayload() ?? []; @@ -119,29 +117,34 @@ class Audits extends Action // Check if we should process the batch by checking both for the batch size and the elapsed time $batchSize = $this->getBatchSize(); - $shouldProcessBatch = \count($this->logs) >= $batchSize; - if (!$shouldProcessBatch && \count($this->logs) > 0) { + $logCount = array_reduce($this->logs, fn (int $current, $logs) => $current + count($logs), 0); + $shouldProcessBatch = $logCount >= $batchSize; + if (!$shouldProcessBatch && $logCount > 0) { $shouldProcessBatch = (\time() - $this->lastTriggeredTime) >= self::BATCH_AGGREGATION_INTERVAL; } - if ($shouldProcessBatch) { - try { - foreach ($this->logs as $internalId => $projectLogs) { - $dbForProject = $getProjectDB($projectLogs['project']); + if (!$shouldProcessBatch) { + return new NoCommit(); + } - Console::log('Processing batch with ' . count($projectLogs['logs']) . ' events'); - $audit = new Audit($dbForProject); + try { + foreach ($this->logs as $internalId => $projectLogs) { + $dbForProject = $getProjectDB($projectLogs['project']); - $audit->logBatch($projectLogs['logs']); - Console::success('Audit logs processed successfully'); + Console::log('Processing batch with ' . count($projectLogs['logs']) . ' events'); + $audit = new Audit($dbForProject); - unset($this->logs[$internalId]); - } - } catch (Throwable $e) { - Console::error('Error processing audit logs: ' . $e->getMessage()); - } finally { - $this->lastTriggeredTime = time(); + $audit->logBatch($projectLogs['logs']); + Console::success('Audit logs processed successfully'); + + unset($this->logs[$internalId]); } + return new Commit(); + } catch (Throwable $e) { + Console::error('Error processing audit logs: ' . $e->getMessage()); + return new NoCommit(); + } finally { + $this->lastTriggeredTime = time(); } } } From 25b6d59e3071b6d223cb53c8e9453631d5f3ec43 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 24 Mar 2025 12:54:19 +0200 Subject: [PATCH 23/80] Add internalId --- src/Appwrite/Utopia/Database/Validator/Queries/Base.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php index b8cff64214..d1b143ec9b 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php @@ -60,11 +60,19 @@ class Base extends Queries 'type' => Database::VAR_STRING, 'array' => false, ]); + + $attributes[] = new Document([ + 'key' => '$internalId', + 'type' => Database::VAR_STRING, + 'array' => false, + ]); + $attributes[] = new Document([ 'key' => '$createdAt', 'type' => Database::VAR_DATETIME, 'array' => false, ]); + $attributes[] = new Document([ 'key' => '$updatedAt', 'type' => Database::VAR_DATETIME, From 42f72b4477ef951aa131429f4f513579b2a4cf46 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 24 Mar 2025 13:33:36 +0200 Subject: [PATCH 24/80] Add internalId to orders --- .../Utopia/Database/Validator/Queries/Base.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php index d1b143ec9b..85fbf43e66 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php @@ -61,12 +61,6 @@ class Base extends Queries 'array' => false, ]); - $attributes[] = new Document([ - 'key' => '$internalId', - 'type' => Database::VAR_STRING, - 'array' => false, - ]); - $attributes[] = new Document([ 'key' => '$createdAt', 'type' => Database::VAR_DATETIME, @@ -84,8 +78,15 @@ class Base extends Queries new Offset(), new Cursor(), new Filter($attributes, APP_DATABASE_QUERY_MAX_VALUES), - new Order($attributes), - ]; + new Order( + array_merge($attributes, [ + new Document([ + 'key' => '$internalId', + 'type' => Database::VAR_STRING, + 'array' => false, + ]) + ]) + )]; parent::__construct($validators); } From 0648de2046c3b7561d37877b3b06c8c811ddc06d Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 24 Mar 2025 14:32:57 +0200 Subject: [PATCH 25/80] Revert Base --- .../Utopia/Database/Validator/Queries/Base.php | 13 ++----------- .../Databases/DatabasesCustomServerTest.php | 4 ++-- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php index 85fbf43e66..b8cff64214 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php @@ -60,13 +60,11 @@ class Base extends Queries 'type' => Database::VAR_STRING, 'array' => false, ]); - $attributes[] = new Document([ 'key' => '$createdAt', 'type' => Database::VAR_DATETIME, 'array' => false, ]); - $attributes[] = new Document([ 'key' => '$updatedAt', 'type' => Database::VAR_DATETIME, @@ -78,15 +76,8 @@ class Base extends Queries new Offset(), new Cursor(), new Filter($attributes, APP_DATABASE_QUERY_MAX_VALUES), - new Order( - array_merge($attributes, [ - new Document([ - 'key' => '$internalId', - 'type' => Database::VAR_STRING, - 'array' => false, - ]) - ]) - )]; + new Order($attributes), + ]; parent::__construct($validators); } diff --git a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php index 57e0b93634..70f8bea4f2 100644 --- a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php +++ b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php @@ -120,7 +120,7 @@ class DatabasesCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'queries' => [ - Query::orderDesc()->toString(), + Query::orderDesc()->setAttribute('')->toString(), ], ]); @@ -453,7 +453,7 @@ class DatabasesCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'queries' => [ - Query::orderDesc()->toString(), + Query::orderDesc()->setAttribute('')->toString(), ], ]); From e11e7e6780cbf8a3dc2ac10c4302360b7d4963c5 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 24 Mar 2025 17:33:02 +0200 Subject: [PATCH 26/80] Add internalId --- src/Appwrite/Utopia/Database/Validator/Queries/Base.php | 8 +++++++- .../e2e/Services/Databases/DatabasesCustomServerTest.php | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php index b8cff64214..a13314ab2f 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php @@ -71,12 +71,18 @@ class Base extends Queries 'array' => false, ]); + $internalId = new Document([ + 'key' => '$internalId', + 'type' => Database::VAR_STRING, + 'array' => false, + ]); + $validators = [ new Limit(), new Offset(), new Cursor(), new Filter($attributes, APP_DATABASE_QUERY_MAX_VALUES), - new Order($attributes), + new Order(array_merge($attributes, [$internalId])), ]; parent::__construct($validators); diff --git a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php index 70f8bea4f2..55cb17b9f8 100644 --- a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php +++ b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php @@ -123,7 +123,7 @@ class DatabasesCustomServerTest extends Scope Query::orderDesc()->setAttribute('')->toString(), ], ]); - + var_dump($databases); $this->assertEquals(2, $databases['body']['total']); $this->assertEquals($base[0]['$id'], $databases['body']['databases'][0]['$id']); $this->assertEquals($base[1]['$id'], $databases['body']['databases'][1]['$id']); From 5799ca1041b8f945c45285f557a35ef6b8633381 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 24 Mar 2025 17:37:34 +0200 Subject: [PATCH 27/80] remove var_dump --- tests/e2e/Services/Databases/DatabasesCustomServerTest.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php index 55cb17b9f8..206567e789 100644 --- a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php +++ b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php @@ -120,10 +120,9 @@ class DatabasesCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'queries' => [ - Query::orderDesc()->setAttribute('')->toString(), + Query::orderDesc()->toString(), ], ]); - var_dump($databases); $this->assertEquals(2, $databases['body']['total']); $this->assertEquals($base[0]['$id'], $databases['body']['databases'][0]['$id']); $this->assertEquals($base[1]['$id'], $databases['body']['databases'][1]['$id']); @@ -453,7 +452,7 @@ class DatabasesCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'queries' => [ - Query::orderDesc()->setAttribute('')->toString(), + Query::orderDesc()->toString(), ], ]); From 2795505e8288074f8981edb90e5f3279bfaf9938 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 24 Mar 2025 17:38:01 +0200 Subject: [PATCH 28/80] line --- tests/e2e/Services/Databases/DatabasesCustomServerTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php index 206567e789..57e0b93634 100644 --- a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php +++ b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php @@ -123,6 +123,7 @@ class DatabasesCustomServerTest extends Scope Query::orderDesc()->toString(), ], ]); + $this->assertEquals(2, $databases['body']['total']); $this->assertEquals($base[0]['$id'], $databases['body']['databases'][0]['$id']); $this->assertEquals($base[1]['$id'], $databases['body']['databases'][1]['$id']); From c0cb4b26a3ebf73822f0abea8c6aeae9dd4d8875 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 25 Mar 2025 15:51:47 +1300 Subject: [PATCH 29/80] Apply suggestions from code review --- src/Appwrite/Platform/Workers/Deletes.php | 35 ++++------------------- 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 8deb227c78..0de79cd67e 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -305,9 +305,6 @@ class Deletes extends Action */ private function deleteExpiredTargets(Document $project, callable $getProjectDB): void { - /** - * todo: No index found for `expired` attribute - */ $this->deleteByGroup( 'targets', [ @@ -323,9 +320,6 @@ class Deletes extends Action private function deleteSessionTargets(Document $project, callable $getProjectDB, Document $session): void { - /** - * todo: No index found for `sessionInternalId` attribute - */ $this->deleteByGroup( 'targets', [ @@ -368,10 +362,6 @@ class Deletes extends Action $queries[] = Query::select(['$internalId', '$id', '$updatedAt']); $queries[] = Query::orderAsc(); - /** - * todo: No index on `resource`, `resourceType`, But it is fine since resource index is a strong index! - */ - $this->deleteByGroup( 'cache', $queries, @@ -446,7 +436,7 @@ class Deletes extends Action Query::equal('period', ['1h']), Query::lessThan('time', $hourlyUsageRetentionDatetime), Query::orderDesc('time'), - Query::orderDesc(), // _key_period_time `period` ASC, `time `ASC + Query::orderDesc(), ], $dbForProject); if ($project->getId() !== 'console') { @@ -459,7 +449,7 @@ class Deletes extends Action Query::equal('period', ['1h']), Query::lessThan('time', $hourlyUsageRetentionDatetime), Query::orderDesc('time'), - Query::orderDesc(), // _key_period_time `period` ASC, `time `ASC + Query::orderDesc(), ], $dbForLogs); } } @@ -639,11 +629,7 @@ class Deletes extends Action Query::orderAsc() ], $dbForPlatform); - /** - * No projectInternalId in this collection - * todo: No index for `projectId` attribute - */ - // Delete Schedules () + // Delete Schedules $this->deleteByGroup('schedules', [ Query::equal('projectId', [$projectId]), Query::orderAsc() @@ -724,9 +710,6 @@ class Deletes extends Action ], $dbForProject); // Delete identities - /** - * todo: Remove Duplication index `_key_userInternalId` , But lets leave because of index length issue in unique index - */ $this->deleteByGroup('identities', [ Query::equal('userInternalId', [$userInternalId]), Query::orderAsc() @@ -799,7 +782,7 @@ class Deletes extends Action $this->deleteByGroup('realtime', [ Query::lessThan('timestamp', $datetime), Query::orderDesc('timestamp'), - Query::orderAsc(), // KEY "_key_timestamp" ("timestamp" DESC), + Query::orderAsc(), ], $dbForPlatform); } @@ -820,7 +803,7 @@ class Deletes extends Action Query::select(['$internalId', '$id', '$updatedAt']), Query::lessThan('time', $auditRetention), Query::orderDesc('time'), - Query::orderAsc(), // KEY "index-time" ("time" DESC) + Query::orderAsc(), ], $dbForProject); } catch (DatabaseException $e) { Console::error('Failed to delete audit logs for project ' . $projectId . ': ' . $e->getMessage()); @@ -845,7 +828,6 @@ class Deletes extends Action /** * Delete rules - * todo: No index for this query, drop _key_projectInternalId and create _key_projectInternalId, resourceInternalId, resourceType */ Console::info("Deleting rules for function " . $functionId); $this->deleteByGroup('rules', [ @@ -859,7 +841,6 @@ class Deletes extends Action /** * Delete Variables - * todo: No index for this query , drop _key_resourceInternalId and create new one with {resourceInternalId, resourceType} */ Console::info("Deleting variables for function " . $functionId); $this->deleteByGroup('variables', [ @@ -885,7 +866,6 @@ class Deletes extends Action /** * Delete builds - * todo: No index for `deploymentInternalId`, perhaps use deploymentId until fixed */ Console::info("Deleting builds for function " . $functionId); @@ -900,7 +880,6 @@ class Deletes extends Action /** * Delete Executions - * todo: No index for `functionInternalId` , perhaps use functionId until fixed */ Console::info("Deleting executions for function " . $functionId); $this->deleteByGroup('executions', [ @@ -923,9 +902,6 @@ class Deletes extends Action $providerRepositoryId = $document->getAttribute('providerRepositoryId', ''); $projectInternalId = $document->getAttribute('projectInternalId', ''); - /** - * todo: add index to this query - */ $this->deleteByGroup('vcsComments', [ Query::equal('providerRepositoryId', [$providerRepositoryId]), Query::equal('projectInternalId', [$projectInternalId]), @@ -1025,7 +1001,6 @@ class Deletes extends Action /** * Delete builds - * todo: no index for `deploymentInternalId` Same as above index , no need to handle again... */ Console::info("Deleting builds for deployment " . $deploymentId); From a20140eb36dfc7238324cf40989bab9842e89abf Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 25 Mar 2025 15:52:54 +1300 Subject: [PATCH 30/80] Update src/Appwrite/Platform/Workers/Deletes.php --- src/Appwrite/Platform/Workers/Deletes.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 0de79cd67e..93dbaf501c 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -851,7 +851,6 @@ class Deletes extends Action /** * Delete Deployments - * todo: No index for `resourceInternalId`, perhaps use resourceId until fixed */ Console::info("Deleting deployments for function " . $functionId); From c9c9db564053523be084bb5a97832221eb6ee28c Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 25 Mar 2025 15:52:59 +1300 Subject: [PATCH 31/80] Update src/Appwrite/Platform/Workers/Deletes.php --- src/Appwrite/Platform/Workers/Deletes.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 93dbaf501c..6dff127784 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -889,7 +889,6 @@ class Deletes extends Action /** * Delete VCS Repositories and VCS Comments - * todo: no index for this query */ Console::info("Deleting VCS repositories and comments linked to function " . $functionId); $this->deleteByGroup('repositories', [ From 6bcdc8c769185b77dc9689ed691e53a4071b1453 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 25 Mar 2025 16:01:27 +1300 Subject: [PATCH 32/80] Update src/Appwrite/Utopia/Database/Validator/Queries/Base.php --- src/Appwrite/Utopia/Database/Validator/Queries/Base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php index a13314ab2f..664476af36 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php @@ -82,7 +82,7 @@ class Base extends Queries new Offset(), new Cursor(), new Filter($attributes, APP_DATABASE_QUERY_MAX_VALUES), - new Order(array_merge($attributes, [$internalId])), + new Order([...$attributes, $internalId])), ]; parent::__construct($validators); From 74d9a3c181ebf09b9a7b6da710b6431dcaac4097 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 25 Mar 2025 16:19:18 +1300 Subject: [PATCH 33/80] Fix syntax --- src/Appwrite/Utopia/Database/Validator/Queries/Base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php index 664476af36..e8eafba5a0 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php @@ -82,7 +82,7 @@ class Base extends Queries new Offset(), new Cursor(), new Filter($attributes, APP_DATABASE_QUERY_MAX_VALUES), - new Order([...$attributes, $internalId])), + new Order([...$attributes, $internalId]), ]; parent::__construct($validators); From 6195674611b22d8d65c83a038f2d28cdc662ba36 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 25 Mar 2025 21:20:26 +1300 Subject: [PATCH 34/80] Update database --- composer.json | 2 +- composer.lock | 27 ++++++++++++++++++--------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index b1b6aed539..5b0f26b883 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "utopia-php/cache": "0.12.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.62.*", + "utopia-php/database": "dev-feat-multi-tenant-insert as 0.62.1", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", diff --git a/composer.lock b/composer.lock index 1ea887fdbc..09b8250af6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d2bec8137dcd84994121f89a29932d31", + "content-hash": "640d2a8be42ae521ea12d8a908873f2b", "packages": [ { "name": "adhocore/jwt", @@ -3497,16 +3497,16 @@ }, { "name": "utopia-php/database", - "version": "0.62.1", + "version": "dev-feat-multi-tenant-insert", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "65dc51466c12552add10395900cdbb4728da4068" + "reference": "2e01c999153a202a8bae9c21e4302e7a674442d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/65dc51466c12552add10395900cdbb4728da4068", - "reference": "65dc51466c12552add10395900cdbb4728da4068", + "url": "https://api.github.com/repos/utopia-php/database/zipball/2e01c999153a202a8bae9c21e4302e7a674442d0", + "reference": "2e01c999153a202a8bae9c21e4302e7a674442d0", "shasum": "" }, "require": { @@ -3546,9 +3546,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.62.1" + "source": "https://github.com/utopia-php/database/tree/feat-multi-tenant-insert" }, - "time": "2025-03-24T08:27:18+00:00" + "time": "2025-03-25T04:45:25+00:00" }, { "name": "utopia-php/domains", @@ -8132,9 +8132,18 @@ "time": "2024-03-07T20:33:40+00:00" } ], - "aliases": [], + "aliases": [ + { + "package": "utopia-php/database", + "version": "dev-feat-multi-tenant-insert", + "alias": "0.62.1", + "alias_normalized": "0.62.1.0" + } + ], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "utopia-php/database": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { From 5582c6956de156a633c3e910060a2d77bf8a3af0 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Tue, 25 Mar 2025 17:03:19 +0530 Subject: [PATCH 35/80] Remove specific checks --- tests/e2e/Services/Sites/SitesCustomServerTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index 35dfaa0e81..86fc20c234 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -2294,7 +2294,6 @@ class SitesCustomServerTest extends Scope $this->assertEventually(function () use ($siteId, $deploymentId) { $deployment = $this->getDeployment($siteId, $deploymentId); $this->assertEquals('failed', $deployment['body']['status'], 'Deployment status is failed, deployment: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); - $this->assertStringContainsString('Error: Build output directory is empty.', $deployment['body']['buildLogs']); }, 100000, 500); $this->cleanupSite($siteId); @@ -2333,7 +2332,6 @@ class SitesCustomServerTest extends Scope $this->assertEventually(function () use ($siteId, $deploymentId) { $deployment = $this->getDeployment($siteId, $deploymentId); $this->assertEquals('failed', $deployment['body']['status'], 'Deployment status is failed, deployment: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); - $this->assertStringContainsString('Error: Output directory is either not set or does not exist.', $deployment['body']['buildLogs']); }, 100000, 500); $this->cleanupSite($siteId); From d89cdbcf5a52717b7dc7e48b88020c2604502f0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 25 Mar 2025 21:34:09 +0100 Subject: [PATCH 36/80] Fix schedules region filtering --- src/Appwrite/Migration/Version/V19.php | 3 +-- src/Appwrite/Platform/Tasks/ScheduleBase.php | 9 ++++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Appwrite/Migration/Version/V19.php b/src/Appwrite/Migration/Version/V19.php index 18234ebdc4..7adf5b85be 100644 --- a/src/Appwrite/Migration/Version/V19.php +++ b/src/Appwrite/Migration/Version/V19.php @@ -10,7 +10,6 @@ use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Exception; use Utopia\Database\Query; -use Utopia\System\System; class V19 extends Migration { @@ -731,7 +730,7 @@ class V19 extends Migration if (empty($document->getAttribute('scheduleId', null))) { $schedule = $this->consoleDB->createDocument('schedules', new Document([ - 'region' => System::getEnv('_APP_REGION', 'default'), // Todo replace with projects region + 'region' => $project->getAttribute('region'), // Todo replace with projects region 'resourceType' => 'function', 'resourceId' => $document->getId(), 'resourceInternalId' => $document->getInternalId(), diff --git a/src/Appwrite/Platform/Tasks/ScheduleBase.php b/src/Appwrite/Platform/Tasks/ScheduleBase.php index dad2db0d9a..324b88d07b 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleBase.php +++ b/src/Appwrite/Platform/Tasks/ScheduleBase.php @@ -103,8 +103,15 @@ abstract class ScheduleBase extends Action $paginationQueries[] = Query::cursorAfter($latestDocument); } + // Temporarly accepting both 'fra' and 'default' + // When all migrated, only use _APP_REGION with 'default' as default value + $regions = [System::getEnv('_APP_REGION', 'default')]; + if (!in_array('default', $regions)) { + $regions[] = 'default'; + } + $results = $dbForPlatform->find('schedules', \array_merge($paginationQueries, [ - Query::equal('region', [System::getEnv('_APP_REGION', 'default')]), + Query::equal('region', $regions), Query::equal('resourceType', [static::getSupportedResource()]), Query::equal('active', [true]), ])); From f25143b240177b141c487048d5e9de03a85e7029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 25 Mar 2025 21:52:02 +0100 Subject: [PATCH 37/80] Support fra-region on cloud; temporary fix --- src/Appwrite/Migration/Version/V19.php | 2 +- src/Appwrite/Platform/Tasks/ScheduleBase.php | 9 ++++++++- src/Appwrite/Platform/Workers/Deletes.php | 9 ++++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Appwrite/Migration/Version/V19.php b/src/Appwrite/Migration/Version/V19.php index 7adf5b85be..4415003bfd 100644 --- a/src/Appwrite/Migration/Version/V19.php +++ b/src/Appwrite/Migration/Version/V19.php @@ -730,7 +730,7 @@ class V19 extends Migration if (empty($document->getAttribute('scheduleId', null))) { $schedule = $this->consoleDB->createDocument('schedules', new Document([ - 'region' => $project->getAttribute('region'), // Todo replace with projects region + 'region' => $project->getAttribute('region'), 'resourceType' => 'function', 'resourceId' => $document->getId(), 'resourceInternalId' => $document->getInternalId(), diff --git a/src/Appwrite/Platform/Tasks/ScheduleBase.php b/src/Appwrite/Platform/Tasks/ScheduleBase.php index 324b88d07b..a3c36cb96e 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleBase.php +++ b/src/Appwrite/Platform/Tasks/ScheduleBase.php @@ -160,8 +160,15 @@ abstract class ScheduleBase extends Action $paginationQueries[] = Query::cursorAfter($latestDocument); } + // Temporarly accepting both 'fra' and 'default' + // When all migrated, only use _APP_REGION with 'default' as default value + $regions = [System::getEnv('_APP_REGION', 'default')]; + if (!in_array('default', $regions)) { + $regions[] = 'default'; + } + $results = $dbForPlatform->find('schedules', \array_merge($paginationQueries, [ - Query::equal('region', [System::getEnv('_APP_REGION', 'default')]), + Query::equal('region', $regions), Query::equal('resourceType', [static::getSupportedResource()]), Query::greaterThanEqual('resourceUpdatedAt', $lastSyncUpdate), ])); diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 6dff127784..8eaa08ce65 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -180,10 +180,17 @@ class Deletes extends Action */ private function deleteSchedules(Database $dbForPlatform, callable $getProjectDB, string $datetime): void { + // Temporarly accepting both 'fra' and 'default' + // When all migrated, only use _APP_REGION with 'default' as default value + $regions = [System::getEnv('_APP_REGION', 'default')]; + if (!in_array('default', $regions)) { + $regions[] = 'default'; + } + $this->listByGroup( 'schedules', [ - Query::equal('region', [System::getEnv('_APP_REGION', 'default')]), + Query::equal('region', $regions), Query::lessThanEqual('resourceUpdatedAt', $datetime), Query::equal('active', [false]), ], From a461d5bf7281841ccbbe1c4a27d31e850b8dc2ad Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 26 Mar 2025 09:20:44 +0200 Subject: [PATCH 38/80] Deletes fix selects --- src/Appwrite/Platform/Workers/Deletes.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 8eaa08ce65..2cc1eaf9af 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -31,6 +31,8 @@ use Utopia\System\System; class Deletes extends Action { + protected array $selects = ['$internalId', '$id', '$collection', '$updatedAt']; + public static function getName(): string { return 'deletes'; @@ -366,7 +368,7 @@ class Deletes extends Action $queries[] = Query::equal('resourceType', [$resourceType]); } - $queries[] = Query::select(['$internalId', '$id', '$updatedAt']); + $queries[] = Query::select($this->selects); $queries[] = Query::orderAsc(); $this->deleteByGroup( @@ -403,7 +405,7 @@ class Deletes extends Action ); $queries = [ - Query::select(['$internalId', '$id', '$updatedAt']), + Query::select($this->selects), Query::lessThan('accessedAt', $datetime), Query::orderDesc('accessedAt'), Query::orderDesc(), @@ -439,7 +441,7 @@ class Deletes extends Action // Delete Usage stats from projectDB $this->deleteByGroup('stats', [ - Query::select(['$internalId', '$id', '$updatedAt']), + Query::select($this->selects), Query::equal('period', ['1h']), Query::lessThan('time', $hourlyUsageRetentionDatetime), Query::orderDesc('time'), @@ -452,7 +454,7 @@ class Deletes extends Action // Delete Usage stats from logsDB $this->deleteByGroup('stats', [ - Query::select(['$internalId', '$id', '$updatedAt']), + Query::select($this->selects), Query::equal('period', ['1h']), Query::lessThan('time', $hourlyUsageRetentionDatetime), Query::orderDesc('time'), @@ -749,7 +751,7 @@ class Deletes extends Action // Delete Executions $this->deleteByGroup('executions', [ - Query::select(['$internalId', '$id', '$updatedAt']), + Query::select($this->selects), Query::lessThan('$createdAt', $datetime), Query::orderDesc('$createdAt'), Query::orderDesc(), @@ -770,7 +772,7 @@ class Deletes extends Action // Delete Sessions $this->deleteByGroup('sessions', [ - Query::select(['$internalId', '$id', '$updatedAt']), + Query::select($this->selects), Query::lessThan('$createdAt', $expired), Query::orderDesc('$createdAt'), Query::orderDesc(), @@ -807,7 +809,7 @@ class Deletes extends Action try { $this->deleteByGroup(Audit::COLLECTION, [ - Query::select(['$internalId', '$id', '$updatedAt']), + Query::select($this->selects), Query::lessThan('time', $auditRetention), Query::orderDesc('time'), Query::orderAsc(), @@ -889,7 +891,7 @@ class Deletes extends Action */ Console::info("Deleting executions for function " . $functionId); $this->deleteByGroup('executions', [ - Query::select(['$internalId', '$id', '$updatedAt']), + Query::select($this->selects), Query::equal('functionInternalId', [$functionInternalId]), Query::orderAsc() ], $dbForProject); From 424a3575034c31b34b6c2c80a24fb953338642bd Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 26 Mar 2025 10:02:30 +0200 Subject: [PATCH 39/80] Add $permissions --- src/Appwrite/Platform/Workers/Deletes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 2cc1eaf9af..5033822241 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -31,7 +31,7 @@ use Utopia\System\System; class Deletes extends Action { - protected array $selects = ['$internalId', '$id', '$collection', '$updatedAt']; + protected array $selects = ['$internalId', '$id', '$collection', '$permissions', '$updatedAt']; public static function getName(): string { From d9d38197ddcd91c6429843ba5878b9a9a4e3bef7 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 26 Mar 2025 23:47:59 +1300 Subject: [PATCH 40/80] Update libs --- composer.json | 2 +- composer.lock | 65 ++++++++++++++++++++++----------------------------- 2 files changed, 29 insertions(+), 38 deletions(-) diff --git a/composer.json b/composer.json index 5b0f26b883..b8c5afa109 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "utopia-php/cache": "0.12.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "dev-feat-multi-tenant-insert as 0.62.1", + "utopia-php/database": "0.63.*", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", diff --git a/composer.lock b/composer.lock index 09b8250af6..d4956aeba0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "640d2a8be42ae521ea12d8a908873f2b", + "content-hash": "5b3c46863e4571c838c30090ad96436c", "packages": [ { "name": "adhocore/jwt", @@ -3497,16 +3497,16 @@ }, { "name": "utopia-php/database", - "version": "dev-feat-multi-tenant-insert", + "version": "0.63.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "2e01c999153a202a8bae9c21e4302e7a674442d0" + "reference": "51ff0a6514e6eda44a6da016d838e7e55282e01a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/2e01c999153a202a8bae9c21e4302e7a674442d0", - "reference": "2e01c999153a202a8bae9c21e4302e7a674442d0", + "url": "https://api.github.com/repos/utopia-php/database/zipball/51ff0a6514e6eda44a6da016d838e7e55282e01a", + "reference": "51ff0a6514e6eda44a6da016d838e7e55282e01a", "shasum": "" }, "require": { @@ -3546,9 +3546,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/feat-multi-tenant-insert" + "source": "https://github.com/utopia-php/database/tree/0.63.0" }, - "time": "2025-03-25T04:45:25+00:00" + "time": "2025-03-26T10:35:37+00:00" }, { "name": "utopia-php/domains", @@ -3950,16 +3950,16 @@ }, { "name": "utopia-php/migration", - "version": "0.8.2", + "version": "0.8.3", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "aa3b7a508feb7090f487e7bf9cd71f5c92fbc7c1" + "reference": "85c2e14647b240b75be6b6b762e5b30e48fb8d8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/aa3b7a508feb7090f487e7bf9cd71f5c92fbc7c1", - "reference": "aa3b7a508feb7090f487e7bf9cd71f5c92fbc7c1", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/85c2e14647b240b75be6b6b762e5b30e48fb8d8a", + "reference": "85c2e14647b240b75be6b6b762e5b30e48fb8d8a", "shasum": "" }, "require": { @@ -3967,7 +3967,7 @@ "ext-curl": "*", "ext-openssl": "*", "php": ">=8.1", - "utopia-php/database": "0.62.*", + "utopia-php/database": "0.63.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*", "utopia-php/storage": "0.18.*" @@ -4000,9 +4000,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/0.8.2" + "source": "https://github.com/utopia-php/migration/tree/0.8.3" }, - "time": "2025-03-24T09:05:31+00:00" + "time": "2025-03-26T10:45:51+00:00" }, { "name": "utopia-php/orchestration", @@ -4385,16 +4385,16 @@ }, { "name": "utopia-php/swoole", - "version": "0.8.2", + "version": "0.8.3", "source": { "type": "git", "url": "https://github.com/utopia-php/swoole.git", - "reference": "5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4" + "reference": "1af73dd3e73987cf729c7db399054e4a70befd99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/swoole/zipball/5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4", - "reference": "5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4", + "url": "https://api.github.com/repos/utopia-php/swoole/zipball/1af73dd3e73987cf729c7db399054e4a70befd99", + "reference": "1af73dd3e73987cf729c7db399054e4a70befd99", "shasum": "" }, "require": { @@ -4430,9 +4430,9 @@ ], "support": { "issues": "https://github.com/utopia-php/swoole/issues", - "source": "https://github.com/utopia-php/swoole/tree/0.8.2" + "source": "https://github.com/utopia-php/swoole/tree/0.8.3" }, - "time": "2024-02-01T14:54:12+00:00" + "time": "2025-03-26T10:09:05+00:00" }, { "name": "utopia-php/system", @@ -4775,16 +4775,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.40.9", + "version": "0.40.10", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "dbb45a5db22cdc3368fe2573c07ba6088f188fa4" + "reference": "054ac96285caf4f77879087b2416a5ddb8263051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/dbb45a5db22cdc3368fe2573c07ba6088f188fa4", - "reference": "dbb45a5db22cdc3368fe2573c07ba6088f188fa4", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/054ac96285caf4f77879087b2416a5ddb8263051", + "reference": "054ac96285caf4f77879087b2416a5ddb8263051", "shasum": "" }, "require": { @@ -4820,9 +4820,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/0.40.9" + "source": "https://github.com/appwrite/sdk-generator/tree/0.40.10" }, - "time": "2025-03-17T18:39:14+00:00" + "time": "2025-03-25T13:44:16+00:00" }, { "name": "doctrine/annotations", @@ -8132,18 +8132,9 @@ "time": "2024-03-07T20:33:40+00:00" } ], - "aliases": [ - { - "package": "utopia-php/database", - "version": "dev-feat-multi-tenant-insert", - "alias": "0.62.1", - "alias_normalized": "0.62.1.0" - } - ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "utopia-php/database": 20 - }, + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { From c79e8e5b954ce3e61c39bcd01413d05f7905b2dd Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 27 Mar 2025 18:04:17 +1300 Subject: [PATCH 41/80] Update database --- composer.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/composer.lock b/composer.lock index d4956aeba0..eb29c765a0 100644 --- a/composer.lock +++ b/composer.lock @@ -709,16 +709,16 @@ }, { "name": "google/protobuf", - "version": "v4.30.1", + "version": "v4.30.2", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "f29ba8a30dfd940efb3a8a75dc44446539101f24" + "reference": "a4c4d8565b40b9f76debc9dfeb221412eacb8ced" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/f29ba8a30dfd940efb3a8a75dc44446539101f24", - "reference": "f29ba8a30dfd940efb3a8a75dc44446539101f24", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/a4c4d8565b40b9f76debc9dfeb221412eacb8ced", + "reference": "a4c4d8565b40b9f76debc9dfeb221412eacb8ced", "shasum": "" }, "require": { @@ -747,9 +747,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.30.1" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.30.2" }, - "time": "2025-03-13T21:08:17+00:00" + "time": "2025-03-26T18:01:50+00:00" }, { "name": "league/csv", @@ -3497,16 +3497,16 @@ }, { "name": "utopia-php/database", - "version": "0.63.0", + "version": "0.63.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "51ff0a6514e6eda44a6da016d838e7e55282e01a" + "reference": "ad191bf34151815f716f553796a363ff2b6ef7d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/51ff0a6514e6eda44a6da016d838e7e55282e01a", - "reference": "51ff0a6514e6eda44a6da016d838e7e55282e01a", + "url": "https://api.github.com/repos/utopia-php/database/zipball/ad191bf34151815f716f553796a363ff2b6ef7d3", + "reference": "ad191bf34151815f716f553796a363ff2b6ef7d3", "shasum": "" }, "require": { @@ -3546,9 +3546,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.63.0" + "source": "https://github.com/utopia-php/database/tree/0.63.1" }, - "time": "2025-03-26T10:35:37+00:00" + "time": "2025-03-27T04:58:07+00:00" }, { "name": "utopia-php/domains", From 17dc8e3f8ad82afc911e492b0c4be9218f0305a3 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 27 Mar 2025 21:37:10 +1300 Subject: [PATCH 42/80] Revert "chore(audits): return queue pre-fetch results" --- src/Appwrite/Platform/Workers/Audits.php | 49 +++++++++++------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Audits.php b/src/Appwrite/Platform/Workers/Audits.php index 962bc622d1..ed5ff8010a 100644 --- a/src/Appwrite/Platform/Workers/Audits.php +++ b/src/Appwrite/Platform/Workers/Audits.php @@ -12,12 +12,12 @@ use Utopia\Database\Exception\Authorization; use Utopia\Database\Exception\Structure; use Utopia\Platform\Action; use Utopia\Queue\Message; -use Utopia\Queue\Result\Commit; -use Utopia\Queue\Result\NoCommit; use Utopia\System\System; class Audits extends Action { + protected const BATCH_SIZE_DEVELOPMENT = 1; // smaller batch size for development + protected const BATCH_SIZE_PRODUCTION = 5_000; protected const BATCH_AGGREGATION_INTERVAL = 60; // in seconds private int $lastTriggeredTime = 0; @@ -27,7 +27,9 @@ class Audits extends Action protected function getBatchSize(): int { - return intval(System::getEnv('_APP_QUEUE_PREFETCH_COUNT', 1)); + return System::getEnv('_APP_ENV', 'development') === 'development' + ? self::BATCH_SIZE_DEVELOPMENT + : self::BATCH_SIZE_PRODUCTION; } public static function getName(): string @@ -55,13 +57,13 @@ class Audits extends Action * @param Message $message * @param callable $getProjectDB * @param Document $project - * @return Commit|NoCommit + * @return void * @throws Throwable * @throws \Utopia\Database\Exception * @throws Authorization * @throws Structure */ - public function action(Message $message, callable $getProjectDB, Document $project): Commit|NoCommit + public function action(Message $message, callable $getProjectDB, Document $project): void { $payload = $message->getPayload() ?? []; @@ -117,34 +119,29 @@ class Audits extends Action // Check if we should process the batch by checking both for the batch size and the elapsed time $batchSize = $this->getBatchSize(); - $logCount = array_reduce($this->logs, fn (int $current, $logs) => $current + count($logs), 0); - $shouldProcessBatch = $logCount >= $batchSize; - if (!$shouldProcessBatch && $logCount > 0) { + $shouldProcessBatch = \count($this->logs) >= $batchSize; + if (!$shouldProcessBatch && \count($this->logs) > 0) { $shouldProcessBatch = (\time() - $this->lastTriggeredTime) >= self::BATCH_AGGREGATION_INTERVAL; } - if (!$shouldProcessBatch) { - return new NoCommit(); - } + if ($shouldProcessBatch) { + try { + foreach ($this->logs as $internalId => $projectLogs) { + $dbForProject = $getProjectDB($projectLogs['project']); - try { - foreach ($this->logs as $internalId => $projectLogs) { - $dbForProject = $getProjectDB($projectLogs['project']); + Console::log('Processing batch with ' . count($projectLogs['logs']) . ' events'); + $audit = new Audit($dbForProject); - Console::log('Processing batch with ' . count($projectLogs['logs']) . ' events'); - $audit = new Audit($dbForProject); + $audit->logBatch($projectLogs['logs']); + Console::success('Audit logs processed successfully'); - $audit->logBatch($projectLogs['logs']); - Console::success('Audit logs processed successfully'); - - unset($this->logs[$internalId]); + unset($this->logs[$internalId]); + } + } catch (Throwable $e) { + Console::error('Error processing audit logs: ' . $e->getMessage()); + } finally { + $this->lastTriggeredTime = time(); } - return new Commit(); - } catch (Throwable $e) { - Console::error('Error processing audit logs: ' . $e->getMessage()); - return new NoCommit(); - } finally { - $this->lastTriggeredTime = time(); } } } From cba700402bb79598a526f0e6d026060cf0cd3e50 Mon Sep 17 00:00:00 2001 From: fogelito Date: Fri, 28 Mar 2025 09:04:50 +0300 Subject: [PATCH 43/80] Add order by for cursor --- src/Appwrite/Platform/Workers/Deletes.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 5033822241..5674448c18 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -405,7 +405,7 @@ class Deletes extends Action ); $queries = [ - Query::select($this->selects), + Query::select(array_merge($this->selects, ['accessedAt'])), Query::lessThan('accessedAt', $datetime), Query::orderDesc('accessedAt'), Query::orderDesc(), @@ -439,9 +439,11 @@ class Deletes extends Action /** @var Database $dbForProject*/ $dbForProject = $getProjectDB($project); + $select = array_merge($this->selects, ['time']); + // Delete Usage stats from projectDB $this->deleteByGroup('stats', [ - Query::select($this->selects), + Query::select($select), Query::equal('period', ['1h']), Query::lessThan('time', $hourlyUsageRetentionDatetime), Query::orderDesc('time'), @@ -454,7 +456,7 @@ class Deletes extends Action // Delete Usage stats from logsDB $this->deleteByGroup('stats', [ - Query::select($this->selects), + Query::select($select), Query::equal('period', ['1h']), Query::lessThan('time', $hourlyUsageRetentionDatetime), Query::orderDesc('time'), @@ -751,7 +753,7 @@ class Deletes extends Action // Delete Executions $this->deleteByGroup('executions', [ - Query::select($this->selects), + Query::select(array_merge($this->selects, ['$createdAt'])), Query::lessThan('$createdAt', $datetime), Query::orderDesc('$createdAt'), Query::orderDesc(), @@ -772,7 +774,7 @@ class Deletes extends Action // Delete Sessions $this->deleteByGroup('sessions', [ - Query::select($this->selects), + Query::select(array_merge($this->selects, ['$createdAt'])), Query::lessThan('$createdAt', $expired), Query::orderDesc('$createdAt'), Query::orderDesc(), @@ -809,7 +811,7 @@ class Deletes extends Action try { $this->deleteByGroup(Audit::COLLECTION, [ - Query::select($this->selects), + Query::select(array_merge($this->selects, ['time'])), Query::lessThan('time', $auditRetention), Query::orderDesc('time'), Query::orderAsc(), From a4c18c4b774eefa25af547b31929986d953a2552 Mon Sep 17 00:00:00 2001 From: fogelito Date: Fri, 28 Mar 2025 09:16:25 +0300 Subject: [PATCH 44/80] Remove array merge --- src/Appwrite/Platform/Workers/Deletes.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 5674448c18..a9b83976a4 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -405,7 +405,7 @@ class Deletes extends Action ); $queries = [ - Query::select(array_merge($this->selects, ['accessedAt'])), + Query::select([...$this->selects, 'accessedAt']), Query::lessThan('accessedAt', $datetime), Query::orderDesc('accessedAt'), Query::orderDesc(), @@ -439,11 +439,11 @@ class Deletes extends Action /** @var Database $dbForProject*/ $dbForProject = $getProjectDB($project); - $select = array_merge($this->selects, ['time']); + $selects = [...$this->selects, 'time']; // Delete Usage stats from projectDB $this->deleteByGroup('stats', [ - Query::select($select), + Query::select($selects), Query::equal('period', ['1h']), Query::lessThan('time', $hourlyUsageRetentionDatetime), Query::orderDesc('time'), @@ -456,7 +456,7 @@ class Deletes extends Action // Delete Usage stats from logsDB $this->deleteByGroup('stats', [ - Query::select($select), + Query::select($selects), Query::equal('period', ['1h']), Query::lessThan('time', $hourlyUsageRetentionDatetime), Query::orderDesc('time'), @@ -753,7 +753,7 @@ class Deletes extends Action // Delete Executions $this->deleteByGroup('executions', [ - Query::select(array_merge($this->selects, ['$createdAt'])), + Query::select([...$this->selects, '$createdAt']), Query::lessThan('$createdAt', $datetime), Query::orderDesc('$createdAt'), Query::orderDesc(), @@ -774,7 +774,7 @@ class Deletes extends Action // Delete Sessions $this->deleteByGroup('sessions', [ - Query::select(array_merge($this->selects, ['$createdAt'])), + Query::select([...$this->selects, '$createdAt']), Query::lessThan('$createdAt', $expired), Query::orderDesc('$createdAt'), Query::orderDesc(), @@ -811,7 +811,7 @@ class Deletes extends Action try { $this->deleteByGroup(Audit::COLLECTION, [ - Query::select(array_merge($this->selects, ['time'])), + Query::select([...$this->selects, 'time']), Query::lessThan('time', $auditRetention), Query::orderDesc('time'), Query::orderAsc(), From 22939831f2d759f26bc5912b2b3e7a9cdceadc62 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 28 Mar 2025 20:58:25 +0100 Subject: [PATCH 45/80] Updated fetch version --- composer.json | 2 +- composer.lock | 78 +++++++++++++++++++++++++-------------------------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/composer.json b/composer.json index b8c5afa109..176b5889fc 100644 --- a/composer.json +++ b/composer.json @@ -55,7 +55,7 @@ "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", - "utopia-php/fetch": "0.3.*", + "utopia-php/fetch": "0.4.*", "utopia-php/image": "0.8.*", "utopia-php/locale": "0.4.*", "utopia-php/logger": "0.6.*", diff --git a/composer.lock b/composer.lock index eb29c765a0..3772b25707 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5b3c46863e4571c838c30090ad96436c", + "content-hash": "c99b4733669c17013e211c7dc54a86f6", "packages": [ { "name": "adhocore/jwt", @@ -3659,16 +3659,16 @@ }, { "name": "utopia-php/fetch", - "version": "0.3.1", + "version": "0.4.0", "source": { "type": "git", "url": "https://github.com/utopia-php/fetch.git", - "reference": "524dd50afa8c64670c4fb18f1df4db9b5bb4b3d0" + "reference": "46e791ff6a95864517750b9df6bbf4a17e3c9c4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/fetch/zipball/524dd50afa8c64670c4fb18f1df4db9b5bb4b3d0", - "reference": "524dd50afa8c64670c4fb18f1df4db9b5bb4b3d0", + "url": "https://api.github.com/repos/utopia-php/fetch/zipball/46e791ff6a95864517750b9df6bbf4a17e3c9c4e", + "reference": "46e791ff6a95864517750b9df6bbf4a17e3c9c4e", "shasum": "" }, "require": { @@ -3692,9 +3692,9 @@ "description": "A simple library that provides an interface for making HTTP Requests.", "support": { "issues": "https://github.com/utopia-php/fetch/issues", - "source": "https://github.com/utopia-php/fetch/tree/0.3.1" + "source": "https://github.com/utopia-php/fetch/tree/0.4.0" }, - "time": "2025-03-05T18:08:55+00:00" + "time": "2025-03-11T21:06:56+00:00" }, { "name": "utopia-php/framework", @@ -3950,16 +3950,16 @@ }, { "name": "utopia-php/migration", - "version": "0.8.3", + "version": "0.8.4", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "85c2e14647b240b75be6b6b762e5b30e48fb8d8a" + "reference": "845fd04ccf5e0edb03c184b864e0596080a432b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/85c2e14647b240b75be6b6b762e5b30e48fb8d8a", - "reference": "85c2e14647b240b75be6b6b762e5b30e48fb8d8a", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/845fd04ccf5e0edb03c184b864e0596080a432b8", + "reference": "845fd04ccf5e0edb03c184b864e0596080a432b8", "shasum": "" }, "require": { @@ -3967,7 +3967,7 @@ "ext-curl": "*", "ext-openssl": "*", "php": ">=8.1", - "utopia-php/database": "0.63.*", + "utopia-php/database": "0.*.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*", "utopia-php/storage": "0.18.*" @@ -4000,9 +4000,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/0.8.3" + "source": "https://github.com/utopia-php/migration/tree/0.8.4" }, - "time": "2025-03-26T10:45:51+00:00" + "time": "2025-03-28T02:08:22+00:00" }, { "name": "utopia-php/orchestration", @@ -4211,23 +4211,23 @@ }, { "name": "utopia-php/queue", - "version": "0.9.0", + "version": "0.9.1", "source": { "type": "git", "url": "https://github.com/utopia-php/queue.git", - "reference": "077075f1d57afa430f76c35ed3bf4616e0eee8e7" + "reference": "32b6f84c55aae761db5a5ae76cc91ca8dbc8bc32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/queue/zipball/077075f1d57afa430f76c35ed3bf4616e0eee8e7", - "reference": "077075f1d57afa430f76c35ed3bf4616e0eee8e7", + "url": "https://api.github.com/repos/utopia-php/queue/zipball/32b6f84c55aae761db5a5ae76cc91ca8dbc8bc32", + "reference": "32b6f84c55aae761db5a5ae76cc91ca8dbc8bc32", "shasum": "" }, "require": { "php": ">=8.3", "php-amqplib/php-amqplib": "^3.7", "utopia-php/cli": "0.15.*", - "utopia-php/fetch": "^0.3.0", + "utopia-php/fetch": "0.4.*", "utopia-php/framework": "0.33.*", "utopia-php/telemetry": "0.1.*" }, @@ -4270,9 +4270,9 @@ ], "support": { "issues": "https://github.com/utopia-php/queue/issues", - "source": "https://github.com/utopia-php/queue/tree/0.9.0" + "source": "https://github.com/utopia-php/queue/tree/0.9.1" }, - "time": "2025-03-13T12:22:41+00:00" + "time": "2025-03-28T19:49:36+00:00" }, { "name": "utopia-php/registry", @@ -4775,16 +4775,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.40.10", + "version": "0.40.11", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "054ac96285caf4f77879087b2416a5ddb8263051" + "reference": "0ec5f4a60c15e33e208bc3444ba6148b1d0f0027" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/054ac96285caf4f77879087b2416a5ddb8263051", - "reference": "054ac96285caf4f77879087b2416a5ddb8263051", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/0ec5f4a60c15e33e208bc3444ba6148b1d0f0027", + "reference": "0ec5f4a60c15e33e208bc3444ba6148b1d0f0027", "shasum": "" }, "require": { @@ -4820,9 +4820,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/0.40.10" + "source": "https://github.com/appwrite/sdk-generator/tree/0.40.11" }, - "time": "2025-03-25T13:44:16+00:00" + "time": "2025-03-26T10:53:16+00:00" }, { "name": "doctrine/annotations", @@ -7154,16 +7154,16 @@ }, { "name": "symfony/console", - "version": "v7.2.1", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" + "reference": "e51498ea18570c062e7df29d05a7003585b19b88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "url": "https://api.github.com/repos/symfony/console/zipball/e51498ea18570c062e7df29d05a7003585b19b88", + "reference": "e51498ea18570c062e7df29d05a7003585b19b88", "shasum": "" }, "require": { @@ -7227,7 +7227,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.1" + "source": "https://github.com/symfony/console/tree/v7.2.5" }, "funding": [ { @@ -7243,7 +7243,7 @@ "type": "tidelift" } ], - "time": "2024-12-11T03:49:26+00:00" + "time": "2025-03-12T08:11:12+00:00" }, { "name": "symfony/filesystem", @@ -7758,16 +7758,16 @@ }, { "name": "symfony/process", - "version": "v7.2.4", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf" + "reference": "87b7c93e57df9d8e39a093d32587702380ff045d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", - "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", + "url": "https://api.github.com/repos/symfony/process/zipball/87b7c93e57df9d8e39a093d32587702380ff045d", + "reference": "87b7c93e57df9d8e39a093d32587702380ff045d", "shasum": "" }, "require": { @@ -7799,7 +7799,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.2.4" + "source": "https://github.com/symfony/process/tree/v7.2.5" }, "funding": [ { @@ -7815,7 +7815,7 @@ "type": "tidelift" } ], - "time": "2025-02-05T08:33:46+00:00" + "time": "2025-03-13T12:21:46+00:00" }, { "name": "symfony/string", From 612b064d2a9121949030fe7459ad43732a36e15c Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 31 Mar 2025 15:16:59 +0300 Subject: [PATCH 46/80] 0.63.2 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 176b5889fc..2e25ca3748 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "utopia-php/cache": "0.12.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.63.*", + "utopia-php/database": "dev-0.63.2-fix as 0.63.2", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", From 4698323f38cfd0f49040eac13da1c63b8cca6003 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 31 Mar 2025 16:19:17 +0300 Subject: [PATCH 47/80] lock --- composer.lock | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/composer.lock b/composer.lock index 3772b25707..a25995668e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c99b4733669c17013e211c7dc54a86f6", + "content-hash": "796b9a9952aac4604cf2acfd0ce455c6", "packages": [ { "name": "adhocore/jwt", @@ -3497,16 +3497,16 @@ }, { "name": "utopia-php/database", - "version": "0.63.1", + "version": "dev-0.63.2-fix", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "ad191bf34151815f716f553796a363ff2b6ef7d3" + "reference": "f32c48a8b94aa2e4b543947046daf59e7cbd49d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/ad191bf34151815f716f553796a363ff2b6ef7d3", - "reference": "ad191bf34151815f716f553796a363ff2b6ef7d3", + "url": "https://api.github.com/repos/utopia-php/database/zipball/f32c48a8b94aa2e4b543947046daf59e7cbd49d4", + "reference": "f32c48a8b94aa2e4b543947046daf59e7cbd49d4", "shasum": "" }, "require": { @@ -3546,9 +3546,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.63.1" + "source": "https://github.com/utopia-php/database/tree/0.63.2-fix" }, - "time": "2025-03-27T04:58:07+00:00" + "time": "2025-03-31T11:53:46+00:00" }, { "name": "utopia-php/domains", @@ -8132,9 +8132,18 @@ "time": "2024-03-07T20:33:40+00:00" } ], - "aliases": [], + "aliases": [ + { + "package": "utopia-php/database", + "version": "dev-0.63.2-fix", + "alias": "0.63.2", + "alias_normalized": "0.63.2.0" + } + ], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": { + "utopia-php/database": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { From d92413fd6ba46eb79f548aa957e4258a8d14f7f7 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 1 Apr 2025 08:13:56 +0300 Subject: [PATCH 48/80] Revert database lib --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2e25ca3748..176b5889fc 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "utopia-php/cache": "0.12.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "dev-0.63.2-fix as 0.63.2", + "utopia-php/database": "0.63.*", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", From b46de29735db1de51981aff820440eddad4bbbfe Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 1 Apr 2025 08:15:03 +0300 Subject: [PATCH 49/80] database 0.63.2 --- composer.lock | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/composer.lock b/composer.lock index a25995668e..06aea5ed69 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "796b9a9952aac4604cf2acfd0ce455c6", + "content-hash": "c99b4733669c17013e211c7dc54a86f6", "packages": [ { "name": "adhocore/jwt", @@ -3497,7 +3497,7 @@ }, { "name": "utopia-php/database", - "version": "dev-0.63.2-fix", + "version": "0.63.2", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", @@ -3546,7 +3546,7 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.63.2-fix" + "source": "https://github.com/utopia-php/database/tree/0.63.2" }, "time": "2025-03-31T11:53:46+00:00" }, @@ -8132,18 +8132,9 @@ "time": "2024-03-07T20:33:40+00:00" } ], - "aliases": [ - { - "package": "utopia-php/database", - "version": "dev-0.63.2-fix", - "alias": "0.63.2", - "alias_normalized": "0.63.2.0" - } - ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "utopia-php/database": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { From 938505da5121dc08c84db9ee39b36d332133fe62 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 1 Apr 2025 21:23:23 +0200 Subject: [PATCH 50/80] Updated websocket version --- composer.json | 2 +- composer.lock | 33 ++++++++++++--------------------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/composer.json b/composer.json index 176b5889fc..39ac2f6574 100644 --- a/composer.json +++ b/composer.json @@ -72,7 +72,7 @@ "utopia-php/system": "0.9.*", "utopia-php/telemetry": "0.1.*", "utopia-php/vcs": "0.9.*", - "utopia-php/websocket": "0.1.*", + "utopia-php/websocket": "0.3.*", "matomo/device-detector": "6.1.*", "dragonmantank/cron-expression": "3.3.2", "phpmailer/phpmailer": "6.9.1", diff --git a/composer.lock b/composer.lock index 06aea5ed69..8ee7cd75f0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c99b4733669c17013e211c7dc54a86f6", + "content-hash": "d393dfb19f208bab8b2f165071915fc9", "packages": [ { "name": "adhocore/jwt", @@ -4592,27 +4592,28 @@ }, { "name": "utopia-php/websocket", - "version": "0.1.0", + "version": "0.3.0", "source": { "type": "git", "url": "https://github.com/utopia-php/websocket.git", - "reference": "51fcb86171400d8aa40d76c54593481fd273dab5" + "reference": "629e53640b108eab43c7cc9ab375efade8622d43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/websocket/zipball/51fcb86171400d8aa40d76c54593481fd273dab5", - "reference": "51fcb86171400d8aa40d76c54593481fd273dab5", + "url": "https://api.github.com/repos/utopia-php/websocket/zipball/629e53640b108eab43c7cc9ab375efade8622d43", + "reference": "629e53640b108eab43c7cc9ab375efade8622d43", "shasum": "" }, "require": { "php": ">=8.0" }, "require-dev": { + "laravel/pint": "^1.15", + "phpstan/phpstan": "^1.12", "phpunit/phpunit": "^9.5.5", - "swoole/ide-helper": "4.6.6", + "swoole/ide-helper": "5.1.2", "textalk/websocket": "1.5.2", - "vimeo/psalm": "^4.8.1", - "workerman/workerman": "^4.0" + "workerman/workerman": "4.1.*" }, "type": "library", "autoload": { @@ -4624,16 +4625,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Eldad Fux", - "email": "eldad@appwrite.io" - }, - { - "name": "Torsten Dittmann", - "email": "torsten@appwrite.io" - } - ], "description": "A simple abstraction for WebSocket servers.", "keywords": [ "framework", @@ -4644,9 +4635,9 @@ ], "support": { "issues": "https://github.com/utopia-php/websocket/issues", - "source": "https://github.com/utopia-php/websocket/tree/0.1.0" + "source": "https://github.com/utopia-php/websocket/tree/0.3.0" }, - "time": "2021-12-20T10:50:09+00:00" + "time": "2025-03-28T01:11:13+00:00" }, { "name": "webmozart/assert", @@ -8134,7 +8125,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { From c152d8e001a4b44e9c4069e6fdfb99f96a9a7077 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 2 Apr 2025 13:02:35 +1300 Subject: [PATCH 51/80] Update database --- composer.json | 4 +-- composer.lock | 92 ++++++++++++++++++++++++++++----------------------- 2 files changed, 53 insertions(+), 43 deletions(-) diff --git a/composer.json b/composer.json index b8c5afa109..2d13ef244f 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "utopia-php/cache": "0.12.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.63.*", + "utopia-php/database": "dev-fix-tenant as 0.64.0", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", @@ -63,7 +63,7 @@ "utopia-php/migration": "0.8.*", "utopia-php/orchestration": "0.9.*", "utopia-php/platform": "0.7.*", - "utopia-php/pools": "0.7.*", + "utopia-php/pools": "0.8.*", "utopia-php/preloader": "0.2.*", "utopia-php/queue": "0.9.*", "utopia-php/registry": "0.5.*", diff --git a/composer.lock b/composer.lock index eb29c765a0..43fb23d14f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5b3c46863e4571c838c30090ad96436c", + "content-hash": "0f7e98ef962fcce12b0ea435188504aa", "packages": [ { "name": "adhocore/jwt", @@ -3497,16 +3497,16 @@ }, { "name": "utopia-php/database", - "version": "0.63.1", + "version": "dev-fix-tenant", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "ad191bf34151815f716f553796a363ff2b6ef7d3" + "reference": "1b7d8b774ae28d98de82a86e4fef262514e8fc40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/ad191bf34151815f716f553796a363ff2b6ef7d3", - "reference": "ad191bf34151815f716f553796a363ff2b6ef7d3", + "url": "https://api.github.com/repos/utopia-php/database/zipball/1b7d8b774ae28d98de82a86e4fef262514e8fc40", + "reference": "1b7d8b774ae28d98de82a86e4fef262514e8fc40", "shasum": "" }, "require": { @@ -3514,7 +3514,8 @@ "ext-pdo": "*", "php": ">=8.1", "utopia-php/cache": "0.12.*", - "utopia-php/framework": "0.33.*" + "utopia-php/framework": "0.33.*", + "utopia-php/pools": "0.8.*" }, "require-dev": { "fakerphp/faker": "1.23.*", @@ -3546,9 +3547,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.63.1" + "source": "https://github.com/utopia-php/database/tree/fix-tenant" }, - "time": "2025-03-27T04:58:07+00:00" + "time": "2025-04-02T00:00:03+00:00" }, { "name": "utopia-php/domains", @@ -3950,16 +3951,16 @@ }, { "name": "utopia-php/migration", - "version": "0.8.3", + "version": "0.8.4", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "85c2e14647b240b75be6b6b762e5b30e48fb8d8a" + "reference": "845fd04ccf5e0edb03c184b864e0596080a432b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/85c2e14647b240b75be6b6b762e5b30e48fb8d8a", - "reference": "85c2e14647b240b75be6b6b762e5b30e48fb8d8a", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/845fd04ccf5e0edb03c184b864e0596080a432b8", + "reference": "845fd04ccf5e0edb03c184b864e0596080a432b8", "shasum": "" }, "require": { @@ -3967,7 +3968,7 @@ "ext-curl": "*", "ext-openssl": "*", "php": ">=8.1", - "utopia-php/database": "0.63.*", + "utopia-php/database": "0.*.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*", "utopia-php/storage": "0.18.*" @@ -4000,9 +4001,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/0.8.3" + "source": "https://github.com/utopia-php/migration/tree/0.8.4" }, - "time": "2025-03-26T10:45:51+00:00" + "time": "2025-03-28T02:08:22+00:00" }, { "name": "utopia-php/orchestration", @@ -4106,16 +4107,16 @@ }, { "name": "utopia-php/pools", - "version": "0.7.0", + "version": "0.8.0", "source": { "type": "git", "url": "https://github.com/utopia-php/pools.git", - "reference": "ad64d45afda08ec8b29e2642a8d18075964d40bf" + "reference": "60733929dc328e7ea47e800579c8bbf0d49df5ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/pools/zipball/ad64d45afda08ec8b29e2642a8d18075964d40bf", - "reference": "ad64d45afda08ec8b29e2642a8d18075964d40bf", + "url": "https://api.github.com/repos/utopia-php/pools/zipball/60733929dc328e7ea47e800579c8bbf0d49df5ba", + "reference": "60733929dc328e7ea47e800579c8bbf0d49df5ba", "shasum": "" }, "require": { @@ -4152,9 +4153,9 @@ ], "support": { "issues": "https://github.com/utopia-php/pools/issues", - "source": "https://github.com/utopia-php/pools/tree/0.7.0" + "source": "https://github.com/utopia-php/pools/tree/0.8.0" }, - "time": "2025-03-18T03:55:33+00:00" + "time": "2025-03-19T10:22:03+00:00" }, { "name": "utopia-php/preloader", @@ -4775,16 +4776,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.40.10", + "version": "0.40.11", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "054ac96285caf4f77879087b2416a5ddb8263051" + "reference": "0ec5f4a60c15e33e208bc3444ba6148b1d0f0027" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/054ac96285caf4f77879087b2416a5ddb8263051", - "reference": "054ac96285caf4f77879087b2416a5ddb8263051", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/0ec5f4a60c15e33e208bc3444ba6148b1d0f0027", + "reference": "0ec5f4a60c15e33e208bc3444ba6148b1d0f0027", "shasum": "" }, "require": { @@ -4820,9 +4821,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/0.40.10" + "source": "https://github.com/appwrite/sdk-generator/tree/0.40.11" }, - "time": "2025-03-25T13:44:16+00:00" + "time": "2025-03-26T10:53:16+00:00" }, { "name": "doctrine/annotations", @@ -7154,16 +7155,16 @@ }, { "name": "symfony/console", - "version": "v7.2.1", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" + "reference": "e51498ea18570c062e7df29d05a7003585b19b88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "url": "https://api.github.com/repos/symfony/console/zipball/e51498ea18570c062e7df29d05a7003585b19b88", + "reference": "e51498ea18570c062e7df29d05a7003585b19b88", "shasum": "" }, "require": { @@ -7227,7 +7228,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.1" + "source": "https://github.com/symfony/console/tree/v7.2.5" }, "funding": [ { @@ -7243,7 +7244,7 @@ "type": "tidelift" } ], - "time": "2024-12-11T03:49:26+00:00" + "time": "2025-03-12T08:11:12+00:00" }, { "name": "symfony/filesystem", @@ -7758,16 +7759,16 @@ }, { "name": "symfony/process", - "version": "v7.2.4", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf" + "reference": "87b7c93e57df9d8e39a093d32587702380ff045d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", - "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", + "url": "https://api.github.com/repos/symfony/process/zipball/87b7c93e57df9d8e39a093d32587702380ff045d", + "reference": "87b7c93e57df9d8e39a093d32587702380ff045d", "shasum": "" }, "require": { @@ -7799,7 +7800,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.2.4" + "source": "https://github.com/symfony/process/tree/v7.2.5" }, "funding": [ { @@ -7815,7 +7816,7 @@ "type": "tidelift" } ], - "time": "2025-02-05T08:33:46+00:00" + "time": "2025-03-13T12:21:46+00:00" }, { "name": "symfony/string", @@ -8132,9 +8133,18 @@ "time": "2024-03-07T20:33:40+00:00" } ], - "aliases": [], + "aliases": [ + { + "package": "utopia-php/database", + "version": "dev-fix-tenant", + "alias": "0.64.0", + "alias_normalized": "0.64.0.0" + } + ], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": { + "utopia-php/database": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { From 32a5ff4c8c5e42c9ad0652f20cb91764089e31e8 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 2 Apr 2025 13:37:26 +1300 Subject: [PATCH 52/80] Update release --- composer.json | 2 +- composer.lock | 27 +++++++++------------------ 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/composer.json b/composer.json index 0eb0f58f04..3920351c06 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "utopia-php/cache": "0.12.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "dev-fix-tenant as 0.64.0", + "utopia-php/database": "0.64.*", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", diff --git a/composer.lock b/composer.lock index 80d9c144f5..270fbca6a4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d9bc106a753e57907134bb717d486310", + "content-hash": "6a54c8bc4f9f14cd3883f55880864630", "packages": [ { "name": "adhocore/jwt", @@ -3497,16 +3497,16 @@ }, { "name": "utopia-php/database", - "version": "dev-fix-tenant", + "version": "0.64.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "1b7d8b774ae28d98de82a86e4fef262514e8fc40" + "reference": "6530a8a6d3c1fe92d0f9a92f0f05eda698d92e0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/1b7d8b774ae28d98de82a86e4fef262514e8fc40", - "reference": "1b7d8b774ae28d98de82a86e4fef262514e8fc40", + "url": "https://api.github.com/repos/utopia-php/database/zipball/6530a8a6d3c1fe92d0f9a92f0f05eda698d92e0b", + "reference": "6530a8a6d3c1fe92d0f9a92f0f05eda698d92e0b", "shasum": "" }, "require": { @@ -3547,9 +3547,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/fix-tenant" + "source": "https://github.com/utopia-php/database/tree/0.64.1" }, - "time": "2025-04-02T00:00:03+00:00" + "time": "2025-04-02T00:35:29+00:00" }, { "name": "utopia-php/domains", @@ -8124,18 +8124,9 @@ "time": "2024-03-07T20:33:40+00:00" } ], - "aliases": [ - { - "package": "utopia-php/database", - "version": "dev-fix-tenant", - "alias": "0.64.0", - "alias_normalized": "0.64.0.0" - } - ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "utopia-php/database": 20 - }, + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { From 1660c0b00c15c216d1f422719217893fc92349cd Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 3 Apr 2025 02:44:29 +0000 Subject: [PATCH 53/80] Fix: image transformation cache --- app/controllers/shared/api.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 1015400a12..87382bbfe2 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -390,7 +390,8 @@ App::init() ->inject('timelimit') ->inject('mode') ->inject('apiKey') - ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Database $dbForProject, callable $timelimit, string $mode, ?Key $apiKey) use ($usageDatabaseListener, $eventDatabaseListener) { + ->inject('plan') + ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Database $dbForProject, callable $timelimit, string $mode, ?Key $apiKey, array $plan) use ($usageDatabaseListener, $eventDatabaseListener) { $route = $utopia->getRoute(); @@ -520,6 +521,10 @@ App::init() $useCache = $route->getLabel('cache', false); if ($useCache) { + $route = $utopia->match($request); + $isImageTransformation = $route->getPath() === '/v1/storage/buckets/:bucketId/files/:fileId/preview'; + $isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && !Auth::isPrivilegedUser(Authorization::getRoles()); + $key = md5($request->getURI() . '*' . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER); $cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key)); $cache = new Cache( @@ -532,7 +537,7 @@ App::init() $parts = explode('/', $cacheLog->getAttribute('resourceType')); $type = $parts[0] ?? null; - if ($type === 'bucket') { + if ($type === 'bucket' && (!$isImageTransformation || !$isDisabled)) { $bucketId = $parts[1] ?? null; $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); @@ -573,8 +578,10 @@ App::init() $response ->addHeader('Cache-Control', sprintf('private, max-age=%d', $timestamp)) ->addHeader('X-Appwrite-Cache', 'hit') - ->setContentType($cacheLog->getAttribute('mimeType')) - ->send($data); + ->setContentType($cacheLog->getAttribute('mimeType')); + if (!$isImageTransformation || !$isDisabled) { + $response->send($data); + } } else { $response ->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate') From bd87672607cbeb8c3152e8f3878d059bafb1daa9 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 3 Apr 2025 18:31:50 +0300 Subject: [PATCH 54/80] Remove audit payload --- src/Appwrite/Platform/Workers/Audits.php | 6 +++++- src/Appwrite/Platform/Workers/Databases.php | 3 ++- src/Appwrite/Platform/Workers/Deletes.php | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Audits.php b/src/Appwrite/Platform/Workers/Audits.php index ed5ff8010a..76309145b8 100644 --- a/src/Appwrite/Platform/Workers/Audits.php +++ b/src/Appwrite/Platform/Workers/Audits.php @@ -74,7 +74,11 @@ class Audits extends Action Console::info('Aggregating audit logs'); $event = $payload['event'] ?? ''; - $auditPayload = $payload['payload'] ?? ''; + + $auditPayload = ''; + if ($project->getId() === 'console') { + $auditPayload = $payload['payload'] ?? ''; + } $mode = $payload['mode'] ?? ''; $resource = $payload['resource'] ?? ''; $userAgent = $payload['userAgent'] ?? ''; diff --git a/src/Appwrite/Platform/Workers/Databases.php b/src/Appwrite/Platform/Workers/Databases.php index 4abd035599..44738b557c 100644 --- a/src/Appwrite/Platform/Workers/Databases.php +++ b/src/Appwrite/Platform/Workers/Databases.php @@ -565,7 +565,8 @@ class Databases extends Action try { $documents = $database->deleteDocuments($collectionId, $queries); } catch (\Throwable $th) { - Console::error('Failed to delete documents for collection ' . $collectionId . ': ' . $th->getMessage()); + $tenant = $database->getSharedTables() ? 'Tenant:'.$database->getTenant() : ''; + Console::error("Failed to delete documents for collection:{$database->getNamespace()}_{$collectionId} {$tenant} :{$th->getMessage()}"); return; } diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index a9b83976a4..09c60c7740 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -1050,7 +1050,8 @@ class Deletes extends Action try { $documents = $database->deleteDocuments($collection, $queries); } catch (Throwable $th) { - Console::error('Failed to delete documents for collection ' . $collection . ': ' . $th->getMessage()); + $tenant = $database->getSharedTables() ? 'Tenant:'.$database->getTenant() : ''; + Console::error("Failed to delete documents for collection:{$database->getNamespace()}_{$collection} {$tenant} :{$th->getMessage()}"); return; } From 2a672f5a633b98f1bcecc73d3addbfc78eecc4b5 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sat, 5 Apr 2025 19:20:24 +0530 Subject: [PATCH 55/80] fix: don't allow the only owner in an org to change their role to anything else. --- app/controllers/api/teams.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index dfdccf5e99..d4db6a4a52 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -1034,7 +1034,6 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') ->param('membershipId', '', new UID(), 'Membership ID.') ->param('roles', [], function (Document $project) { if ($project->getId() === 'console') { - ; $roles = array_keys(Config::getParam('roles', [])); array_filter($roles, function ($role) { return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]); @@ -1046,9 +1045,10 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') ->inject('request') ->inject('response') ->inject('user') + ->inject('project') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) { + ->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents) { $team = $dbForProject->getDocument('teams', $teamId); if ($team->isEmpty()) { @@ -1069,6 +1069,21 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') $isAppUser = Auth::isAppUser(Authorization::getRoles()); $isOwner = Authorization::isRole('team:' . $team->getId() . '/owner'); + if ($project->getId() === 'console') { + // Quick check: fetch up to 2 owners to determine if only one exists + $ownersCount = $dbForProject->count( + collection: 'memberships', + queries: [Query::contains('roles', ['owner'])], + max: 2 + ); + + // If there's only one owner, + // and the requester is that owner, prevent role change + if ($ownersCount === 1 && $isOwner) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'There must be at least one owner in the organization.'); + } + } + if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server) throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to modify roles'); } From 7297188c1bf0dd67229afc2ef30ad527cc76fecc Mon Sep 17 00:00:00 2001 From: Darshan Date: Sat, 5 Apr 2025 19:42:20 +0530 Subject: [PATCH 56/80] update: roles check. --- app/controllers/api/teams.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index d4db6a4a52..3e0e366b6b 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -1077,9 +1077,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') max: 2 ); - // If there's only one owner, - // and the requester is that owner, prevent role change - if ($ownersCount === 1 && $isOwner) { + // Prevent role change if there's only one owner left, + // the requester is that owner, and the new `$roles` no longer include 'owner'! + if ($ownersCount === 1 && $isOwner && !\in_array('owner', $roles)) { throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'There must be at least one owner in the organization.'); } } From abd953974d4dec5af095ced36dec79ab0904df5f Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Sat, 5 Apr 2025 14:43:07 +0000 Subject: [PATCH 57/80] chore: give read write permission to vendor for gitpod config --- .gitpod.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitpod.yml b/.gitpod.yml index e9c0928229..c103d81900 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,13 +1,14 @@ tasks: - name: Run Appwrite Docker Stack init: | - git config --global --add safe.directory /app git submodule update --init docker compose build docker compose pull docker pull composer command: | + sudo chown -R $(id -u):$(id -g) vendor/ docker run --rm --interactive --tty \ + --user "$(id -u):$(id -g)" \ --volume $PWD:/app \ composer install \ --ignore-platform-reqs \ From e5ef825ac41fd63fad3983115730c48a3725fd14 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Sat, 5 Apr 2025 15:47:22 +0000 Subject: [PATCH 58/80] chore: update php image to 0.8.1 --- composer.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/composer.lock b/composer.lock index 270fbca6a4..8db4706bb5 100644 --- a/composer.lock +++ b/composer.lock @@ -2965,16 +2965,16 @@ }, { "name": "tbachert/spi", - "version": "v1.0.2", + "version": "v1.0.3", "source": { "type": "git", "url": "https://github.com/Nevay/spi.git", - "reference": "2ddfaf815dafb45791a61b08170de8d583c16062" + "reference": "506a79c98e1a51522e76ee921ccb6c62d52faf3a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Nevay/spi/zipball/2ddfaf815dafb45791a61b08170de8d583c16062", - "reference": "2ddfaf815dafb45791a61b08170de8d583c16062", + "url": "https://api.github.com/repos/Nevay/spi/zipball/506a79c98e1a51522e76ee921ccb6c62d52faf3a", + "reference": "506a79c98e1a51522e76ee921ccb6c62d52faf3a", "shasum": "" }, "require": { @@ -3011,9 +3011,9 @@ ], "support": { "issues": "https://github.com/Nevay/spi/issues", - "source": "https://github.com/Nevay/spi/tree/v1.0.2" + "source": "https://github.com/Nevay/spi/tree/v1.0.3" }, - "time": "2024-10-04T16:36:12+00:00" + "time": "2025-04-02T19:38:14+00:00" }, { "name": "thecodingmachine/safe", @@ -3746,16 +3746,16 @@ }, { "name": "utopia-php/image", - "version": "0.8.0", + "version": "0.8.1", "source": { "type": "git", "url": "https://github.com/utopia-php/image.git", - "reference": "dcae5b1c6deb3ff6865f4e68f012b3709c289bca" + "reference": "e8cc7dd14f423270a1b7570ec0dae88a66195b63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/image/zipball/dcae5b1c6deb3ff6865f4e68f012b3709c289bca", - "reference": "dcae5b1c6deb3ff6865f4e68f012b3709c289bca", + "url": "https://api.github.com/repos/utopia-php/image/zipball/e8cc7dd14f423270a1b7570ec0dae88a66195b63", + "reference": "e8cc7dd14f423270a1b7570ec0dae88a66195b63", "shasum": "" }, "require": { @@ -3789,9 +3789,9 @@ ], "support": { "issues": "https://github.com/utopia-php/image/issues", - "source": "https://github.com/utopia-php/image/tree/0.8.0" + "source": "https://github.com/utopia-php/image/tree/0.8.1" }, - "time": "2025-02-20T11:49:03+00:00" + "time": "2025-04-04T18:55:20+00:00" }, { "name": "utopia-php/locale", From 403083b0cef7053588474b98587b036790fdc01c Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Sun, 6 Apr 2025 08:31:46 +0530 Subject: [PATCH 59/80] Update .gitpod.yml --- .gitpod.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitpod.yml b/.gitpod.yml index c103d81900..78375bb1f0 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -6,7 +6,6 @@ tasks: docker compose pull docker pull composer command: | - sudo chown -R $(id -u):$(id -g) vendor/ docker run --rm --interactive --tty \ --user "$(id -u):$(id -g)" \ --volume $PWD:/app \ From c3c568653fd9f4a5693e5b06a8c4d6c342e30fc6 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 6 Apr 2025 10:21:55 +0530 Subject: [PATCH 60/80] add: roles check test. --- tests/e2e/Services/Teams/TeamsBase.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/e2e/Services/Teams/TeamsBase.php b/tests/e2e/Services/Teams/TeamsBase.php index 2328e4cdbf..87486696d2 100644 --- a/tests/e2e/Services/Teams/TeamsBase.php +++ b/tests/e2e/Services/Teams/TeamsBase.php @@ -37,6 +37,31 @@ trait TeamsBase $teamUid = $response1['body']['$id']; $teamName = $response1['body']['name']; + /** + * Test: Attempt to downgrade the only OWNER in a team (should fail) + */ + // Step 1: Fetch all team memberships — only one exists at this point + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + // Step 2: Extract the membership ID of the only member (also the only OWNER) + $membershipID = $response['body']['memberships'][0]['$id']; + + // Step 3: Attempt to downgrade the member's role to 'developer' + $response = $this->client->call(Client::METHOD_PATCH, '/teams/' . $teamUid . '/memberships/' . $membershipID, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'roles' => ['developer'] + ]); + + // Step 4: Assert failure — cannot remove the only OWNER from a team + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertEquals('general_argument_invalid', $response['body']['type']); + $this->assertEquals('There must be at least one owner in the organization.', $response['body']['message']); + $teamId = ID::unique(); $response2 = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ 'content-type' => 'application/json', From 2243f0c72e45e5a8abfe716bb2b8470375a07f6b Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 6 Apr 2025 10:29:18 +0530 Subject: [PATCH 61/80] fix: test for only console db <> orgs. --- tests/e2e/Services/Teams/TeamsBase.php | 44 +++++++++++++++----------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/tests/e2e/Services/Teams/TeamsBase.php b/tests/e2e/Services/Teams/TeamsBase.php index 87486696d2..80ac1621ee 100644 --- a/tests/e2e/Services/Teams/TeamsBase.php +++ b/tests/e2e/Services/Teams/TeamsBase.php @@ -38,29 +38,35 @@ trait TeamsBase $teamName = $response1['body']['name']; /** - * Test: Attempt to downgrade the only OWNER in a team (should fail) + * Test: Attempt to downgrade the only OWNER in an organization (should fail) */ - // Step 1: Fetch all team memberships — only one exists at this point - $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); + if ($this->getProject()['$id'] === 'console') { + // Step 1: Fetch all team memberships — only one exists at this point + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::limit(1)->toString(), + ], + ]); - // Step 2: Extract the membership ID of the only member (also the only OWNER) - $membershipID = $response['body']['memberships'][0]['$id']; + // Step 2: Extract the membership ID of the only member (also the only OWNER) + $membershipID = $response['body']['memberships'][0]['$id']; - // Step 3: Attempt to downgrade the member's role to 'developer' - $response = $this->client->call(Client::METHOD_PATCH, '/teams/' . $teamUid . '/memberships/' . $membershipID, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'roles' => ['developer'] - ]); + // Step 3: Attempt to downgrade the member's role to 'developer' + $response = $this->client->call(Client::METHOD_PATCH, '/teams/' . $teamUid . '/memberships/' . $membershipID, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'roles' => ['developer'] + ]); - // Step 4: Assert failure — cannot remove the only OWNER from a team - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertEquals('general_argument_invalid', $response['body']['type']); - $this->assertEquals('There must be at least one owner in the organization.', $response['body']['message']); + // Step 4: Assert failure — cannot remove the only OWNER from a team + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertEquals('general_argument_invalid', $response['body']['type']); + $this->assertEquals('There must be at least one owner in the organization.', $response['body']['message']); + } $teamId = ID::unique(); $response2 = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ From 954ed45a36ff821125cbfa63c75f16aebc7fb8f1 Mon Sep 17 00:00:00 2001 From: Fabian Gruber Date: Fri, 28 Feb 2025 13:52:34 +0100 Subject: [PATCH 62/80] feat: update appwrite to use generic S3 adapter --- app/init/resources.php | 15 +++++++++++++-- docker-compose.yml | 6 +++++- src/Executor/Executor.php | 6 +++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/app/init/resources.php b/app/init/resources.php index 4e53b24c06..0011a8c677 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -38,6 +38,7 @@ use Utopia\Logger\Log; use Utopia\Pools\Group; use Utopia\Queue\Publisher; use Utopia\Storage\Device; +use Utopia\Storage\Device\AWS; use Utopia\Storage\Device\Backblaze; use Utopia\Storage\Device\DOSpaces; use Utopia\Storage\Device\Linode; @@ -540,7 +541,12 @@ function getDevice(string $root, string $connection = ''): Device switch ($device) { case Storage::DEVICE_S3: - return new S3($root, $accessKey, $accessSecret, $bucket, $region, $acl, $url); + if (!empty($url)) { + return new S3($root, $accessKey, $accessSecret, $url, $region, $acl); + } else { + return new AWS($root, $accessKey, $accessSecret, $bucket, $region, $acl); + } + // no break case STORAGE::DEVICE_DO_SPACES: $device = new DOSpaces($root, $accessKey, $accessSecret, $bucket, $region, $acl); $device->setHttpVersion(S3::HTTP_VERSION_1_1); @@ -567,7 +573,12 @@ function getDevice(string $root, string $connection = ''): Device $s3Bucket = System::getEnv('_APP_STORAGE_S3_BUCKET', ''); $s3Acl = 'private'; $s3EndpointUrl = System::getEnv('_APP_STORAGE_S3_ENDPOINT', ''); - return new S3($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl, $s3EndpointUrl); + if (!empty($s3EndpointUrl)) { + return new S3($root, $s3AccessKey, $s3SecretKey, $s3EndpointUrl, $s3Region, $s3Acl); + } else { + return new AWS($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl); + } + // no break case Storage::DEVICE_DO_SPACES: $doSpacesAccessKey = System::getEnv('_APP_STORAGE_DO_SPACES_ACCESS_KEY', ''); $doSpacesSecretKey = System::getEnv('_APP_STORAGE_DO_SPACES_SECRET', ''); diff --git a/docker-compose.yml b/docker-compose.yml index 05ddba967a..8c8a364f30 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -198,6 +198,8 @@ services: - _APP_DATABASE_SHARED_TABLES_V1 - _APP_DATABASE_SHARED_NAMESPACE - _APP_FUNCTIONS_CREATION_ABUSE_LIMIT + extra_hosts: + - "host.docker.internal:host-gateway" appwrite-console: <<: *x-logging @@ -489,6 +491,8 @@ services: - _APP_STORAGE_WASABI_REGION - _APP_STORAGE_WASABI_BUCKET - _APP_DATABASE_SHARED_TABLES + extra_hosts: + - "host.docker.internal:host-gateway" appwrite-worker-certificates: entrypoint: worker-certificates @@ -1131,4 +1135,4 @@ volumes: appwrite-certificates: appwrite-functions: appwrite-builds: - appwrite-config: \ No newline at end of file + appwrite-config: diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index c230cfb664..936fba5b24 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -235,7 +235,11 @@ class Executor throw new \Exception($message, $status); } - $response['body']['headers'] = \json_decode($response['body']['headers'] ?? '{}', true); + $headers = $response['body']['headers'] ?? []; + if (is_string($headers)) { + $headers = \json_decode($headers, true); + } + $response['body']['headers'] = $headers; $response['body']['statusCode'] = \intval($response['body']['statusCode'] ?? 500); $response['body']['duration'] = \floatval($response['body']['duration'] ?? 0); $response['body']['startTime'] = \floatval($response['body']['startTime'] ?? \microtime(true)); From 0ae70f8321e1916eaad4f6afa1cd430e55f7b3b9 Mon Sep 17 00:00:00 2001 From: Fabian Gruber Date: Tue, 25 Mar 2025 12:33:03 +0100 Subject: [PATCH 63/80] feat: inject executor instead of creating a new instance --- app/cli.php | 3 ++ app/controllers/api/functions.php | 12 +++---- app/controllers/general.php | 23 +++++++------ app/init/resources.php | 3 ++ app/worker.php | 3 ++ src/Appwrite/Platform/Workers/Builds.php | 15 +++++---- src/Appwrite/Platform/Workers/Deletes.php | 26 ++++++++------- src/Appwrite/Platform/Workers/Functions.php | 11 ++++-- src/Executor/Executor.php | 37 +++++++++++++-------- 9 files changed, 82 insertions(+), 51 deletions(-) diff --git a/app/cli.php b/app/cli.php index f080217365..82f229673e 100644 --- a/app/cli.php +++ b/app/cli.php @@ -9,6 +9,7 @@ use Appwrite\Event\StatsResources; use Appwrite\Event\StatsUsage; use Appwrite\Platform\Appwrite; use Appwrite\Runtimes\Runtimes; +use Executor\Executor; use Utopia\Cache\Adapter\Sharding; use Utopia\Cache\Cache; use Utopia\CLI\CLI; @@ -255,6 +256,8 @@ CLI::setResource('logError', function (Registry $register) { }; }, ['register']); +CLI::setResource('executor', fn () => new Executor(fn (string $projectId, string $deploymentId) => System::getEnv('_APP_EXECUTOR_HOST'))); + $platform = new Appwrite(); $platform->init(Service::TYPE_TASK); diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 37932e4165..c7f24f984a 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -869,7 +869,8 @@ App::put('/v1/functions/:functionId') ->inject('queueForBuilds') ->inject('dbForPlatform') ->inject('gitHub') - ->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, ?string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForPlatform, GitHub $github) use ($redeployVcs) { + ->inject('executor') + ->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, ?string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForPlatform, GitHub $github, Executor $executor) use ($redeployVcs) { // TODO: If only branch changes, re-deploy $function = $dbForProject->getDocument('functions', $functionId); @@ -972,7 +973,6 @@ App::put('/v1/functions/:functionId') // Enforce Cold Start if spec limits change. if ($function->getAttribute('specification') !== $specification && !empty($function->getAttribute('deployment'))) { - $executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST')); try { $executor->deleteRuntime($project->getId(), $function->getAttribute('deployment')); } catch (\Throwable $th) { @@ -1779,7 +1779,8 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId/build') ->inject('dbForProject') ->inject('project') ->inject('queueForEvents') - ->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Document $project, Event $queueForEvents) { + ->inject('executor') + ->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Executor $executor) { $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { @@ -1834,7 +1835,6 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId/build') $dbForProject->purgeCachedDocument('deployments', $deployment->getId()); try { - $executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST')); $executor->deleteRuntime($project->getId(), $deploymentId . "-build"); } catch (\Throwable $th) { // Don't throw if the deployment doesn't exist @@ -1886,8 +1886,9 @@ App::post('/v1/functions/:functionId/executions') ->inject('queueForEvents') ->inject('queueForStatsUsage') ->inject('queueForFunctions') + ->inject('executor') ->inject('geodb') - ->action(function (string $functionId, string $body, mixed $async, string $path, string $method, mixed $headers, ?string $scheduledAt, Response $response, Request $request, Document $project, Database $dbForProject, Database $dbForPlatform, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Reader $geodb) { + ->action(function (string $functionId, string $body, mixed $async, string $path, string $method, mixed $headers, ?string $scheduledAt, Response $response, Request $request, Document $project, Database $dbForProject, Database $dbForPlatform, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb) { $async = \strval($async) === 'true' || \strval($async) === '1'; if (!$async && !is_null($scheduledAt)) { @@ -2160,7 +2161,6 @@ App::post('/v1/functions/:functionId/executions') ]); /** Execute function */ - $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST')); try { $version = $function->getAttribute('version', 'v2'); $command = $runtime['startCommand']; diff --git a/app/controllers/general.php b/app/controllers/general.php index 1423763cec..6c1167cf76 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -50,7 +50,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, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname) { $utopia->getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml'); @@ -339,7 +339,6 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw ]); /** Execute function */ - $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST')); try { $version = $function->getAttribute('version', 'v2'); $command = $runtime['startCommand']; @@ -503,9 +502,10 @@ App::init() ->inject('queueForEvents') ->inject('queueForCertificates') ->inject('queueForFunctions') + ->inject('executor') ->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) { + ->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, Executor $executor, callable $isResourceBlocked, string $previewHostname) { /* * Appwrite Router */ @@ -513,7 +513,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, $executor, $geodb, $isResourceBlocked, $previewHostname)) { return; } } @@ -742,11 +742,12 @@ App::options() ->inject('queueForEvents') ->inject('queueForStatsUsage') ->inject('queueForFunctions') + ->inject('executor') ->inject('geodb') ->inject('isResourceBlocked') ->inject('previewHostname') ->inject('project') - ->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, Document $project) { + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, Document $project) { /* * Appwrite Router */ @@ -754,7 +755,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, $executor, $geodb, $isResourceBlocked, $previewHostname)) { return; } } @@ -1062,10 +1063,11 @@ App::get('/robots.txt') ->inject('queueForEvents') ->inject('queueForStatsUsage') ->inject('queueForFunctions') + ->inject('executor') ->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) { + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname) { $host = $request->getHostname() ?? ''; $mainDomain = System::getEnv('_APP_DOMAIN', ''); @@ -1073,7 +1075,7 @@ App::get('/robots.txt') $template = new View(__DIR__ . '/../views/general/robots.phtml'); $response->text($template->render(false)); } else { - router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname); + router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname); } }); @@ -1090,10 +1092,11 @@ App::get('/humans.txt') ->inject('queueForEvents') ->inject('queueForStatsUsage') ->inject('queueForFunctions') + ->inject('executor') ->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) { + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname) { $host = $request->getHostname() ?? ''; $mainDomain = System::getEnv('_APP_DOMAIN', ''); @@ -1101,7 +1104,7 @@ App::get('/humans.txt') $template = new View(__DIR__ . '/../views/general/humans.phtml'); $response->text($template->render(false)); } else { - router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $geodb, $isResourceBlocked, $previewHostname); + router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname); } }); diff --git a/app/init/resources.php b/app/init/resources.php index 0011a8c677..2360179913 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -21,6 +21,7 @@ use Appwrite\Extend\Exception; use Appwrite\GraphQL\Schema; use Appwrite\Network\Validator\Origin; use Appwrite\Utopia\Request; +use Executor\Executor; use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis; use Utopia\App; use Utopia\Cache\Adapter\Sharding; @@ -833,3 +834,5 @@ App::setResource('apiKey', function (Request $request, Document $project): ?Key return Key::decode($project, $key); }, ['request', 'project']); + +App::setResource('executor', fn () => new Executor(fn (string $projectId, string $deploymentId) => System::getEnv('_APP_EXECUTOR_HOST'))); diff --git a/app/worker.php b/app/worker.php index 90496c0430..6a51ee55be 100644 --- a/app/worker.php +++ b/app/worker.php @@ -18,6 +18,7 @@ use Appwrite\Event\StatsUsage; use Appwrite\Event\StatsUsageDump; use Appwrite\Event\Webhook; use Appwrite\Platform\Appwrite; +use Executor\Executor; use Swoole\Runtime; use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis; use Utopia\Cache\Adapter\Sharding; @@ -413,6 +414,8 @@ Server::setResource('logError', function (Registry $register, Document $project) }; }, ['register', 'project']); +Server::setResource('executor', fn () => new Executor(fn (string $projectId, string $deploymentId) => System::getEnv('_APP_EXECUTOR_HOST'))); + $pools = $register->get('pools'); $platform = new Appwrite(); $args = $platform->getEnv('argv'); diff --git a/src/Appwrite/Platform/Workers/Builds.php b/src/Appwrite/Platform/Workers/Builds.php index 6f26e9a80c..4057d4b190 100644 --- a/src/Appwrite/Platform/Workers/Builds.php +++ b/src/Appwrite/Platform/Workers/Builds.php @@ -59,8 +59,9 @@ class Builds extends Action ->inject('deviceForFunctions') ->inject('isResourceBlocked') ->inject('log') - ->callback(fn ($message, Document $project, Database $dbForPlatform, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, StatsUsage $usage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log) => - $this->action($message, $project, $dbForPlatform, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $usage, $cache, $dbForProject, $deviceForFunctions, $isResourceBlocked, $log)); + ->inject('executor') + ->callback(fn ($message, Document $project, Database $dbForPlatform, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, StatsUsage $usage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log, Executor $executor) => + $this->action($message, $project, $dbForPlatform, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $usage, $cache, $dbForProject, $deviceForFunctions, $isResourceBlocked, $log, $executor)); } /** @@ -76,10 +77,11 @@ class Builds extends Action * @param Database $dbForProject * @param Device $deviceForFunctions * @param Log $log + * @param Executor $executor * @return void * @throws \Utopia\Database\Exception */ - public function action(Message $message, Document $project, Database $dbForPlatform, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, StatsUsage $queueForStatsUsage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log): void + public function action(Message $message, Document $project, Database $dbForPlatform, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, StatsUsage $queueForStatsUsage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log, Executor $executor): void { $payload = $message->getPayload() ?? []; @@ -100,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, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $queueForEvents, $queueForStatsUsage, $dbForPlatform, $dbForProject, $github, $project, $resource, $deployment, $template, $isResourceBlocked, $log); + $this->buildDeployment($deviceForFunctions, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $queueForEvents, $queueForStatsUsage, $dbForPlatform, $dbForProject, $github, $project, $resource, $deployment, $template, $isResourceBlocked, $log, $executor); break; default: @@ -123,14 +125,13 @@ class Builds extends Action * @param Document $deployment * @param Document $template * @param Log $log + * @param Executor $executor * @return void * @throws \Utopia\Database\Exception * @throws Exception */ - protected function buildDeployment(Device $deviceForFunctions, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Event $queueForEvents, StatsUsage $queueForStatsUsage, Database $dbForPlatform, Database $dbForProject, GitHub $github, Document $project, Document $function, Document $deployment, Document $template, callable $isResourceBlocked, Log $log): void + protected function buildDeployment(Device $deviceForFunctions, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Event $queueForEvents, StatsUsage $queueForStatsUsage, Database $dbForPlatform, Database $dbForProject, GitHub $github, Document $project, Document $function, Document $deployment, Document $template, callable $isResourceBlocked, Log $log, Executor $executor): void { - $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST')); - $functionId = $function->getId(); $log->addTag('functionId', $function->getId()); diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 09c60c7740..5092599d67 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -55,12 +55,13 @@ class Deletes extends Action ->inject('deviceForBuilds') ->inject('deviceForCache') ->inject('certificates') + ->inject('executor') ->inject('executionRetention') ->inject('auditRetention') ->inject('log') ->callback( - fn ($message, Document $project, Database $dbForPlatform, callable $getProjectDB, callable $getLogsDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, string $executionRetention, string $auditRetention, Log $log) => - $this->action($message, $project, $dbForPlatform, $getProjectDB, $getLogsDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $executionRetention, $auditRetention, $log) + fn ($message, Document $project, Database $dbForPlatform, callable $getProjectDB, callable $getLogsDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, Executor $executor, string $executionRetention, string $auditRetention, Log $log) => + $this->action($message, $project, $dbForPlatform, $getProjectDB, $getLogsDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $executor, $executionRetention, $auditRetention, $log) ); } @@ -68,7 +69,7 @@ class Deletes extends Action * @throws Exception * @throws Throwable */ - public function action(Message $message, Document $project, Database $dbForPlatform, callable $getProjectDB, callable $getLogsDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, string $executionRetention, string $auditRetention, Log $log): void + public function action(Message $message, Document $project, Database $dbForPlatform, callable $getProjectDB, callable $getLogsDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, Executor $executor, string $executionRetention, string $auditRetention, Log $log): void { $payload = $message->getPayload() ?? []; @@ -93,10 +94,10 @@ class Deletes extends Action $this->deleteProject($dbForPlatform, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $document); break; case DELETE_TYPE_FUNCTIONS: - $this->deleteFunction($dbForPlatform, $getProjectDB, $deviceForFunctions, $deviceForBuilds, $certificates, $document, $project); + $this->deleteFunction($dbForPlatform, $getProjectDB, $deviceForFunctions, $deviceForBuilds, $certificates, $document, $project, $executor); break; case DELETE_TYPE_DEPLOYMENTS: - $this->deleteDeployment($getProjectDB, $deviceForFunctions, $deviceForBuilds, $document, $project); + $this->deleteDeployment($getProjectDB, $deviceForFunctions, $deviceForBuilds, $document, $project, $executor); break; case DELETE_TYPE_USERS: $this->deleteUser($getProjectDB, $document, $project); @@ -827,10 +828,11 @@ class Deletes extends Action * @param Device $deviceForBuilds * @param Document $document function document * @param Document $project + * @param Executor $executor * @return void * @throws Exception */ - private function deleteFunction(Database $dbForPlatform, callable $getProjectDB, Device $deviceForFunctions, Device $deviceForBuilds, CertificatesAdapter $certificates, Document $document, Document $project): void + private function deleteFunction(Database $dbForPlatform, callable $getProjectDB, Device $deviceForFunctions, Device $deviceForBuilds, CertificatesAdapter $certificates, Document $document, Document $project, Executor $executor): void { $projectId = $project->getId(); $dbForProject = $getProjectDB($project); @@ -922,7 +924,7 @@ class Deletes extends Action * Request executor to delete all deployment containers */ Console::info("Requesting executor to delete all deployment containers for function " . $functionId); - $this->deleteRuntimes($getProjectDB, $document, $project); + $this->deleteRuntimes($getProjectDB, $document, $project, $executor); } /** @@ -993,10 +995,11 @@ class Deletes extends Action * @param Device $deviceForBuilds * @param Document $document * @param Document $project + * @param Executor $executor * @return void * @throws Exception */ - private function deleteDeployment(callable $getProjectDB, Device $deviceForFunctions, Device $deviceForBuilds, Document $document, Document $project): void + private function deleteDeployment(callable $getProjectDB, Device $deviceForFunctions, Device $deviceForBuilds, Document $document, Document $project, Executor $executor): void { $projectId = $project->getId(); $dbForProject = $getProjectDB($project); @@ -1024,7 +1027,7 @@ class Deletes extends Action * Request executor to delete all deployment containers */ Console::info("Requesting executor to delete deployment container for deployment " . $deploymentId); - $this->deleteRuntimes($getProjectDB, $document, $project); + $this->deleteRuntimes($getProjectDB, $document, $project, $executor); } /** @@ -1180,13 +1183,12 @@ class Deletes extends Action * @param callable $getProjectDB * @param ?Document $function * @param Document $project + * @param Executor $executor * @return void * @throws Exception */ - private function deleteRuntimes(callable $getProjectDB, ?Document $function, Document $project): void + private function deleteRuntimes(callable $getProjectDB, ?Document $function, Document $project, Executor $executor): void { - $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST')); - $deleteByFunction = function (Document $function) use ($getProjectDB, $project, $executor) { $this->listByGroup( 'deployments', diff --git a/src/Appwrite/Platform/Workers/Functions.php b/src/Appwrite/Platform/Workers/Functions.php index a7caa3207f..4e1794c085 100644 --- a/src/Appwrite/Platform/Workers/Functions.php +++ b/src/Appwrite/Platform/Workers/Functions.php @@ -51,11 +51,12 @@ class Functions extends Action ->inject('queueForEvents') ->inject('queueForStatsUsage') ->inject('log') + ->inject('executor') ->inject('isResourceBlocked') - ->callback(fn (Document $project, Message $message, Database $dbForProject, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Event $queueForEvents, StatsUsage $queueForStatsUsage, Log $log, callable $isResourceBlocked) => $this->action($project, $message, $dbForProject, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $queueForEvents, $queueForStatsUsage, $log, $isResourceBlocked)); + ->callback(fn (Document $project, Message $message, Database $dbForProject, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Event $queueForEvents, StatsUsage $queueForStatsUsage, Log $log, Executor $executor, callable $isResourceBlocked) => $this->action($project, $message, $dbForProject, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $queueForEvents, $queueForStatsUsage, $log, $executor, $isResourceBlocked)); } - public function action(Document $project, Message $message, Database $dbForProject, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Event $queueForEvents, StatsUsage $queueForStatsUsage, Log $log, callable $isResourceBlocked): void + public function action(Document $project, Message $message, Database $dbForProject, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Event $queueForEvents, StatsUsage $queueForStatsUsage, Log $log, Executor $executor, callable $isResourceBlocked): void { $payload = $message->getPayload() ?? []; @@ -146,6 +147,7 @@ class Functions extends Action queueForEvents: $queueForEvents, project: $project, function: $function, + executor: $executor, trigger: 'event', path: '/', method: 'POST', @@ -188,6 +190,7 @@ class Functions extends Action queueForEvents: $queueForEvents, project: $project, function: $function, + executor: $executor, trigger: 'http', path: $path, method: $method, @@ -212,6 +215,7 @@ class Functions extends Action queueForEvents: $queueForEvents, project: $project, function: $function, + executor: $executor, trigger: 'schedule', path: $path, method: $method, @@ -298,6 +302,7 @@ class Functions extends Action * @param Event $queueForEvents * @param Document $project * @param Document $function + * @param Executor $executor * @param string $trigger * @param string $path * @param string $method @@ -324,6 +329,7 @@ class Functions extends Action Event $queueForEvents, Document $project, Document $function, + Executor $executor, string $trigger, string $path, string $method, @@ -514,7 +520,6 @@ class Functions extends Action try { $version = $function->getAttribute('version', 'v2'); $command = $runtime['startCommand']; - $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST')); $command = $version === 'v2' ? '' : 'cp /tmp/code.tar.gz /mnt/code/code.tar.gz && nohup helpers/start.sh "' . $command . '"'; $executionResponse = $executor->createExecution( projectId: $project->getId(), diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index 936fba5b24..6bc2fe7aab 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -21,17 +21,19 @@ class Executor private bool $selfSigned = false; - private string $endpoint; + /** + * @var callable(string, string): string $endpoint + */ + private $endpointSelector; protected array $headers; - public function __construct(string $endpoint) + /** + * @param callable(string, string): string $endpointSelector + */ + public function __construct(callable $endpointSelector) { - if (!filter_var($endpoint, FILTER_VALIDATE_URL)) { - throw new Exception('Unsupported endpoint'); - } - - $this->endpoint = $endpoint; + $this->endpointSelector = $endpointSelector; $this->headers = [ 'content-type' => 'application/json', 'authorization' => 'Bearer ' . System::getEnv('_APP_EXECUTOR_SECRET', ''), @@ -92,7 +94,8 @@ class Executor 'timeout' => $timeout, ]; - $response = $this->call(self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout); + $endpoint = $this->selectEndpoint($projectId, $deploymentId); + $response = $this->call($endpoint, self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout); $status = $response['headers']['status-code']; if ($status >= 400) { @@ -123,7 +126,8 @@ class Executor 'timeout' => $timeout ]; - $this->call(self::METHOD_GET, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout, $callback); + $endpoint = $this->selectEndpoint($projectId, $deploymentId); + $this->call($endpoint, self::METHOD_GET, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout, $callback); } /** @@ -139,7 +143,8 @@ class Executor $runtimeId = "$projectId-$deploymentId"; $route = "/runtimes/$runtimeId"; - $response = $this->call(self::METHOD_DELETE, $route, [ + $endpoint = $this->selectEndpoint($projectId, $deploymentId); + $response = $this->call($endpoint, self::METHOD_DELETE, $route, [ 'x-opr-addressing-method' => 'broadcast' ], [], true, 30); @@ -227,7 +232,8 @@ class Executor $requestTimeout = $timeout + 15; } - $response = $this->call(self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId, 'content-type' => 'multipart/form-data', 'accept' => 'multipart/form-data' ], $params, true, $requestTimeout); + $endpoint = $this->selectEndpoint($projectId, $deploymentId); + $response = $this->call($endpoint, self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId, 'content-type' => 'multipart/form-data', 'accept' => 'multipart/form-data' ], $params, true, $requestTimeout); $status = $response['headers']['status-code']; if ($status >= 400) { @@ -260,10 +266,10 @@ class Executor * @return array|string * @throws Exception */ - public function call(string $method, string $path = '', array $headers = [], array $params = [], bool $decode = true, int $timeout = 15, callable $callback = null) + private function call(string $endpoint, string $method, string $path = '', array $headers = [], array $params = [], bool $decode = true, int $timeout = 15, callable $callback = null) { $headers = array_merge($this->headers, $headers); - $ch = curl_init($this->endpoint . $path . (($method == self::METHOD_GET && !empty($params)) ? '?' . http_build_query($params) : '')); + $ch = curl_init($endpoint . $path . (($method == self::METHOD_GET && !empty($params)) ? '?' . http_build_query($params) : '')); $responseHeaders = []; $responseStatus = -1; $responseType = ''; @@ -426,4 +432,9 @@ class Executor return $output; } + + private function selectEndpoint(string $projectId, string $deploymentId): string + { + return call_user_func($this->endpointSelector, $projectId, $deploymentId); + } } From 89a0fb8c9b8d73808ebbd1d5992fad6c98168c39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 7 Apr 2025 17:29:02 +0200 Subject: [PATCH 64/80] Update docker-compose.yml --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 3ba03a6b79..835ef22b18 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -205,7 +205,7 @@ services: appwrite-console: <<: *x-logging container_name: appwrite-console - image: appwrite/console:5.3.0-sites-rc.35 + image: appwrite/console:5.3.0-sites-rc.36 restart: unless-stopped networks: - appwrite From a0c013fcec4c065a8fe2ddc0c0a617ca523fed2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 8 Apr 2025 10:44:57 +0200 Subject: [PATCH 65/80] Post-1.6.x-merge fixes --- .../specs/open-api3-latest-console.json | 2 +- app/config/specs/open-api3-latest-server.json | 2 +- app/config/specs/swagger2-latest-console.json | 2 +- app/config/specs/swagger2-latest-server.json | 2 +- app/controllers/general.php | 2 +- composer.lock | 376 +++--------------- .../Http/Deployments/Status/Update.php | 6 +- .../Functions/Http/Executions/Create.php | 5 +- .../Functions/Http/Functions/Update.php | 5 +- .../Modules/Functions/Workers/Builds.php | 14 +- .../Sites/Http/Deployments/Status/Update.php | 6 +- .../Modules/Sites/Http/Sites/Update.php | 5 +- 12 files changed, 78 insertions(+), 349 deletions(-) diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index 019047436a..d493a2893e 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -25914,7 +25914,7 @@ }, "\/sites\/{siteId}\/deployments\/template": { "post": { - "summary": "Create deployment", + "summary": "Create template deployment", "operationId": "sitesCreateTemplateDeployment", "tags": [ "sites" diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index 9903453e60..a8cb0d8eed 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -17792,7 +17792,7 @@ }, "\/sites\/{siteId}\/deployments\/template": { "post": { - "summary": "Create deployment", + "summary": "Create template deployment", "operationId": "sitesCreateTemplateDeployment", "tags": [ "sites" diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index 2c1d094b63..1db29376f2 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -26442,7 +26442,7 @@ }, "\/sites\/{siteId}\/deployments\/template": { "post": { - "summary": "Create deployment", + "summary": "Create template deployment", "operationId": "sitesCreateTemplateDeployment", "consumes": [ "application\/json" diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index c89973e2a3..e1e07f5851 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -18290,7 +18290,7 @@ }, "\/sites\/{siteId}\/deployments\/template": { "post": { - "summary": "Create deployment", + "summary": "Create template deployment", "operationId": "sitesCreateTemplateDeployment", "consumes": [ "application\/json" diff --git a/app/controllers/general.php b/app/controllers/general.php index f7da369367..972fc036f6 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -1254,7 +1254,7 @@ App::get('/robots.txt') ->inject('isResourceBlocked') ->inject('previewHostname') ->inject('apiKey') - ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey) { + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey) { $host = $request->getHostname() ?? ''; $mainDomain = System::getEnv('_APP_DOMAIN', ''); diff --git a/composer.lock b/composer.lock index a9585532f6..6e25678d0c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e4281c45c1b60dade2ec8df58e6926d5", + "content-hash": "cac7679b9486588135dad678d9488f9e", "packages": [ { "name": "adhocore/jwt", @@ -751,66 +751,6 @@ }, "time": "2025-03-26T18:01:50+00:00" }, - { - "name": "jean85/pretty-package-versions", - "version": "2.1.1", - "source": { - "type": "git", - "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a", - "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2.1.0", - "php": "^7.4|^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.2", - "jean85/composer-provided-replaced-stub-package": "^1.0", - "phpstan/phpstan": "^2.0", - "phpunit/phpunit": "^7.5|^8.5|^9.6", - "rector/rector": "^2.0", - "vimeo/psalm": "^4.3 || ^5.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Jean85\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Alessandro Lai", - "email": "alessandro.lai85@gmail.com" - } - ], - "description": "A library to get pretty versions strings of installed dependencies", - "keywords": [ - "composer", - "package", - "release", - "versions" - ], - "support": { - "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1" - }, - "time": "2025-03-19T14:43:43+00:00" - }, { "name": "league/csv", "version": "9.14.0", @@ -969,75 +909,6 @@ }, "time": "2023-10-02T10:01:54+00:00" }, - { - "name": "mongodb/mongodb", - "version": "1.10.0", - "source": { - "type": "git", - "url": "https://github.com/mongodb/mongo-php-library.git", - "reference": "b0bbd657f84219212487d01a8ffe93a789e1e488" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/b0bbd657f84219212487d01a8ffe93a789e1e488", - "reference": "b0bbd657f84219212487d01a8ffe93a789e1e488", - "shasum": "" - }, - "require": { - "ext-hash": "*", - "ext-json": "*", - "ext-mongodb": "^1.11.0", - "jean85/pretty-package-versions": "^1.2 || ^2.0.1", - "php": "^7.1 || ^8.0", - "symfony/polyfill-php80": "^1.19" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0", - "squizlabs/php_codesniffer": "^3.6", - "symfony/phpunit-bridge": "^5.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10.x-dev" - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "MongoDB\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Andreas Braun", - "email": "andreas.braun@mongodb.com" - }, - { - "name": "Jeremy Mikola", - "email": "jmikola@gmail.com" - } - ], - "description": "MongoDB driver library", - "homepage": "https://jira.mongodb.org/browse/PHPLIB", - "keywords": [ - "database", - "driver", - "mongodb", - "persistence" - ], - "support": { - "issues": "https://github.com/mongodb/mongo-php-library/issues", - "source": "https://github.com/mongodb/mongo-php-library/tree/1.10.0" - }, - "time": "2021-10-20T22:22:37+00:00" - }, { "name": "mustangostang/spyc", "version": "0.6.3", @@ -2933,86 +2804,6 @@ ], "time": "2024-09-09T11:45:10+00:00" }, - { - "name": "symfony/polyfill-php80", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, { "name": "symfony/polyfill-php82", "version": "v1.31.0", @@ -3174,16 +2965,16 @@ }, { "name": "tbachert/spi", - "version": "v1.0.2", + "version": "v1.0.3", "source": { "type": "git", "url": "https://github.com/Nevay/spi.git", - "reference": "2ddfaf815dafb45791a61b08170de8d583c16062" + "reference": "506a79c98e1a51522e76ee921ccb6c62d52faf3a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Nevay/spi/zipball/2ddfaf815dafb45791a61b08170de8d583c16062", - "reference": "2ddfaf815dafb45791a61b08170de8d583c16062", + "url": "https://api.github.com/repos/Nevay/spi/zipball/506a79c98e1a51522e76ee921ccb6c62d52faf3a", + "reference": "506a79c98e1a51522e76ee921ccb6c62d52faf3a", "shasum": "" }, "require": { @@ -3220,9 +3011,9 @@ ], "support": { "issues": "https://github.com/Nevay/spi/issues", - "source": "https://github.com/Nevay/spi/tree/v1.0.2" + "source": "https://github.com/Nevay/spi/tree/v1.0.3" }, - "time": "2024-10-04T16:36:12+00:00" + "time": "2025-04-02T19:38:14+00:00" }, { "name": "thecodingmachine/safe", @@ -3706,16 +3497,16 @@ }, { "name": "utopia-php/database", - "version": "0.61.2", + "version": "0.64.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "349fbdf4bc088f7775c7dfb8b80239a617a88436" + "reference": "6530a8a6d3c1fe92d0f9a92f0f05eda698d92e0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/349fbdf4bc088f7775c7dfb8b80239a617a88436", - "reference": "349fbdf4bc088f7775c7dfb8b80239a617a88436", + "url": "https://api.github.com/repos/utopia-php/database/zipball/6530a8a6d3c1fe92d0f9a92f0f05eda698d92e0b", + "reference": "6530a8a6d3c1fe92d0f9a92f0f05eda698d92e0b", "shasum": "" }, "require": { @@ -3724,7 +3515,7 @@ "php": ">=8.1", "utopia-php/cache": "0.12.*", "utopia-php/framework": "0.33.*", - "utopia-php/mongo": "0.3.*" + "utopia-php/pools": "0.8.*" }, "require-dev": { "fakerphp/faker": "1.23.*", @@ -3756,9 +3547,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.61.2" + "source": "https://github.com/utopia-php/database/tree/0.64.1" }, - "time": "2025-03-15T11:47:42+00:00" + "time": "2025-04-02T00:35:29+00:00" }, { "name": "utopia-php/detector", @@ -3914,16 +3705,16 @@ }, { "name": "utopia-php/fetch", - "version": "0.3.1", + "version": "0.4.0", "source": { "type": "git", "url": "https://github.com/utopia-php/fetch.git", - "reference": "524dd50afa8c64670c4fb18f1df4db9b5bb4b3d0" + "reference": "46e791ff6a95864517750b9df6bbf4a17e3c9c4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/fetch/zipball/524dd50afa8c64670c4fb18f1df4db9b5bb4b3d0", - "reference": "524dd50afa8c64670c4fb18f1df4db9b5bb4b3d0", + "url": "https://api.github.com/repos/utopia-php/fetch/zipball/46e791ff6a95864517750b9df6bbf4a17e3c9c4e", + "reference": "46e791ff6a95864517750b9df6bbf4a17e3c9c4e", "shasum": "" }, "require": { @@ -3947,9 +3738,9 @@ "description": "A simple library that provides an interface for making HTTP Requests.", "support": { "issues": "https://github.com/utopia-php/fetch/issues", - "source": "https://github.com/utopia-php/fetch/tree/0.3.1" + "source": "https://github.com/utopia-php/fetch/tree/0.4.0" }, - "time": "2025-03-05T18:08:55+00:00" + "time": "2025-03-11T21:06:56+00:00" }, { "name": "utopia-php/framework", @@ -4000,16 +3791,16 @@ }, { "name": "utopia-php/image", - "version": "0.8.0", + "version": "0.8.1", "source": { "type": "git", "url": "https://github.com/utopia-php/image.git", - "reference": "dcae5b1c6deb3ff6865f4e68f012b3709c289bca" + "reference": "e8cc7dd14f423270a1b7570ec0dae88a66195b63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/image/zipball/dcae5b1c6deb3ff6865f4e68f012b3709c289bca", - "reference": "dcae5b1c6deb3ff6865f4e68f012b3709c289bca", + "url": "https://api.github.com/repos/utopia-php/image/zipball/e8cc7dd14f423270a1b7570ec0dae88a66195b63", + "reference": "e8cc7dd14f423270a1b7570ec0dae88a66195b63", "shasum": "" }, "require": { @@ -4043,9 +3834,9 @@ ], "support": { "issues": "https://github.com/utopia-php/image/issues", - "source": "https://github.com/utopia-php/image/tree/0.8.0" + "source": "https://github.com/utopia-php/image/tree/0.8.1" }, - "time": "2025-02-20T11:49:03+00:00" + "time": "2025-04-04T18:55:20+00:00" }, { "name": "utopia-php/locale", @@ -4259,66 +4050,6 @@ }, "time": "2025-03-28T02:08:22+00:00" }, - { - "name": "utopia-php/mongo", - "version": "0.3.1", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/mongo.git", - "reference": "52326a9a43e2d27ff0c15c48ba746dacbe9a7aee" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/mongo/zipball/52326a9a43e2d27ff0c15c48ba746dacbe9a7aee", - "reference": "52326a9a43e2d27ff0c15c48ba746dacbe9a7aee", - "shasum": "" - }, - "require": { - "ext-mongodb": "*", - "mongodb/mongodb": "1.10.0", - "php": ">=8.0" - }, - "require-dev": { - "fakerphp/faker": "^1.14", - "laravel/pint": "1.2.*", - "phpstan/phpstan": "1.8.*", - "phpunit/phpunit": "^9.4", - "swoole/ide-helper": "4.8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\Mongo\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Eldad Fux", - "email": "eldad@appwrite.io" - }, - { - "name": "Wess", - "email": "wess@appwrite.io" - } - ], - "description": "A simple library to manage Mongo database", - "keywords": [ - "database", - "mongo", - "php", - "upf", - "utopia" - ], - "support": { - "issues": "https://github.com/utopia-php/mongo/issues", - "source": "https://github.com/utopia-php/mongo/tree/0.3.1" - }, - "time": "2023-09-01T17:25:28+00:00" - }, { "name": "utopia-php/orchestration", "version": "0.9.1", @@ -4421,16 +4152,16 @@ }, { "name": "utopia-php/pools", - "version": "0.7.0", + "version": "0.8.0", "source": { "type": "git", "url": "https://github.com/utopia-php/pools.git", - "reference": "ad64d45afda08ec8b29e2642a8d18075964d40bf" + "reference": "60733929dc328e7ea47e800579c8bbf0d49df5ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/pools/zipball/ad64d45afda08ec8b29e2642a8d18075964d40bf", - "reference": "ad64d45afda08ec8b29e2642a8d18075964d40bf", + "url": "https://api.github.com/repos/utopia-php/pools/zipball/60733929dc328e7ea47e800579c8bbf0d49df5ba", + "reference": "60733929dc328e7ea47e800579c8bbf0d49df5ba", "shasum": "" }, "require": { @@ -4467,9 +4198,9 @@ ], "support": { "issues": "https://github.com/utopia-php/pools/issues", - "source": "https://github.com/utopia-php/pools/tree/0.7.0" + "source": "https://github.com/utopia-php/pools/tree/0.8.0" }, - "time": "2025-03-18T03:55:33+00:00" + "time": "2025-03-19T10:22:03+00:00" }, { "name": "utopia-php/preloader", @@ -4526,23 +4257,23 @@ }, { "name": "utopia-php/queue", - "version": "0.9.0", + "version": "0.9.1", "source": { "type": "git", "url": "https://github.com/utopia-php/queue.git", - "reference": "077075f1d57afa430f76c35ed3bf4616e0eee8e7" + "reference": "32b6f84c55aae761db5a5ae76cc91ca8dbc8bc32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/queue/zipball/077075f1d57afa430f76c35ed3bf4616e0eee8e7", - "reference": "077075f1d57afa430f76c35ed3bf4616e0eee8e7", + "url": "https://api.github.com/repos/utopia-php/queue/zipball/32b6f84c55aae761db5a5ae76cc91ca8dbc8bc32", + "reference": "32b6f84c55aae761db5a5ae76cc91ca8dbc8bc32", "shasum": "" }, "require": { "php": ">=8.3", "php-amqplib/php-amqplib": "^3.7", "utopia-php/cli": "0.15.*", - "utopia-php/fetch": "^0.3.0", + "utopia-php/fetch": "0.4.*", "utopia-php/framework": "0.33.*", "utopia-php/telemetry": "0.1.*" }, @@ -4585,9 +4316,9 @@ ], "support": { "issues": "https://github.com/utopia-php/queue/issues", - "source": "https://github.com/utopia-php/queue/tree/0.9.0" + "source": "https://github.com/utopia-php/queue/tree/0.9.1" }, - "time": "2025-03-13T12:22:41+00:00" + "time": "2025-03-28T19:49:36+00:00" }, { "name": "utopia-php/registry", @@ -4906,27 +4637,28 @@ }, { "name": "utopia-php/websocket", - "version": "0.1.0", + "version": "0.3.0", "source": { "type": "git", "url": "https://github.com/utopia-php/websocket.git", - "reference": "51fcb86171400d8aa40d76c54593481fd273dab5" + "reference": "629e53640b108eab43c7cc9ab375efade8622d43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/websocket/zipball/51fcb86171400d8aa40d76c54593481fd273dab5", - "reference": "51fcb86171400d8aa40d76c54593481fd273dab5", + "url": "https://api.github.com/repos/utopia-php/websocket/zipball/629e53640b108eab43c7cc9ab375efade8622d43", + "reference": "629e53640b108eab43c7cc9ab375efade8622d43", "shasum": "" }, "require": { "php": ">=8.0" }, "require-dev": { + "laravel/pint": "^1.15", + "phpstan/phpstan": "^1.12", "phpunit/phpunit": "^9.5.5", - "swoole/ide-helper": "4.6.6", + "swoole/ide-helper": "5.1.2", "textalk/websocket": "1.5.2", - "vimeo/psalm": "^4.8.1", - "workerman/workerman": "^4.0" + "workerman/workerman": "4.1.*" }, "type": "library", "autoload": { @@ -4938,16 +4670,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Eldad Fux", - "email": "eldad@appwrite.io" - }, - { - "name": "Torsten Dittmann", - "email": "torsten@appwrite.io" - } - ], "description": "A simple abstraction for WebSocket servers.", "keywords": [ "framework", @@ -4958,9 +4680,9 @@ ], "support": { "issues": "https://github.com/utopia-php/websocket/issues", - "source": "https://github.com/utopia-php/websocket/tree/0.1.0" + "source": "https://github.com/utopia-php/websocket/tree/0.3.0" }, - "time": "2021-12-20T10:50:09+00:00" + "time": "2025-03-28T01:11:13+00:00" }, { "name": "webmozart/assert", diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Status/Update.php b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Status/Update.php index 053a10f982..a17fe3a29c 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Status/Update.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Status/Update.php @@ -9,7 +9,6 @@ use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response; use Executor\Executor; -use Utopia\App; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; @@ -58,6 +57,7 @@ class Update extends Action ->inject('dbForProject') ->inject('project') ->inject('queueForEvents') + ->inject('executor') ->callback([$this, 'action']); } @@ -67,7 +67,8 @@ class Update extends Action Response $response, Database $dbForProject, Document $project, - Event $queueForEvents + Event $queueForEvents, + Executor $executor ) { $function = $dbForProject->getDocument('functions', $functionId); @@ -101,7 +102,6 @@ class Update extends Action } try { - $executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST')); $executor->deleteRuntime($project->getId(), $deploymentId . "-build"); } catch (\Throwable $th) { // Don't throw if the deployment doesn't exist diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php index 7e89196a5b..b92e5c2784 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php @@ -93,6 +93,7 @@ class Create extends Base ->inject('queueForStatsUsage') ->inject('queueForFunctions') ->inject('geodb') + ->inject('executor') ->callback([$this, 'action']); } @@ -113,7 +114,8 @@ class Create extends Base Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, - Reader $geodb + Reader $geodb, + Executor $executor ) { $async = \strval($async) === 'true' || \strval($async) === '1'; @@ -381,7 +383,6 @@ class Create extends Base ]); /** Execute function */ - $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST')); try { $version = $function->getAttribute('version', 'v2'); $command = $runtime['startCommand']; diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php index 3c876ed91e..f18ba5dedf 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php @@ -103,6 +103,7 @@ class Update extends Base ->inject('queueForBuilds') ->inject('dbForPlatform') ->inject('gitHub') + ->inject('executor') ->callback([$this, 'action']); } @@ -132,7 +133,8 @@ class Update extends Base Event $queueForEvents, Build $queueForBuilds, Database $dbForPlatform, - GitHub $github + GitHub $github, + Executor $executor ) { // TODO: If only branch changes, re-deploy $function = $dbForProject->getDocument('functions', $functionId); @@ -234,7 +236,6 @@ class Update extends Base // Enforce Cold Start if spec limits change. if ($function->getAttribute('specification') !== $specification && !empty($function->getAttribute('deploymentId'))) { - $executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST')); try { $executor->deleteRuntime($project->getId(), $function->getAttribute('deploymentId')); } catch (\Throwable $th) { diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index a4951e3525..e1ae7a9749 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -72,6 +72,7 @@ class Builds extends Action ->inject('isResourceBlocked') ->inject('deviceForFiles') ->inject('log') + ->inject('executor') ->callback([$this, 'action']); } @@ -90,6 +91,7 @@ class Builds extends Action * @param Device $deviceForSites * @param Device $deviceForFiles * @param Log $log + * @param Executor $executor * @return void * @throws \Utopia\Database\Exception */ @@ -108,7 +110,8 @@ class Builds extends Action Device $deviceForSites, callable $isResourceBlocked, Device $deviceForFiles, - Log $log + Log $log, + Executor $executor ): void { $payload = $message->getPayload() ?? []; @@ -146,7 +149,8 @@ class Builds extends Action $deployment, $template, $isResourceBlocked, - $log + $log, + $executor ); break; @@ -172,6 +176,7 @@ class Builds extends Action * @param Document $deployment * @param Document $template * @param Log $log + * @param Executor $executor * @return void * @throws \Utopia\Database\Exception * @@ -194,7 +199,8 @@ class Builds extends Action Document $deployment, Document $template, callable $isResourceBlocked, - Log $log + Log $log, + Executor $executor ): void { $resourceKey = match ($resource->getCollection()) { 'functions' => 'functionId', @@ -207,8 +213,6 @@ class Builds extends Action 'functions' => $deviceForFunctions, }; - $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST')); - $log->addTag($resourceKey, $resource->getId()); $resource = $dbForProject->getDocument($resource->getCollection(), $resource->getId()); diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Status/Update.php b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Status/Update.php index 1970fa898e..6fd2d77f6b 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Status/Update.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Status/Update.php @@ -9,7 +9,6 @@ use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response; use Executor\Executor; -use Utopia\App; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; @@ -56,6 +55,7 @@ class Update extends Action ->inject('dbForProject') ->inject('project') ->inject('queueForEvents') + ->inject('executor') ->callback([$this, 'action']); } @@ -65,7 +65,8 @@ class Update extends Action Response $response, Database $dbForProject, Document $project, - Event $queueForEvents + Event $queueForEvents, + Executor $executor ) { $site = $dbForProject->getDocument('sites', $siteId); @@ -99,7 +100,6 @@ class Update extends Action } try { - $executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST')); $executor->deleteRuntime($project->getId(), $deploymentId . "-build"); } catch (\Throwable $th) { // Don't throw if the deployment doesn't exist diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php index 416062862a..9daa45b22c 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php @@ -96,6 +96,7 @@ class Update extends Base ->inject('queueForBuilds') ->inject('dbForPlatform') ->inject('gitHub') + ->inject('executor') ->callback([$this, 'action']); } @@ -125,7 +126,8 @@ class Update extends Base Event $queueForEvents, Build $queueForBuilds, Database $dbForPlatform, - GitHub $github + GitHub $github, + Executor $executor ) { if (!empty($adapter)) { $configFramework = Config::getParam('frameworks')[$framework] ?? []; @@ -233,7 +235,6 @@ class Update extends Base // Enforce Cold Start if spec limits change. if ($site->getAttribute('specification') !== $specification && !empty($site->getAttribute('deploymentId'))) { - $executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST')); try { $executor->deleteRuntime($project->getId(), $site->getAttribute('deploymentId')); } catch (\Throwable $th) { From 5a0b22f638d0afce2a606323ce8c8f874966085d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 8 Apr 2025 11:09:12 +0200 Subject: [PATCH 66/80] Fix after merge --- src/Executor/Executor.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index aa2e894c1d..c381ceeae3 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -273,7 +273,8 @@ class Executor 'timeout' => $timeout ]; - $response = $this->call(self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout); + $endpoint = $this->selectEndpoint($projectId, $deploymentId); + $response = $this->call($endpoint, self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout); $status = $response['headers']['status-code']; if ($status >= 400) { From f7605453b4ee0f27afa79adb890b76c263b86d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 8 Apr 2025 11:26:24 +0200 Subject: [PATCH 67/80] Upgrade console --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 20bf76f19e..5f0f12643e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -208,7 +208,7 @@ services: appwrite-console: <<: *x-logging container_name: appwrite-console - image: appwrite/console:5.3.0-sites-rc.36 + image: appwrite/console:5.3.0-sites-rc.37 restart: unless-stopped networks: - appwrite From 2acf2418273c608672406a0f21ab04b6b78bfa81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 8 Apr 2025 12:12:53 +0200 Subject: [PATCH 68/80] Increase default timeout to fix screenshot cold-start issues --- src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php | 2 +- src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php index 1a39edea46..ec95b56409 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php @@ -66,7 +66,7 @@ class Create extends Base ->param('framework', '', new WhiteList(\array_keys(Config::getParam('frameworks')), true), 'Sites framework.') ->param('enabled', true, new Boolean(), 'Is site enabled? When set to \'disabled\', users cannot access the site but Server SDKs with and API key can still access the site. No data is lost when this is toggled.', true) ->param('logging', true, new Boolean(), 'When disabled, request logs will exclude logs and errors, and site responses will be slightly faster.', true) - ->param('timeout', 15, new Range(1, (int) System::getEnv('_APP_COMPUTE_TIMEOUT', 900)), 'Maximum request time in seconds.', true) + ->param('timeout', 60, new Range(1, (int) System::getEnv('_APP_COMPUTE_TIMEOUT', 900)), 'Maximum request time in seconds.', true) ->param('installCommand', '', new Text(8192, 0), 'Install Command.', true) ->param('buildCommand', '', new Text(8192, 0), 'Build Command.', true) ->param('outputDirectory', '', new Text(8192, 0), 'Output Directory for site.', true) diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php index 9daa45b22c..78b76b6627 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php @@ -70,7 +70,7 @@ class Update extends Base ->param('framework', '', new WhiteList(\array_keys(Config::getParam('frameworks')), true), 'Sites framework.') ->param('enabled', true, new Boolean(), 'Is site enabled? When set to \'disabled\', users cannot access the site but Server SDKs with and API key can still access the site. No data is lost when this is toggled.', true) ->param('logging', true, new Boolean(), 'When disabled, request logs will exclude logs and errors, and site responses will be slightly faster.', true) - ->param('timeout', 15, new Range(1, (int) System::getEnv('_APP_COMPUTE_TIMEOUT', 900)), 'Maximum request time in seconds.', true) + ->param('timeout', 60, new Range(1, (int) System::getEnv('_APP_COMPUTE_TIMEOUT', 900)), 'Maximum request time in seconds.', true) ->param('installCommand', '', new Text(8192, 0), 'Install Command.', true) ->param('buildCommand', '', new Text(8192, 0), 'Build Command.', true) ->param('outputDirectory', '', new Text(8192, 0), 'Output Directory for site.', true) From d205ea61c1f3f150709d3e11cc74e1c235c70a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 8 Apr 2025 12:23:43 +0200 Subject: [PATCH 69/80] Separate function & site timeout --- .env | 3 ++- app/views/install/compose.phtml | 9 ++++++--- docker-compose.yml | 9 ++++++--- .../Platform/Modules/Functions/Http/Functions/Create.php | 2 +- .../Platform/Modules/Functions/Http/Functions/Update.php | 2 +- .../Platform/Modules/Sites/Http/Sites/Create.php | 2 +- .../Platform/Modules/Sites/Http/Sites/Update.php | 2 +- tests/resources/docker/docker-compose.yml | 6 ++++-- 8 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.env b/.env index 51a690132e..ac7e2a6836 100644 --- a/.env +++ b/.env @@ -71,7 +71,8 @@ _APP_SMS_PROJECTS_DENY_LIST= _APP_STORAGE_LIMIT=30000000 _APP_STORAGE_PREVIEW_LIMIT=20000000 _APP_COMPUTE_SIZE_LIMIT=30000000 -_APP_COMPUTE_TIMEOUT=900 +_APP_FUNCTIONS_TIMEOUT=900 +_APP_SITES_TIMEOUT=30 _APP_COMPUTE_BUILD_TIMEOUT=900 _APP_COMPUTE_CPUS=8 _APP_COMPUTE_MEMORY=8192 diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml index 7a0f450e4f..bca4222376 100644 --- a/app/views/install/compose.phtml +++ b/app/views/install/compose.phtml @@ -134,7 +134,8 @@ $image = $this->getParam('image', ''); - _APP_STORAGE_WASABI_REGION - _APP_STORAGE_WASABI_BUCKET - _APP_COMPUTE_SIZE_LIMIT - - _APP_COMPUTE_TIMEOUT + - _APP_FUNCTIONS_TIMEOUT + - _APP_SITES_TIMEOUT - _APP_COMPUTE_BUILD_TIMEOUT - _APP_COMPUTE_CPUS - _APP_COMPUTE_MEMORY @@ -406,7 +407,8 @@ $image = $this->getParam('image', ''); - _APP_VCS_GITHUB_APP_NAME - _APP_VCS_GITHUB_PRIVATE_KEY - _APP_VCS_GITHUB_APP_ID - - _APP_COMPUTE_TIMEOUT + - _APP_FUNCTIONS_TIMEOUT + - _APP_SITES_TIMEOUT - _APP_COMPUTE_BUILD_TIMEOUT - _APP_COMPUTE_CPUS - _APP_COMPUTE_MEMORY @@ -497,7 +499,8 @@ $image = $this->getParam('image', ''); - _APP_DB_SCHEMA - _APP_DB_USER - _APP_DB_PASS - - _APP_COMPUTE_TIMEOUT + - _APP_FUNCTIONS_TIMEOUT + - _APP_SITES_TIMEOUT - _APP_COMPUTE_BUILD_TIMEOUT - _APP_COMPUTE_CPUS - _APP_COMPUTE_MEMORY diff --git a/docker-compose.yml b/docker-compose.yml index 5f0f12643e..a2d7fc91dc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -160,7 +160,8 @@ services: - _APP_STORAGE_WASABI_REGION - _APP_STORAGE_WASABI_BUCKET - _APP_COMPUTE_SIZE_LIMIT - - _APP_COMPUTE_TIMEOUT + - _APP_FUNCTIONS_TIMEOUT + - _APP_SITES_TIMEOUT - _APP_COMPUTE_BUILD_TIMEOUT - _APP_COMPUTE_CPUS - _APP_COMPUTE_MEMORY @@ -467,7 +468,8 @@ services: - _APP_VCS_GITHUB_APP_NAME - _APP_VCS_GITHUB_PRIVATE_KEY - _APP_VCS_GITHUB_APP_ID - - _APP_COMPUTE_TIMEOUT + - _APP_FUNCTIONS_TIMEOUT + - _APP_SITES_TIMEOUT - _APP_COMPUTE_BUILD_TIMEOUT - _APP_COMPUTE_CPUS - _APP_COMPUTE_MEMORY @@ -566,7 +568,8 @@ services: - _APP_DB_SCHEMA - _APP_DB_USER - _APP_DB_PASS - - _APP_COMPUTE_TIMEOUT + - _APP_FUNCTIONS_TIMEOUT + - _APP_SITES_TIMEOUT - _APP_COMPUTE_BUILD_TIMEOUT - _APP_COMPUTE_CPUS - _APP_COMPUTE_MEMORY diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php index cf31d3d7d2..9436833dad 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php @@ -74,7 +74,7 @@ class Create extends Base ->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of role strings with execution permissions. By default no user is granted with any execute permissions. [learn more about roles](https://appwrite.io/docs/permissions#permission-roles). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true) ->param('events', [], new ArrayList(new FunctionEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true) ->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true) - ->param('timeout', 15, new Range(1, (int) System::getEnv('_APP_COMPUTE_TIMEOUT', 900)), 'Function maximum execution time in seconds.', true) + ->param('timeout', 15, new Range(1, (int) System::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Function maximum execution time in seconds.', true) ->param('enabled', true, new Boolean(), 'Is function enabled? When set to \'disabled\', users cannot access the function but Server SDKs with and API key can still access the function. No data is lost when this is toggled.', true) ->param('logging', true, new Boolean(), 'When disabled, executions will exclude logs and errors, and will be slightly faster.', true) ->param('entrypoint', '', new Text(1028, 0), 'Entrypoint File. This path is relative to the "providerRootDirectory".', true) diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php index f18ba5dedf..3e4399f8db 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php @@ -78,7 +78,7 @@ class Update extends Base ->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of role strings with execution permissions. By default no user is granted with any execute permissions. [learn more about roles](https://appwrite.io/docs/permissions#permission-roles). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true) ->param('events', [], new ArrayList(new FunctionEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true) ->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true) - ->param('timeout', 15, new Range(1, (int) System::getEnv('_APP_COMPUTE_TIMEOUT', 900)), 'Maximum execution time in seconds.', true) + ->param('timeout', 15, new Range(1, (int) System::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Maximum execution time in seconds.', true) ->param('enabled', true, new Boolean(), 'Is function enabled? When set to \'disabled\', users cannot access the function but Server SDKs with and API key can still access the function. No data is lost when this is toggled.', true) ->param('logging', true, new Boolean(), 'When disabled, executions will exclude logs and errors, and will be slightly faster.', true) ->param('entrypoint', '', new Text(1028, 0), 'Entrypoint File. This path is relative to the "providerRootDirectory".', true) diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php index ec95b56409..c91ec449be 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php @@ -66,7 +66,7 @@ class Create extends Base ->param('framework', '', new WhiteList(\array_keys(Config::getParam('frameworks')), true), 'Sites framework.') ->param('enabled', true, new Boolean(), 'Is site enabled? When set to \'disabled\', users cannot access the site but Server SDKs with and API key can still access the site. No data is lost when this is toggled.', true) ->param('logging', true, new Boolean(), 'When disabled, request logs will exclude logs and errors, and site responses will be slightly faster.', true) - ->param('timeout', 60, new Range(1, (int) System::getEnv('_APP_COMPUTE_TIMEOUT', 900)), 'Maximum request time in seconds.', true) + ->param('timeout', 30, new Range(1, (int) System::getEnv('_APP_SITES_TIMEOUT', 30)), 'Maximum request time in seconds.', true) ->param('installCommand', '', new Text(8192, 0), 'Install Command.', true) ->param('buildCommand', '', new Text(8192, 0), 'Build Command.', true) ->param('outputDirectory', '', new Text(8192, 0), 'Output Directory for site.', true) diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php index 78b76b6627..de220e174c 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php @@ -70,7 +70,7 @@ class Update extends Base ->param('framework', '', new WhiteList(\array_keys(Config::getParam('frameworks')), true), 'Sites framework.') ->param('enabled', true, new Boolean(), 'Is site enabled? When set to \'disabled\', users cannot access the site but Server SDKs with and API key can still access the site. No data is lost when this is toggled.', true) ->param('logging', true, new Boolean(), 'When disabled, request logs will exclude logs and errors, and site responses will be slightly faster.', true) - ->param('timeout', 60, new Range(1, (int) System::getEnv('_APP_COMPUTE_TIMEOUT', 900)), 'Maximum request time in seconds.', true) + ->param('timeout', 30, new Range(1, (int) System::getEnv('_APP_SITES_TIMEOUT', 30)), 'Maximum request time in seconds.', true) ->param('installCommand', '', new Text(8192, 0), 'Install Command.', true) ->param('buildCommand', '', new Text(8192, 0), 'Build Command.', true) ->param('outputDirectory', '', new Text(8192, 0), 'Output Directory for site.', true) diff --git a/tests/resources/docker/docker-compose.yml b/tests/resources/docker/docker-compose.yml index 67bb9a1aef..1551a2c30c 100644 --- a/tests/resources/docker/docker-compose.yml +++ b/tests/resources/docker/docker-compose.yml @@ -82,7 +82,8 @@ services: - _APP_USAGE_STATS - _APP_STORAGE_ANTIVIRUS=disabled - _APP_STORAGE_LIMIT - - _APP_COMPUTE_TIMEOUT + - _APP_FUNCTIONS_TIMEOUT + - _APP_SITES_TIMEOUT - _APP_COMPUTE_CPUS - _APP_COMPUTE_MEMORY - _APP_EXECUTOR_HOST @@ -238,7 +239,8 @@ services: - _APP_DB_SCHEMA - _APP_DB_USER - _APP_DB_PASS - - _APP_COMPUTE_TIMEOUT + - _APP_FUNCTIONS_TIMEOUT + - _APP_SITES_TIMEOUT - _APP_COMPUTE_CPUS - _APP_COMPUTE_MEMORY - _APP_EXECUTOR_HOST From 2689cceb71360bfd208537ad9033b81367cef459 Mon Sep 17 00:00:00 2001 From: Matej Baco Date: Tue, 8 Apr 2025 14:48:50 +0000 Subject: [PATCH 70/80] Implement A/AAAA custom domain support --- .env | 4 +- app/config/variables.php | 29 ++++++++++- app/controllers/api/console.php | 25 ++++++++-- app/controllers/api/proxy.php | 31 +++++++++--- composer.lock | 26 +++++----- docker-compose.yml | 16 ++++-- .../Network/Validator/{CNAME.php => DNS.php} | 40 +++++++++------ .../Modules/Proxy/Http/Rules/API/Create.php | 31 ++++++++---- .../Proxy/Http/Rules/Function/Create.php | 31 ++++++++---- .../Proxy/Http/Rules/Redirect/Create.php | 31 ++++++++---- .../Modules/Proxy/Http/Rules/Site/Create.php | 31 ++++++++---- src/Appwrite/Platform/Tasks/Doctor.php | 22 ++++++-- .../Platform/Workers/Certificates.php | 37 ++++++++++---- .../Response/Model/ConsoleVariables.php | 24 ++++++--- .../Console/ConsoleConsoleClientTest.php | 7 +-- tests/unit/Network/Validators/CNAMETest.php | 29 ----------- tests/unit/Network/Validators/DNSTest.php | 50 +++++++++++++++++++ 17 files changed, 325 insertions(+), 139 deletions(-) rename src/Appwrite/Network/Validator/{CNAME.php => DNS.php} (53%) delete mode 100644 tests/unit/Network/Validators/CNAMETest.php create mode 100644 tests/unit/Network/Validators/DNSTest.php diff --git a/.env b/.env index ac7e2a6836..1d46f6cad9 100644 --- a/.env +++ b/.env @@ -23,7 +23,9 @@ _APP_OPENSSL_KEY_V1=your-secret-key _APP_DOMAIN=traefik _APP_DOMAIN_FUNCTIONS=functions.localhost _APP_DOMAIN_SITES=sites.localhost -_APP_DOMAIN_TARGET=test.appwrite.io +_APP_DOMAIN_TARGET_CNAME=test.appwrite.io +_APP_DOMAIN_TARGET_A=127.0.0.1 +_APP_DOMAIN_TARGET_AAAA=::1 _APP_RULES_FORMAT=md5 _APP_REDIS_HOST=redis _APP_REDIS_PORT=6379 diff --git a/app/config/variables.php b/app/config/variables.php index 27463d2fee..d6516f7a54 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -90,13 +90,40 @@ return [ ], [ 'name' => '_APP_DOMAIN_TARGET', - 'description' => 'A DNS A record hostname to serve as a CNAME target for your Appwrite custom domains. You can use the same value as used for the Appwrite \'_APP_DOMAIN\' variable. The default value is \'localhost\'.', + 'description' => 'Deprecated since 1.7.0. A DNS A record hostname to serve as a CNAME target for your Appwrite custom domains. You can use the same value as used for the Appwrite \'_APP_DOMAIN\' variable. The default value is \'localhost\'.', 'introduction' => '', 'default' => 'localhost', 'required' => true, 'question' => 'Enter a DNS A record hostname to serve as a CNAME for your custom domains.' . PHP_EOL . 'You can use the same value as used for the Appwrite hostname.', 'filter' => 'domainTarget' ], + [ + 'name' => '_APP_DOMAIN_TARGET_CNAME', + 'description' => 'A domain that can be used as DNS CNAME record to point to instance of Appwrite server.', + 'introduction' => '', + 'default' => 'localhost', + 'required' => false, + 'question' => '', + 'filter' => '' + ], + [ + 'name' => '_APP_DOMAIN_TARGET_AAAA', + 'description' => 'An IPv6 that can be used as DNS AAAA record to point to instance of Appwrite server.', + 'introduction' => '', + 'default' => '::1', + 'required' => false, + 'question' => '', + 'filter' => '' + ], + [ + 'name' => '_APP_DOMAIN_TARGET_A', + 'description' => 'An IPV4 that can be used as DNS A record to point to instance of Appwrite server.', + 'introduction' => '', + 'default' => '127.0.0.1', + 'required' => false, + 'question' => '', + 'filter' => '' + ], [ 'name' => '_APP_CONSOLE_WHITELIST_ROOT', 'description' => 'This option allows you to disable the creation of new users on the Appwrite console. When enabled only 1 user will be able to use the registration form. New users can be added by inviting them to your project. By default this option is enabled.', diff --git a/app/controllers/api/console.php b/app/controllers/api/console.php index 4cf00faf65..4dd5432d3a 100644 --- a/app/controllers/api/console.php +++ b/app/controllers/api/console.php @@ -8,7 +8,9 @@ use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response; use Utopia\App; use Utopia\Database\Document; +use Utopia\Domains\Domain; use Utopia\System\System; +use Utopia\Validator\IP; use Utopia\Validator\Text; App::init() @@ -40,10 +42,21 @@ App::get('/v1/console/variables') )) ->inject('response') ->action(function (Response $response) { - $isDomainEnabled = !empty(System::getEnv('_APP_DOMAIN', '')) - && !empty(System::getEnv('_APP_DOMAIN_TARGET', '')) - && System::getEnv('_APP_DOMAIN', '') !== 'localhost' - && System::getEnv('_APP_DOMAIN_TARGET', '') !== 'localhost'; + $validator = new Domain(System::getEnv('_APP_DOMAIN')); + $isDomainValid = !empty(System::getEnv('_APP_DOMAIN', '')) && $validator->isKnown() && !$validator->isTest(); + + $validator = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME')); + $isCNAMEValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_CNAME', '')) && $validator->isKnown() && !$validator->isTest(); + + $validator = new IP(IP::V4); + $isAValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_A', '')) && ($validator->isValid(System::getEnv('_APP_DOMAIN_TARGET_A'))); + + $validator = new IP(IP::V6); + $isAAAAValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_AAAA', '')) && $validator->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA')); + + $isDomainEnabled = $isDomainValid && ( + $isAAAAValid || $isAValid || $isCNAMEValid + ); $isVcsEnabled = !empty(System::getEnv('_APP_VCS_GITHUB_APP_NAME', '')) && !empty(System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY', '')) @@ -54,7 +67,9 @@ App::get('/v1/console/variables') $isAssistantEnabled = !empty(System::getEnv('_APP_ASSISTANT_OPENAI_API_KEY', '')); $variables = new Document([ - '_APP_DOMAIN_TARGET' => System::getEnv('_APP_DOMAIN_TARGET'), + '_APP_DOMAIN_TARGET_CNAME' => System::getEnv('_APP_DOMAIN_TARGET_CNAME'), + '_APP_DOMAIN_TARGET_AAAA' => System::getEnv('_APP_DOMAIN_TARGET_AAAA'), + '_APP_DOMAIN_TARGET_A' => System::getEnv('_APP_DOMAIN_TARGET_A'), '_APP_STORAGE_LIMIT' => +System::getEnv('_APP_STORAGE_LIMIT'), '_APP_COMPUTE_SIZE_LIMIT' => +System::getEnv('_APP_COMPUTE_SIZE_LIMIT'), '_APP_USAGE_STATS' => System::getEnv('_APP_USAGE_STATS'), diff --git a/app/controllers/api/proxy.php b/app/controllers/api/proxy.php index e7aa3acb6c..d8e637725a 100644 --- a/app/controllers/api/proxy.php +++ b/app/controllers/api/proxy.php @@ -4,7 +4,7 @@ use Appwrite\Event\Certificate; use Appwrite\Event\Delete; use Appwrite\Event\Event; use Appwrite\Extend\Exception; -use Appwrite\Network\Validator\CNAME; +use Appwrite\Network\Validator\DNS; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; use Appwrite\SDK\Method; @@ -21,6 +21,8 @@ use Utopia\Database\Validator\UID; use Utopia\Domains\Domain; use Utopia\Logger\Log; use Utopia\System\System; +use Utopia\Validator\AnyOf; +use Utopia\Validator\IP; use Utopia\Validator\Text; App::get('/v1/proxy/rules') @@ -208,17 +210,27 @@ App::patch('/v1/proxy/rules/:ruleId/verification') throw new Exception(Exception::RULE_NOT_FOUND); } - $target = new Domain(System::getEnv('_APP_DOMAIN_TARGET', '')); + $validators = []; + $targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', '')); + if (!$targetCNAME->isKnown() || $targetCNAME->isTest()) { + $validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME); + } + if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) { + $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), DNS::RECORD_A); + } + if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) { + $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), DNS::RECORD_AAAA); + } - if (!$target->isKnown() || $target->isTest()) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Domain target must be configured as environment variable.'); + if (empty($validators)) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, 'At least one of domain targets environment variable must be configured.'); } if ($rule->getAttribute('verification') === true) { return $response->dynamic($rule, Response::MODEL_PROXY_RULE); } - $validator = new CNAME($target->get()); // Verify Domain with DNS records + $validator = new AnyOf($validators, AnyOf::TYPE_STRING); $domain = new Domain($rule->getAttribute('domain', '')); $validationStart = \microtime(true); @@ -226,7 +238,14 @@ App::patch('/v1/proxy/rules/:ruleId/verification') $log->addExtra('dnsTiming', \strval(\microtime(true) - $validationStart)); $log->addTag('dnsDomain', $domain->get()); - $error = $validator->getLogs(); + $errors = []; + foreach ($validators as $validator) { + if (!empty($validator->getLogs())) { + $errors[] = $validator->getLogs(); + } + } + + $error = \implode("\n", $errors); $log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error)); throw new Exception(Exception::RULE_VERIFICATION_FAILED); diff --git a/composer.lock b/composer.lock index 6e25678d0c..401ae5f8d2 100644 --- a/composer.lock +++ b/composer.lock @@ -1365,16 +1365,16 @@ }, { "name": "open-telemetry/sdk", - "version": "1.2.2", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "37eec0fe47ddd627911f318f29b6cd48196be0c0" + "reference": "0e7804c176c4b09d95b7985400aa38ce544cb7fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/37eec0fe47ddd627911f318f29b6cd48196be0c0", - "reference": "37eec0fe47ddd627911f318f29b6cd48196be0c0", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/0e7804c176c4b09d95b7985400aa38ce544cb7fc", + "reference": "0e7804c176c4b09d95b7985400aa38ce544cb7fc", "shasum": "" }, "require": { @@ -1451,7 +1451,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-01-29T21:40:28+00:00" + "time": "2025-04-08T09:55:41+00:00" }, { "name": "open-telemetry/sem-conv", @@ -3791,16 +3791,16 @@ }, { "name": "utopia-php/image", - "version": "0.8.1", + "version": "0.8.2", "source": { "type": "git", "url": "https://github.com/utopia-php/image.git", - "reference": "e8cc7dd14f423270a1b7570ec0dae88a66195b63" + "reference": "6c736965177f9a9e71311e22b80cfa88511768e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/image/zipball/e8cc7dd14f423270a1b7570ec0dae88a66195b63", - "reference": "e8cc7dd14f423270a1b7570ec0dae88a66195b63", + "url": "https://api.github.com/repos/utopia-php/image/zipball/6c736965177f9a9e71311e22b80cfa88511768e9", + "reference": "6c736965177f9a9e71311e22b80cfa88511768e9", "shasum": "" }, "require": { @@ -3834,9 +3834,9 @@ ], "support": { "issues": "https://github.com/utopia-php/image/issues", - "source": "https://github.com/utopia-php/image/tree/0.8.1" + "source": "https://github.com/utopia-php/image/tree/0.8.2" }, - "time": "2025-04-04T18:55:20+00:00" + "time": "2025-04-08T11:31:45+00:00" }, { "name": "utopia-php/locale", @@ -8229,7 +8229,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -8253,5 +8253,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/docker-compose.yml b/docker-compose.yml index a2d7fc91dc..fe40e75363 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -115,7 +115,9 @@ services: - _APP_OPTIONS_COMPUTE_FORCE_HTTPS - _APP_OPENSSL_KEY_V1 - _APP_DOMAIN - - _APP_DOMAIN_TARGET + - _APP_DOMAIN_TARGET_CNAME + - _APP_DOMAIN_TARGET_AAAA + - _APP_DOMAIN_TARGET_A - _APP_DOMAIN_FUNCTIONS - _APP_REDIS_HOST - _APP_REDIS_PORT @@ -524,7 +526,9 @@ services: - _APP_WORKER_PER_CORE - _APP_OPENSSL_KEY_V1 - _APP_DOMAIN - - _APP_DOMAIN_TARGET + - _APP_DOMAIN_TARGET_CNAME + - _APP_DOMAIN_TARGET_AAAA + - _APP_DOMAIN_TARGET_A - _APP_DOMAIN_FUNCTIONS - _APP_EMAIL_CERTIFICATES - _APP_REDIS_HOST @@ -690,7 +694,9 @@ services: - _APP_WORKER_PER_CORE - _APP_OPENSSL_KEY_V1 - _APP_DOMAIN - - _APP_DOMAIN_TARGET + - _APP_DOMAIN_TARGET_CNAME + - _APP_DOMAIN_TARGET_AAAA + - _APP_DOMAIN_TARGET_A - _APP_EMAIL_SECURITY - _APP_REDIS_HOST - _APP_REDIS_PORT @@ -722,7 +728,9 @@ services: - _APP_ENV - _APP_WORKER_PER_CORE - _APP_DOMAIN - - _APP_DOMAIN_TARGET + - _APP_DOMAIN_TARGET_CNAME + - _APP_DOMAIN_TARGET_AAAA + - _APP_DOMAIN_TARGET_A - _APP_DOMAIN_FUNCTIONS - _APP_OPENSSL_KEY_V1 - _APP_REDIS_HOST diff --git a/src/Appwrite/Network/Validator/CNAME.php b/src/Appwrite/Network/Validator/DNS.php similarity index 53% rename from src/Appwrite/Network/Validator/CNAME.php rename to src/Appwrite/Network/Validator/DNS.php index e1ae061c84..9fa78f360a 100644 --- a/src/Appwrite/Network/Validator/CNAME.php +++ b/src/Appwrite/Network/Validator/DNS.php @@ -4,24 +4,22 @@ namespace Appwrite\Network\Validator; use Utopia\Validator; -class CNAME extends Validator +class DNS extends Validator { + public const RECORD_A = 'a'; + public const RECORD_AAAA = 'aaaa'; + public const RECORD_CNAME = 'cname'; + /** * @var mixed */ protected mixed $logs; - /** - * @var string - */ - protected $target; - /** * @param string $target */ - public function __construct($target) + public function __construct(protected $target, protected string $type = self::RECORD_CNAME) { - $this->target = $target; } /** @@ -29,7 +27,7 @@ class CNAME extends Validator */ public function getDescription(): string { - return 'Invalid CNAME record'; + return 'Invalid DNS record'; } /** @@ -41,20 +39,34 @@ class CNAME extends Validator } /** - * Check if CNAME record target value matches selected target + * Check if DNS record value matches specific value * * @param mixed $domain * * @return bool */ - public function isValid($domain): bool + public function isValid($value): bool { - if (!is_string($domain)) { + $typeNative = match ($this->type) { + self::RECORD_A => DNS_A, + self::RECORD_AAAA => DNS_AAAA, + self::RECORD_CNAME => DNS_CNAME, + default => throw new \Exception('Record type not supported.') + }; + + $dnsKey = match ($this->type) { + self::RECORD_A => 'ip', + self::RECORD_AAAA => 'ipv6', + self::RECORD_CNAME => 'target', + default => throw new \Exception('Record type not supported.') + }; + + if (!is_string($value)) { return false; } try { - $records = \dns_get_record($domain, DNS_CNAME); + $records = \dns_get_record($value, $typeNative); $this->logs = $records; } catch (\Throwable $th) { return false; @@ -65,7 +77,7 @@ class CNAME extends Validator } foreach ($records as $record) { - if (isset($record['target']) && $record['target'] === $this->target) { + if (isset($record[$dnsKey]) && $record[$dnsKey] === $this->target) { return true; } } diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php index 50c56b7ea3..b6349c3aea 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php @@ -5,12 +5,11 @@ namespace Appwrite\Platform\Modules\Proxy\Http\Rules\API; use Appwrite\Event\Certificate; use Appwrite\Event\Event; use Appwrite\Extend\Exception; -use Appwrite\Network\Validator\CNAME; +use Appwrite\Network\Validator\DNS; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response; -use Utopia\App; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; @@ -19,7 +18,9 @@ use Utopia\Domains\Domain; use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; use Utopia\System\System; +use Utopia\Validator\AnyOf; use Utopia\Validator\Domain as ValidatorDomain; +use Utopia\Validator\IP; class Create extends Action { @@ -95,13 +96,6 @@ class Create extends Action throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.'); } - // Apex domain prevention due to CNAME limitations - if (empty(App::getEnv('_APP_DOMAINS_NAMESERVERS', ''))) { - if ($domain->get() === $domain->getRegisterable()) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'The instance does not allow root-level (apex) domains.'); - } - } - // TODO: @christyjacob remove once we migrate the rules in 1.7.x $ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique(); @@ -110,8 +104,23 @@ class Create extends Action $status = 'verified'; } if ($status === 'created') { - $target = new Domain(System::getEnv('_APP_DOMAIN_TARGET', '')); - $validator = new CNAME($target->get()); + $validators = []; + $targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', '')); + if (!$targetCNAME->isKnown() || $targetCNAME->isTest()) { + $validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME); + } + if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) { + $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), DNS::RECORD_A); + } + if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) { + $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), DNS::RECORD_AAAA); + } + + if (empty($validators)) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, 'At least one of domain targets environment variable must be configured.'); + } + + $validator = new AnyOf($validators, AnyOf::TYPE_STRING); if ($validator->isValid($domain->get())) { $status = 'verifying'; } diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php index 890eb00dec..a85a4fa063 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php @@ -5,12 +5,11 @@ namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Function; use Appwrite\Event\Certificate; use Appwrite\Event\Event; use Appwrite\Extend\Exception; -use Appwrite\Network\Validator\CNAME; +use Appwrite\Network\Validator\DNS; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response; -use Utopia\App; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; @@ -20,7 +19,9 @@ use Utopia\Domains\Domain; use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; use Utopia\System\System; +use Utopia\Validator\AnyOf; use Utopia\Validator\Domain as ValidatorDomain; +use Utopia\Validator\IP; use Utopia\Validator\Text; class Create extends Action @@ -96,13 +97,6 @@ class Create extends Action throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.'); } - // Apex domain prevention due to CNAME limitations - if (empty(App::getEnv('_APP_DOMAINS_NAMESERVERS', ''))) { - if ($domain->get() === $domain->getRegisterable()) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'The instance does not allow root-level (apex) domains.'); - } - } - $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND); @@ -118,8 +112,23 @@ class Create extends Action $status = 'verified'; } if ($status === 'created') { - $target = new Domain(System::getEnv('_APP_DOMAIN_TARGET', '')); - $validator = new CNAME($target->get()); + $validators = []; + $targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', '')); + if (!$targetCNAME->isKnown() || $targetCNAME->isTest()) { + $validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME); + } + if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) { + $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), DNS::RECORD_A); + } + if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) { + $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), DNS::RECORD_AAAA); + } + + if (empty($validators)) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, 'At least one of domain targets environment variable must be configured.'); + } + + $validator = new AnyOf($validators, AnyOf::TYPE_STRING); if ($validator->isValid($domain->get())) { $status = 'verifying'; } diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php index 5e6f69af4b..58271ef08e 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php @@ -5,12 +5,11 @@ namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Redirect; use Appwrite\Event\Certificate; use Appwrite\Event\Event; use Appwrite\Extend\Exception; -use Appwrite\Network\Validator\CNAME; +use Appwrite\Network\Validator\DNS; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response; -use Utopia\App; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; @@ -19,7 +18,9 @@ use Utopia\Domains\Domain; use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; use Utopia\System\System; +use Utopia\Validator\AnyOf; use Utopia\Validator\Domain as ValidatorDomain; +use Utopia\Validator\IP; use Utopia\Validator\URL; use Utopia\Validator\WhiteList; @@ -95,13 +96,6 @@ class Create extends Action throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.'); } - // Apex domain prevention due to CNAME limitations - if (empty(App::getEnv('_APP_DOMAINS_NAMESERVERS', ''))) { - if ($domain->get() === $domain->getRegisterable()) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'The instance does not allow root-level (apex) domains.'); - } - } - // TODO: @christyjacob remove once we migrate the rules in 1.7.x $ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique(); @@ -110,8 +104,23 @@ class Create extends Action $status = 'verified'; } if ($status === 'created') { - $dnsTarget = new Domain(System::getEnv('_APP_DOMAIN_TARGET', '')); - $validator = new CNAME($dnsTarget->get()); + $validators = []; + $targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', '')); + if (!$targetCNAME->isKnown() || $targetCNAME->isTest()) { + $validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME); + } + if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) { + $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), DNS::RECORD_A); + } + if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) { + $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), DNS::RECORD_AAAA); + } + + if (empty($validators)) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, 'At least one of domain targets environment variable must be configured.'); + } + + $validator = new AnyOf($validators, AnyOf::TYPE_STRING); if ($validator->isValid($domain->get())) { $status = 'verifying'; } diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php index 4506f10dce..1a0cbe7fdc 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php @@ -5,12 +5,11 @@ namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Site; use Appwrite\Event\Certificate; use Appwrite\Event\Event; use Appwrite\Extend\Exception; -use Appwrite\Network\Validator\CNAME; +use Appwrite\Network\Validator\DNS; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response; -use Utopia\App; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; @@ -20,7 +19,9 @@ use Utopia\Domains\Domain; use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; use Utopia\System\System; +use Utopia\Validator\AnyOf; use Utopia\Validator\Domain as ValidatorDomain; +use Utopia\Validator\IP; use Utopia\Validator\Text; class Create extends Action @@ -96,13 +97,6 @@ class Create extends Action throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.'); } - // Apex domain prevention due to CNAME limitations - if (empty(App::getEnv('_APP_DOMAINS_NAMESERVERS', ''))) { - if ($domain->get() === $domain->getRegisterable()) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'The instance does not allow root-level (apex) domains.'); - } - } - $site = $dbForProject->getDocument('sites', $siteId); if ($site->isEmpty()) { throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND); @@ -118,8 +112,23 @@ class Create extends Action $status = 'verified'; } if ($status === 'created') { - $target = new Domain(System::getEnv('_APP_DOMAIN_TARGET', '')); - $validator = new CNAME($target->get()); + $validators = []; + $targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', '')); + if (!$targetCNAME->isKnown() || $targetCNAME->isTest()) { + $validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME); + } + if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) { + $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), DNS::RECORD_A); + } + if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) { + $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), DNS::RECORD_AAAA); + } + + if (empty($validators)) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, 'At least one of domain targets environment variable must be configured.'); + } + + $validator = new AnyOf($validators, AnyOf::TYPE_STRING); if ($validator->isValid($domain->get())) { $status = 'verifying'; } diff --git a/src/Appwrite/Platform/Tasks/Doctor.php b/src/Appwrite/Platform/Tasks/Doctor.php index a9d0a6220c..c07aaa1363 100644 --- a/src/Appwrite/Platform/Tasks/Doctor.php +++ b/src/Appwrite/Platform/Tasks/Doctor.php @@ -15,6 +15,7 @@ use Utopia\Registry\Registry; use Utopia\Storage\Device\Local; use Utopia\Storage\Storage; use Utopia\System\System; +use Utopia\Validator\IP; class Doctor extends Action { @@ -50,12 +51,25 @@ class Doctor extends Action Console::log('🟢 Hostname has a public suffix (' . $domain->get() . ')'); } - $domain = new Domain(System::getEnv('_APP_DOMAIN_TARGET')); - + $domain = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME')); if (!$domain->isKnown() || $domain->isTest()) { - Console::log('🔴 CNAME target has no public suffix (' . $domain->get() . ')'); + Console::log('🔴 CNAME record target is not valid (' . $domain->get() . ')'); } else { - Console::log('🟢 CNAME target has a public suffix (' . $domain->get() . ')'); + Console::log('🟢 CNAME record target is not valid (' . $domain->get() . ')'); + } + + $ipv4 = new IP(IP::V4); + if ($ipv4->isValid(System::getEnv('_APP_DOMAIN_TARGET_A'))) { + Console::log('🔴 A record target is not valid (' . System::getEnv('_APP_DOMAIN_TARGET_A') . ')'); + } else { + Console::log('🟢 A record target is not valid (' . System::getEnv('_APP_DOMAIN_TARGET_A') . ')'); + } + + $ipv6 = new IP(IP::V6); + if ($ipv6->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA'))) { + Console::log('🔴 A record target is not valid (' . System::getEnv('_APP_DOMAIN_TARGET_AAAA') . ')'); + } else { + Console::log('🟢 A record target is not valid (' . System::getEnv('_APP_DOMAIN_TARGET_AAAA') . ')'); } if (System::getEnv('_APP_OPENSSL_KEY_V1') === 'your-secret-key' || empty(System::getEnv('_APP_OPENSSL_KEY_V1'))) { diff --git a/src/Appwrite/Platform/Workers/Certificates.php b/src/Appwrite/Platform/Workers/Certificates.php index 712196fc72..15f9645bb0 100644 --- a/src/Appwrite/Platform/Workers/Certificates.php +++ b/src/Appwrite/Platform/Workers/Certificates.php @@ -8,7 +8,7 @@ use Appwrite\Event\Func; use Appwrite\Event\Mail; use Appwrite\Event\Realtime; use Appwrite\Event\Webhook; -use Appwrite\Network\Validator\CNAME; +use Appwrite\Network\Validator\DNS; use Appwrite\Template\Template; use Appwrite\Utopia\Response\Model\Rule; use Exception; @@ -28,6 +28,8 @@ use Utopia\Logger\Log; use Utopia\Platform\Action; use Utopia\Queue\Message; use Utopia\System\System; +use Utopia\Validator\AnyOf; +use Utopia\Validator\IP; class Certificates extends Action { @@ -290,22 +292,39 @@ class Certificates extends Action } if (!$isMainDomain) { - // TODO: Would be awesome to also support A/AAAA records here. Maybe dry run? - // Validate if domain target is properly configured - $target = new Domain(System::getEnv('_APP_DOMAIN_TARGET', '')); + $validationStart = \microtime(true); - if (!$target->isKnown() || $target->isTest()) { - throw new Exception('Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.'); + $validators = []; + $targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', '')); + if (!$targetCNAME->isKnown() || $targetCNAME->isTest()) { + $validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME); + } + if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) { + $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), DNS::RECORD_A); + } + if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) { + $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), DNS::RECORD_AAAA); + } + + // Validate if domain target is properly configured + if (empty($validators)) { + throw new Exception('At least one of domain targets environment variable must be configured.'); } // Verify domain with DNS records - $validationStart = \microtime(true); - $validator = new CNAME($target->get()); + $validator = new AnyOf($validators, AnyOf::TYPE_STRING); if (!$validator->isValid($domain->get())) { $log->addExtra('dnsTiming', \strval(\microtime(true) - $validationStart)); $log->addTag('dnsDomain', $domain->get()); - $error = $validator->getLogs(); + $errors = []; + foreach ($validators as $validator) { + if (!empty($validator->getLogs())) { + $errors[] = $validator->getLogs(); + } + } + + $error = \implode("\n", $errors); $log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error)); throw new Exception('Failed to verify domain DNS records.'); diff --git a/src/Appwrite/Utopia/Response/Model/ConsoleVariables.php b/src/Appwrite/Utopia/Response/Model/ConsoleVariables.php index 57f287fc78..97dae2efcd 100644 --- a/src/Appwrite/Utopia/Response/Model/ConsoleVariables.php +++ b/src/Appwrite/Utopia/Response/Model/ConsoleVariables.php @@ -10,12 +10,24 @@ class ConsoleVariables extends Model public function __construct() { $this - ->addRule('_APP_DOMAIN_TARGET', [ - 'type' => self::TYPE_STRING, - 'description' => 'CNAME target for your Appwrite custom domains.', - 'default' => '', - 'example' => 'appwrite.io', - ]) + ->addRule('_APP_DOMAIN_TARGET_CNAME', [ + 'type' => self::TYPE_STRING, + 'description' => 'CNAME target for your Appwrite custom domains.', + 'default' => '', + 'example' => 'appwrite.io', + ]) + ->addRule('_APP_DOMAIN_TARGET_A', [ + 'type' => self::TYPE_STRING, + 'description' => 'A target for your Appwrite custom domains.', + 'default' => '', + 'example' => '127.0.0.1', + ]) + ->addRule('_APP_DOMAIN_TARGET_AAAA', [ + 'type' => self::TYPE_STRING, + 'description' => 'AAAA target for your Appwrite custom domains.', + 'default' => '', + 'example' => '::1', + ]) ->addRule('_APP_STORAGE_LIMIT', [ 'type' => self::TYPE_INTEGER, 'description' => 'Maximum file size allowed for file upload in bytes.', diff --git a/tests/e2e/Services/Console/ConsoleConsoleClientTest.php b/tests/e2e/Services/Console/ConsoleConsoleClientTest.php index a24ec148bf..6059cb2000 100644 --- a/tests/e2e/Services/Console/ConsoleConsoleClientTest.php +++ b/tests/e2e/Services/Console/ConsoleConsoleClientTest.php @@ -24,11 +24,12 @@ class ConsoleConsoleClientTest extends Scope ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertCount(11, $response['body']); - $this->assertIsString($response['body']['_APP_DOMAIN_TARGET']); + $this->assertCount(13, $response['body']); + $this->assertIsString($response['body']['_APP_DOMAIN_TARGET_CNAME']); + $this->assertIsString($response['body']['_APP_DOMAIN_TARGET_A']); + $this->assertIsString($response['body']['_APP_DOMAIN_TARGET_AAAA']); $this->assertIsInt($response['body']['_APP_STORAGE_LIMIT']); $this->assertIsInt($response['body']['_APP_COMPUTE_SIZE_LIMIT']); - $this->assertIsString($response['body']['_APP_DOMAIN_TARGET']); $this->assertIsBool($response['body']['_APP_DOMAIN_ENABLED']); $this->assertIsBool($response['body']['_APP_VCS_ENABLED']); $this->assertIsBool($response['body']['_APP_ASSISTANT_ENABLED']); diff --git a/tests/unit/Network/Validators/CNAMETest.php b/tests/unit/Network/Validators/CNAMETest.php deleted file mode 100644 index cbb07f19b5..0000000000 --- a/tests/unit/Network/Validators/CNAMETest.php +++ /dev/null @@ -1,29 +0,0 @@ -object = new CNAME('appwrite.io'); - } - - public function tearDown(): void - { - } - - public function testValues(): void - { - $this->assertEquals($this->object->isValid(''), false); - $this->assertEquals($this->object->isValid(null), false); - $this->assertEquals($this->object->isValid(false), false); - $this->assertEquals($this->object->isValid('cname-unit-test.appwrite.org'), true); - $this->assertEquals($this->object->isValid('test1.appwrite.org'), false); - } -} diff --git a/tests/unit/Network/Validators/DNSTest.php b/tests/unit/Network/Validators/DNSTest.php new file mode 100644 index 0000000000..5e8652381a --- /dev/null +++ b/tests/unit/Network/Validators/DNSTest.php @@ -0,0 +1,50 @@ +assertEquals($validator->isValid(''), false); + $this->assertEquals($validator->isValid(null), false); + $this->assertEquals($validator->isValid(false), false); + $this->assertEquals($validator->isValid('cname-unit-test.appwrite.org'), true); + $this->assertEquals($validator->isValid('test1.appwrite.org'), false); + } + + public function testA(): void + { + // IPv4 for documentation purposes + $validator = new DNS('203.0.113.1', DNS::RECORD_A); + $this->assertEquals($validator->isValid(''), false); + $this->assertEquals($validator->isValid(null), false); + $this->assertEquals($validator->isValid(false), false); + $this->assertEquals($validator->isValid('a-unit-test.appwrite.org'), true); + $this->assertEquals($validator->isValid('test1.appwrite.org'), false); + } + + public function testAAAA(): void + { + // IPv6 for documentation purposes + $validator = new DNS('2001:db8::1', DNS::RECORD_AAAA); + $this->assertEquals($validator->isValid(''), false); + $this->assertEquals($validator->isValid(null), false); + $this->assertEquals($validator->isValid(false), false); + $this->assertEquals($validator->isValid('aaaa-unit-test.appwrite.org'), true); + $this->assertEquals($validator->isValid('test1.appwrite.org'), false); + } +} From e7b64113ca9dcdd3f5b495f83be6263e74ad5e4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 9 Apr 2025 10:31:33 +0200 Subject: [PATCH 71/80] pr review changes; fix tests --- composer.lock | 30 +++++++++---------- src/Appwrite/Platform/Tasks/Doctor.php | 17 +++++------ .../Services/Proxy/ProxyCustomServerTest.php | 6 ++-- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/composer.lock b/composer.lock index 401ae5f8d2..32a98f418a 100644 --- a/composer.lock +++ b/composer.lock @@ -3996,16 +3996,16 @@ }, { "name": "utopia-php/migration", - "version": "0.8.4", + "version": "0.8.5", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "845fd04ccf5e0edb03c184b864e0596080a432b8" + "reference": "0dd95b148c581579ec05d2abbbdc13c2b4702331" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/845fd04ccf5e0edb03c184b864e0596080a432b8", - "reference": "845fd04ccf5e0edb03c184b864e0596080a432b8", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/0dd95b148c581579ec05d2abbbdc13c2b4702331", + "reference": "0dd95b148c581579ec05d2abbbdc13c2b4702331", "shasum": "" }, "require": { @@ -4046,9 +4046,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/0.8.4" + "source": "https://github.com/utopia-php/migration/tree/0.8.5" }, - "time": "2025-03-28T02:08:22+00:00" + "time": "2025-04-09T05:21:09+00:00" }, { "name": "utopia-php/orchestration", @@ -5085,16 +5085,16 @@ }, { "name": "laravel/pint", - "version": "v1.21.2", + "version": "v1.22.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "370772e7d9e9da087678a0edf2b11b6960e40558" + "reference": "7ddfaa6523a675fae5c4123ee38fc6bfb8ee4f36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/370772e7d9e9da087678a0edf2b11b6960e40558", - "reference": "370772e7d9e9da087678a0edf2b11b6960e40558", + "url": "https://api.github.com/repos/laravel/pint/zipball/7ddfaa6523a675fae5c4123ee38fc6bfb8ee4f36", + "reference": "7ddfaa6523a675fae5c4123ee38fc6bfb8ee4f36", "shasum": "" }, "require": { @@ -5105,9 +5105,9 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.72.0", + "friendsofphp/php-cs-fixer": "^3.75.0", "illuminate/view": "^11.44.2", - "larastan/larastan": "^3.2.0", + "larastan/larastan": "^3.3.1", "laravel-zero/framework": "^11.36.1", "mockery/mockery": "^1.6.12", "nunomaduro/termwind": "^2.3", @@ -5147,7 +5147,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-03-14T22:31:42+00:00" + "time": "2025-04-08T22:11:45+00:00" }, { "name": "matthiasmullie/minify", @@ -8229,7 +8229,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -8253,5 +8253,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/src/Appwrite/Platform/Tasks/Doctor.php b/src/Appwrite/Platform/Tasks/Doctor.php index c07aaa1363..329248eae2 100644 --- a/src/Appwrite/Platform/Tasks/Doctor.php +++ b/src/Appwrite/Platform/Tasks/Doctor.php @@ -44,32 +44,31 @@ class Doctor extends Action Console::log('[Settings]'); $domain = new Domain(System::getEnv('_APP_DOMAIN')); - if (!$domain->isKnown() || $domain->isTest()) { - Console::log('🔴 Hostname has no public suffix (' . $domain->get() . ')'); + Console::log('🔴 Hostname is not valid (' . $domain->get() . ')'); } else { - Console::log('🟢 Hostname has a public suffix (' . $domain->get() . ')'); + Console::log('🟢 Hostname is valid (' . $domain->get() . ')'); } $domain = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME')); if (!$domain->isKnown() || $domain->isTest()) { Console::log('🔴 CNAME record target is not valid (' . $domain->get() . ')'); } else { - Console::log('🟢 CNAME record target is not valid (' . $domain->get() . ')'); + Console::log('🟢 CNAME record target is valid (' . $domain->get() . ')'); } $ipv4 = new IP(IP::V4); - if ($ipv4->isValid(System::getEnv('_APP_DOMAIN_TARGET_A'))) { + if (!$ipv4->isValid(System::getEnv('_APP_DOMAIN_TARGET_A'))) { Console::log('🔴 A record target is not valid (' . System::getEnv('_APP_DOMAIN_TARGET_A') . ')'); } else { - Console::log('🟢 A record target is not valid (' . System::getEnv('_APP_DOMAIN_TARGET_A') . ')'); + Console::log('🟢 A record target is valid (' . System::getEnv('_APP_DOMAIN_TARGET_A') . ')'); } $ipv6 = new IP(IP::V6); - if ($ipv6->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA'))) { - Console::log('🔴 A record target is not valid (' . System::getEnv('_APP_DOMAIN_TARGET_AAAA') . ')'); + if (!$ipv6->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA'))) { + Console::log('🔴 AAAA record target is not valid (' . System::getEnv('_APP_DOMAIN_TARGET_AAAA') . ')'); } else { - Console::log('🟢 A record target is not valid (' . System::getEnv('_APP_DOMAIN_TARGET_AAAA') . ')'); + Console::log('🟢 AAAA record target is valid (' . System::getEnv('_APP_DOMAIN_TARGET_AAAA') . ')'); } if (System::getEnv('_APP_OPENSSL_KEY_V1') === 'your-secret-key' || empty(System::getEnv('_APP_OPENSSL_KEY_V1'))) { diff --git a/tests/e2e/Services/Proxy/ProxyCustomServerTest.php b/tests/e2e/Services/Proxy/ProxyCustomServerTest.php index 6312840095..a3bf597f21 100644 --- a/tests/e2e/Services/Proxy/ProxyCustomServerTest.php +++ b/tests/e2e/Services/Proxy/ProxyCustomServerTest.php @@ -53,8 +53,10 @@ class ProxyCustomServerTest extends Scope public function testCreateRuleApex(): void { - $rule = $this->createAPIRule('myapp.com'); - $this->assertEquals(400, $rule['headers']['status-code']); + $domain = \uniqid() . '.com'; + $rule = $this->createAPIRule($domain); + $this->assertEquals(201, $rule['headers']['status-code']); + $this->assertEquals('created', $rule['body']['status']); } public function testCreateRuleVcs(): void From dff1319e4d82abe41faf60b99967a478b37b8311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 9 Apr 2025 10:50:26 +0200 Subject: [PATCH 72/80] Fix failure test --- tests/e2e/Services/Proxy/ProxyCustomServerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Proxy/ProxyCustomServerTest.php b/tests/e2e/Services/Proxy/ProxyCustomServerTest.php index a3bf597f21..0ac2d9c186 100644 --- a/tests/e2e/Services/Proxy/ProxyCustomServerTest.php +++ b/tests/e2e/Services/Proxy/ProxyCustomServerTest.php @@ -339,7 +339,7 @@ class ProxyCustomServerTest extends Scope $this->cleanupRule($rule['body']['$id']); // Create + update - $domain = \uniqid() . '-cname-api.custom.localhost'; + $domain = \uniqid() . '-cname-api.custom.com'; $rule = $this->createAPIRule($domain); $this->assertEquals(201, $rule['headers']['status-code']); From e2f082eea1be834437edc2106392d59605227683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 9 Apr 2025 11:48:27 +0200 Subject: [PATCH 73/80] Update specs --- .../specs/open-api3-latest-console.json | 16 +++++++++++++-- app/config/specs/swagger2-latest-console.json | 20 +++++++++++++++---- app/config/specs/swagger2-latest-server.json | 4 ++-- composer.lock | 12 +++++------ 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index d493a2893e..9957b930a7 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -40872,11 +40872,21 @@ "description": "Console Variables", "type": "object", "properties": { - "_APP_DOMAIN_TARGET": { + "_APP_DOMAIN_TARGET_CNAME": { "type": "string", "description": "CNAME target for your Appwrite custom domains.", "x-example": "appwrite.io" }, + "_APP_DOMAIN_TARGET_A": { + "type": "string", + "description": "A target for your Appwrite custom domains.", + "x-example": "127.0.0.1" + }, + "_APP_DOMAIN_TARGET_AAAA": { + "type": "string", + "description": "AAAA target for your Appwrite custom domains.", + "x-example": "::1" + }, "_APP_STORAGE_LIMIT": { "type": "integer", "description": "Maximum file size allowed for file upload in bytes.", @@ -40931,7 +40941,9 @@ } }, "required": [ - "_APP_DOMAIN_TARGET", + "_APP_DOMAIN_TARGET_CNAME", + "_APP_DOMAIN_TARGET_A", + "_APP_DOMAIN_TARGET_AAAA", "_APP_STORAGE_LIMIT", "_APP_COMPUTE_SIZE_LIMIT", "_APP_USAGE_STATS", diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index 1db29376f2..09b3f5009f 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -25244,7 +25244,7 @@ "timeout": { "type": "integer", "description": "Maximum request time in seconds.", - "default": 15, + "default": 30, "x-example": 1 }, "installCommand": { @@ -25895,7 +25895,7 @@ "timeout": { "type": "integer", "description": "Maximum request time in seconds.", - "default": 15, + "default": 30, "x-example": 1 }, "installCommand": { @@ -41531,11 +41531,21 @@ "description": "Console Variables", "type": "object", "properties": { - "_APP_DOMAIN_TARGET": { + "_APP_DOMAIN_TARGET_CNAME": { "type": "string", "description": "CNAME target for your Appwrite custom domains.", "x-example": "appwrite.io" }, + "_APP_DOMAIN_TARGET_A": { + "type": "string", + "description": "A target for your Appwrite custom domains.", + "x-example": "127.0.0.1" + }, + "_APP_DOMAIN_TARGET_AAAA": { + "type": "string", + "description": "AAAA target for your Appwrite custom domains.", + "x-example": "::1" + }, "_APP_STORAGE_LIMIT": { "type": "integer", "description": "Maximum file size allowed for file upload in bytes.", @@ -41590,7 +41600,9 @@ } }, "required": [ - "_APP_DOMAIN_TARGET", + "_APP_DOMAIN_TARGET_CNAME", + "_APP_DOMAIN_TARGET_A", + "_APP_DOMAIN_TARGET_AAAA", "_APP_STORAGE_LIMIT", "_APP_COMPUTE_SIZE_LIMIT", "_APP_USAGE_STATS", diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index e1e07f5851..2255731e10 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -17308,7 +17308,7 @@ "timeout": { "type": "integer", "description": "Maximum request time in seconds.", - "default": 15, + "default": 30, "x-example": 1 }, "installCommand": { @@ -17738,7 +17738,7 @@ "timeout": { "type": "integer", "description": "Maximum request time in seconds.", - "default": 15, + "default": 30, "x-example": 1 }, "installCommand": { diff --git a/composer.lock b/composer.lock index 32a98f418a..8935658b04 100644 --- a/composer.lock +++ b/composer.lock @@ -3497,16 +3497,16 @@ }, { "name": "utopia-php/database", - "version": "0.64.1", + "version": "0.64.2", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "6530a8a6d3c1fe92d0f9a92f0f05eda698d92e0b" + "reference": "dc9c4a68c93e8bea2dfaa76d1ba308be539998bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/6530a8a6d3c1fe92d0f9a92f0f05eda698d92e0b", - "reference": "6530a8a6d3c1fe92d0f9a92f0f05eda698d92e0b", + "url": "https://api.github.com/repos/utopia-php/database/zipball/dc9c4a68c93e8bea2dfaa76d1ba308be539998bd", + "reference": "dc9c4a68c93e8bea2dfaa76d1ba308be539998bd", "shasum": "" }, "require": { @@ -3547,9 +3547,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.64.1" + "source": "https://github.com/utopia-php/database/tree/0.64.2" }, - "time": "2025-04-02T00:35:29+00:00" + "time": "2025-04-09T07:53:05+00:00" }, { "name": "utopia-php/detector", From 1a2db0665488bd2fa62ba89d893ffaf7f3e6130a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 9 Apr 2025 13:51:34 +0200 Subject: [PATCH 74/80] Upgrade detector --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 8935658b04..f33d83dc4c 100644 --- a/composer.lock +++ b/composer.lock @@ -3553,16 +3553,16 @@ }, { "name": "utopia-php/detector", - "version": "0.1.2", + "version": "0.1.4", "source": { "type": "git", "url": "https://github.com/utopia-php/detector.git", - "reference": "8617ea205738743ef3363ad95791139e82ae44d5" + "reference": "895a4147463965b5f9cbc083b764b6476f547879" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/detector/zipball/8617ea205738743ef3363ad95791139e82ae44d5", - "reference": "8617ea205738743ef3363ad95791139e82ae44d5", + "url": "https://api.github.com/repos/utopia-php/detector/zipball/895a4147463965b5f9cbc083b764b6476f547879", + "reference": "895a4147463965b5f9cbc083b764b6476f547879", "shasum": "" }, "require": { @@ -3592,9 +3592,9 @@ ], "support": { "issues": "https://github.com/utopia-php/detector/issues", - "source": "https://github.com/utopia-php/detector/tree/0.1.2" + "source": "https://github.com/utopia-php/detector/tree/0.1.4" }, - "time": "2025-03-11T13:01:25+00:00" + "time": "2025-04-09T11:50:45+00:00" }, { "name": "utopia-php/domains", From db15b05332366accde25907756de7395e2eda244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 9 Apr 2025 14:35:54 +0200 Subject: [PATCH 75/80] Upgrade COnsole --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index fe40e75363..9a6f507da3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -211,7 +211,7 @@ services: appwrite-console: <<: *x-logging container_name: appwrite-console - image: appwrite/console:5.3.0-sites-rc.37 + image: appwrite/console:5.3.0-sites-rc.38 restart: unless-stopped networks: - appwrite From 25236130563195123321d92a44f5d5b99054e645 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 10 Apr 2025 10:20:12 +0200 Subject: [PATCH 76/80] Improve logs coloring --- src/Appwrite/Platform/Modules/Functions/Workers/Builds.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index e1ae7a9749..ea6cac8374 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -804,7 +804,7 @@ class Builds extends Action if ($resource->getCollection() === 'sites') { $date = \date('H:i:s'); - $logs .= "[$date] [appwrite] Screenshot capturing started. \n"; + $logs .= "[$date] [appwrite] Screenshot capturing started. \n"; } $deployment->setAttribute('buildLogs', $logs); @@ -944,7 +944,7 @@ class Builds extends Action $logs = $deployment->getAttribute('buildLogs', ''); $date = \date('H:i:s'); - $logs .= "[$date] [appwrite] Screenshot capturing finished. \n"; + $logs .= "[$date] [appwrite] Screenshot capturing finished. \n"; $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment); From 52024ccc262c8bb2e792bb1b19b89a4988f161df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 10 Apr 2025 16:11:38 +0200 Subject: [PATCH 77/80] Add starters; update console --- app/config/templates/site.php | 143 ++++++++++++++++++ docker-compose.yml | 2 +- .../templates/starter-for-analog-dark.png | Bin 0 -> 60216 bytes .../templates/starter-for-analog-light.png | Bin 0 -> 60216 bytes .../templates/starter-for-astro-dark.png | Bin 0 -> 48885 bytes .../templates/starter-for-astro-light.png | Bin 0 -> 48885 bytes .../templates/starter-for-remix-dark.png | Bin 0 -> 54339 bytes .../templates/starter-for-remix-light.png | Bin 0 -> 54339 bytes .../Modules/Functions/Workers/Builds.php | 4 + src/Appwrite/Platform/Tasks/Screenshot.php | 2 +- 10 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 public/images/sites/templates/starter-for-analog-dark.png create mode 100644 public/images/sites/templates/starter-for-analog-light.png create mode 100644 public/images/sites/templates/starter-for-astro-dark.png create mode 100644 public/images/sites/templates/starter-for-astro-light.png create mode 100644 public/images/sites/templates/starter-for-remix-dark.png create mode 100644 public/images/sites/templates/starter-for-remix-light.png diff --git a/app/config/templates/site.php b/app/config/templates/site.php index 5aae737843..6989cc026d 100644 --- a/app/config/templates/site.php +++ b/app/config/templates/site.php @@ -116,6 +116,15 @@ const TEMPLATE_FRAMEWORKS = [ 'outputDirectory' => './dist/angular/browser', 'fallbackFile' => 'index.html', ], + 'ANALOG' => [ + 'key' => 'analog', + 'name' => 'Analog', + 'installCommand' => 'npm install', + 'buildCommand' => 'npm run build', + 'buildRuntime' => 'node-22', + 'adapter' => 'ssr', + 'outputDirectory' => './dist/analog', + ], 'VUE' => [ 'key' => 'vue', 'name' => 'Vue.js', @@ -581,6 +590,139 @@ return [ ], ] ], + [ + 'key' => 'starter-for-astro', + 'name' => 'Astro starter', + 'useCases' => [UseCases::STARTER], + 'tagline' => 'Simple Astro application integrated with Appwrite SDK.', + 'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) + 'screenshotDark' => $url . '/images/sites/templates/starter-for-astro-dark.png', + 'screenshotLight' => $url . '/images/sites/templates/starter-for-astro-light.png', + 'frameworks' => [ + getFramework('ASTRO', [ + 'providerRootDirectory' => './', + 'adapter' => 'static', + ]), + ], + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'starter-for-astro', + 'providerOwner' => 'appwrite', + 'providerVersion' => '0.1.*', + 'variables' => [ + [ + 'name' => 'PUBLIC_APPWRITE_ENDPOINT', + 'description' => 'Endpoint of Appwrite server', + 'value' => '{apiEndpoint}', + 'placeholder' => '{apiEndpoint}', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'PUBLIC_APPWRITE_PROJECT_ID', + 'description' => 'Your Appwrite project ID', + 'value' => '{projectId}', + 'placeholder' => '{projectId}', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'PUBLIC_APPWRITE_PROJECT_NAME', + 'description' => 'Your Appwrite project name', + 'value' => '{projectName}', + 'placeholder' => '{projectName}', + 'required' => true, + 'type' => 'text' + ], + ] + ], + [ + 'key' => 'starter-for-analog', + 'name' => 'Analog starter', + 'useCases' => [UseCases::STARTER], + 'tagline' => 'Simple Analog application integrated with Appwrite SDK.', + 'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) + 'screenshotDark' => $url . '/images/sites/templates/starter-for-analog-dark.png', + 'screenshotLight' => $url . '/images/sites/templates/starter-for-analog-light.png', + 'frameworks' => [ + getFramework('ANALOG', [ + 'providerRootDirectory' => './', + ]), + ], + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'starter-for-analog', + 'providerOwner' => 'appwrite', + 'providerVersion' => '0.1.*', + 'variables' => [ + [ + 'name' => 'VITE_APPWRITE_ENDPOINT', + 'description' => 'Endpoint of Appwrite server', + 'value' => '{apiEndpoint}', + 'placeholder' => '{apiEndpoint}', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'VITE_APPWRITE_PROJECT_ID', + 'description' => 'Your Appwrite project ID', + 'value' => '{projectId}', + 'placeholder' => '{projectId}', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'VITE_APPWRITE_PROJECT_NAME', + 'description' => 'Your Appwrite project name', + 'value' => '{projectName}', + 'placeholder' => '{projectName}', + 'required' => true, + 'type' => 'text' + ], + ] + ], + [ + 'key' => 'starter-for-remix', + 'name' => 'Remix starter', + 'useCases' => [UseCases::STARTER], + 'tagline' => 'Simple Remix application integrated with Appwrite SDK.', + 'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible) + 'screenshotDark' => $url . '/images/sites/templates/starter-for-remix-dark.png', + 'screenshotLight' => $url . '/images/sites/templates/starter-for-remix-light.png', + 'frameworks' => [ + getFramework('REMIX', [ + 'providerRootDirectory' => './', + ]), + ], + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'starter-for-remix', + 'providerOwner' => 'appwrite', + 'providerVersion' => '0.1.*', + 'variables' => [ + [ + 'name' => 'VITE_APPWRITE_ENDPOINT', + 'description' => 'Endpoint of Appwrite server', + 'value' => '{apiEndpoint}', + 'placeholder' => '{apiEndpoint}', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'VITE_APPWRITE_PROJECT_ID', + 'description' => 'Your Appwrite project ID', + 'value' => '{projectId}', + 'placeholder' => '{projectId}', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'VITE_APPWRITE_PROJECT_NAME', + 'description' => 'Your Appwrite project name', + 'value' => '{projectName}', + 'placeholder' => '{projectName}', + 'required' => true, + 'type' => 'text' + ], + ] + ], [ 'key' => 'starter-for-svelte', 'name' => 'Svelte starter', @@ -966,6 +1108,7 @@ return [ 'providerVersion' => '0.1.*', 'variables' => [] ], + // TODO: Remove astro starter eventually, or add all frameworks's starters [ 'key' => 'astro-starter', 'name' => 'Astro starter', diff --git a/docker-compose.yml b/docker-compose.yml index 9a6f507da3..bec3d8c88e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -211,7 +211,7 @@ services: appwrite-console: <<: *x-logging container_name: appwrite-console - image: appwrite/console:5.3.0-sites-rc.38 + image: appwrite/console:5.3.0-sites-rc.40 restart: unless-stopped networks: - appwrite diff --git a/public/images/sites/templates/starter-for-analog-dark.png b/public/images/sites/templates/starter-for-analog-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7529e08dd8c3b1f15c915d4058c459c8ee5e9b85 GIT binary patch literal 60216 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 literal 0 HcmV?d00001 diff --git a/public/images/sites/templates/starter-for-analog-light.png b/public/images/sites/templates/starter-for-analog-light.png new file mode 100644 index 0000000000000000000000000000000000000000..7529e08dd8c3b1f15c915d4058c459c8ee5e9b85 GIT binary patch literal 60216 zcmYJbcRZE<|37|^JtBJx*|LuvMMe@KWOHmMLXMeHMsyroajYUE87F&X@0Exnqr5s2 zj);)`dmg<%-=BX@x6XB)>$x85e!oAic;h>|v{W2a5D0`;Ur*Zv0wD$eB>qT23_dh@ zZxMkHB7YOz8<5H|u2l$x52CMq-7F{vzu-}TGt1igoo2RB(Xv{Me$1$Cqsc(T?Z_0x zaKVf$kV`Sv*G7k3S4Z1^KAGscHWL%8wsy)r7iLz(M^}bk(hS2+vQ-w2>|*q<7PIA? zeXYjJxwo#AnPyC-;m(dA4do_fC1Zp7dBWv#a+4+y<2!w9Z_>XjrC5Y5*p2^9!TT^* z?aT=*T`^+xZou+>Qf~0xdB5a;VeGzIg5Dl=cVd2XZTl>o-+?iw>&#`{w;*>IdtQTCikbmYP=wjyCkX6%%&WQd(PyAlsURE0y^>G&NJefcb<8Bk=t;Oegw|J5!M< zHij0m7E80_#AZCl-G$=@ORG;TnDh$?76ln&=IAjQ}SB8Gl)>OhgHG?~J z%``)z!J0;Xxg0?nZBHT*oJ{=h;3*DzyvAXBUQfK$g{O#S;dIitIK}B9WrRbIK0owM zB#cS(%_bt5kb+q7zYr1i!6SLWnXFV8V@qLz4vDcRc;KxOHUVX3#$jJBjK^zihhV?Z zf$udZPTFa~{W5G6M3W8-by-KuRTmBizsG~N#6KW{Ji7tTT2q1Ldo4q!I7E$B%l+h5 zzd|jdB7&tjb&Vf0p%db1X{LMVE)X^e42!T>1@r&6p;pPQ^|EtJvRbim?DZMGOAAgUW1LVFx-RGXdHqB zf{P+%qmMSD=}m4ex+^iuFz4OJhSA;ag=QOVZ<9fm!OQjng4H zp9sZ`Vu}tFj+;CcGfjGKrbT=abzn|>s|^AOM@G(!k~oStw8WLc=*^3ol@mK34a zY!V(2W35p~a>zH)ll+Xe7xE-o`q3KW;rt_(-uFko{)%+0vUmQ??3!s?NZX*vz1E=m zl!~v!j-_$4#feb58~~Hnn9ygSc%uJI zTCIFGF!6gMw-jA}bNdbUv~3dD3d5=H{AOalzLCa!K0+_ca;U+xg1cY;@6yDq%Mp9q zRW?k`uG)CO!dOEWb?{(Q}%q1hqdXk*!0KpCf*>7S;B+d)_M0wS0$zRe{2c#*K&gGaG- z(;f%wAD<;kS}$sFrNSn^_~*RjH+|wwy7e(YJo1WkA6ucVg%kXA;zk+C^`F_22&oLN z)P|x|7>Vc2pUuprmj~y1w|qU!Ai->Wyk54D0TNPaK8jU3s_rf_y*`Qx9)v3K4d6%g zbj8S|>EFgZxe(*HK{NndGX=jgXBrp!c51$xcn%f;V=_bHyoh=T{wq(jmW_`b{7?+G zC||OH8>1NiN{i?~(1$@VZQh&k>=@u`h2Tx%u^d?9!j30fI{(k0R3CEKJu8$F5vj@${B)>;(*Kzjj#OdQ)59fvdo!AUp_G1zhPTJ2xgisiJshfhMgh{X$t- z1fz8pwQdU$A!hJ#i;_}(SE5shCmqDRauSq+JI(Mok&t*k1NNkiCo=(YV8XpaqG>nP zA=QE}6_#eOjUp*9&+ev!beNXD5R0|xN8zG0;VeC5vA3Lhsri_7BZ;nK{q_^)n^V~+ z+GP9myyXYKoB6>@+5XRf1`o=e-=h;Bm3a0PS38lwMq~M9=ph965!ssj8`4Go1c!w{ zf`Q{JqHp$<}kP7R35|xel2em>`6EA(5>V5wf&zwL$0<`8VV*DLD!HC|VZ589@mj5#&}B zgfOzT7#ni>a8~zE+Dtmz6<_YEOTZ)P!N&5zx$lBiW76_V5rs+@I5-8y6N6>ZX-obE z>GBL708`ItG`&ZBnLvr#dMcTV6SW3hs~&=o6lYk3X` z?g=s5E`OL}33hA1UR?KQs!%Y~-yU%CH-wIPwt@F2#c+!uK3`UYo+k^k4Cs5$Xm5)y z^*_Ybqw&{n22>Z_FI)||(%KsxM^3{+Sg?g0grE<{s%d1Dv3gfh&hsk>zGt*FrI!h_ zZA=({g2UbO^SOmGlpP#KJ3GBl#6-PHwOl+m7e08DG0ouq?Mrq8@w7(pxCGkULGWq; zL^N^oQ)0Hd?&%3);qOG%0>PgzD?``(Ap?|zQ&PspmxH@5rVO2RZ^+ZP!}fT;6;#Xy z=1NGj^n{^r9EKkl`nBlWErO_Q%!UDR=`#mJStRnVs#C+f6UkHVL2ht2_VO+{8wDX# z(rK&qsJ&9VAoz7e|L?X=3lTW?mdK9@j!XsKU^lN1h#Avl%B!Z%8TwI)*DrAOGn^mK zG&OobSg=-wF{IEAEB_^B-rGrWjw`p4M`a3yMHEi_3-{@q9{hal0!+QBEv8zseWIRk zl<|uK7z%+~Bl#|+YOpnk;LGK6T7w8oJrjc&C=13r(Ock=1$;DsbI=%o) zmr?mMUa3*zFElv$XwRyFq-06-smI8>v5qc=$|LL4@Nnt5hcsopl>Lm9_q}qrZnC!s zgi5n}JmBhIWwE}~ynCm=%QZdZ?E;xEle*WAj?>S*1{td|o?@vVKCma)PbK5^6tnW7 z_~99;Gx=JZk{jg>;Y%@S>nl>9?c+n zbwIw1T5JEl@{;6@%owrnnKHre?G&ph<>GTC45l4loE3!|`1z5hGmuITNwx-4TWDjf zQ}nXu4qoSzKk}w&e&CR*$l2yi(X#xmQ+%P`5o6EP(wlJH99<-~x67A8RbrZ=qemxH z*ElNsxKY#3wtweQL(%2=$2C$qUlWc$4Q#F*xA|5$9~W;tGi3)c?wa`8wDH26u$Sjc z-x4i&QXr@5(%}0|;RM`tZZHBqTTq9vZLsFa-)h*cj2c^(R4WNMo9>hU&n{_9xZd#a zSoJa#b~|VriMeu1%s9=$Jms(4RiB0Mpp^#Q4~pM=FzY)K6)7$s56T7leJ>#zo#!j+ zf5{K|{x?$_uc6kgvR1E5;j`T}Yy!u8kuiF3d-|j`w@@M=1N$H<{-{kRzYbg58y<=$ zlSh=NSMQB|*x_#?64+p|t3rg^-XPI2|DHnY(uLIzXAogV^Ju5nnr9*S0ktPle614$L<3 z0QSvby-VREzqB-{U3d?J6O+xk@_?;WK?#gtjn!$ro{kaD*@iz-+msG5QhgABX z&zhnWKTH5=JBWHzLI?@qc!J=iJbM>jnV(x>kXS4oX*4YPwKP-Kxl?AsIU@8##dQ4Q zWc4=mewhwhF8-@=l36jq?bwbmr*4oK_L}VK*}G z->Lw(h))-%Xi23##0X$br?TXcRsN9udLq z>s?kgOy}Y>rU%8Z-=Y*0+V9)bM|SQRAyaAdR&@7n#CRkY*L2v40f$A)rgWaTp>;qk zFT)<+gF9TqED?O>>j^?>gbYauQ)O9lSJd1V;oGfZ13A2NYJU{=+W&S$litKqQ+7}w zooV_Bj*c{;$5IoxJCcFu3Rv&QSCP(0W7X9C3-KGKm?$DpnCWa(zb1ExoB#3z+u^!N ztd8e{KzczkQIwoxJ$&(E+iNXJy~^btC;b|b=oq?P$Md~WBqBIwDR>nAnW3m);RIe< z@2+5`BK5oZ^Xir_W-I+hP|(kK++sQx+A@_h5o;{C=T4}yks+5Ct~&x}+|rsNKnz?q zwk~c9k%mN&5DK+qRQUv+knpT3gj@;M2brTY@ZZ~CvkxN7Xq~IT;JOqw01_ZOm7ZDsz(j+c!PCiEd(RT>wHPGFZuUjW>{YB(Mf(5zQSzfZLJ-t>FmSR#boYx( zcpwkCMkY!~#*?3nCUJimeS*_rv>xcp#$H=KbcUR#3|EBIf-`K~ZV|F(Deotoh7Zdr zh3d743h=-vLZ~IRTidFrMI>2K($lHpmB-)L7Ta>yl-1m^;VxPt*SuB5?u|dSeW$#!Py@x`cxH z^uR;v_Z!E`C5&7{1+zLBpu3KLhwISu|& zVu<33k-;)AO}sXg#8#&GPveAZ+s~R;f@OAY9ljyGqHpB|yX_W)dF=e#9A7o?w~`mV zeruk5E%*V9!)hHl8F*^)JXL6ze@O|o;?eIxEs=$k$t$t0Fkee2@yN+@bwW;j@oMN* z$kw1wH%Zhf94AsJy}rP1O+;@u|bpqyu7D?Ot>L*MP}n zm-X2|cd?^v$?djkH!m`mDu~id8`s`OB$E{yXe(0$6k^SM@*@pcBam_`t>XG8LdQ` z_UjNK%`}ZOUa@K7S(7P5+ggSOBPf5{(Jl1N48gBX?-t3l2w*E zm5<`=KgGGsV`GGORIdh5v&|M4Z7}@|_McUivtB0q$}7nLQ+%?J&}Y{mwIC#|ql{WT z&cvBrepWvmt19;0IR$>%vErrgnZ@5bSs0lMn|)U;7OtU(As=Tr2cH^;(Eg$DFyB!Hm&^jE5TBl3uFpZ08=ZNCu{VWvO0>d zi+EyRIKjrv%{?_@P+@I5;oUH|u{Sb;{AvS^##zow;Yl;2GwvZQzIXt`ey~V1MysWp z8xJa5ovW6Kucb*uGi#^i*&B)$5D;j) z`WaWn44Zg`998?H0iC#b^;dPbNdh6oFU{p^^OooM{{1`Lp4{8nxo8$%Z&fiksDHMZ z_iHW3|55UDOjbj9pAf_hpbL?H>jC-A(;*=tUwpYft=lN0s?q zIXx^{+Yip%ft85e#nOPJNkW-LiHH`sKV&CN!7~ma9=Q@cIXPKaSlG09OV zz{$zEqvqr3=?RCw!v{(`CI5)S%+kUb19W^*gkcV7<5@&P9Zt2bOV9;A5`%H%N*u86 z&K@P|z~Jx^DW7B2$shZ+$wCHkvQWkWcJ$N3Pd4{_h#=ArnK}kYL-Blu<)6W7p5$2F zUuhLPp4;H~7+|Y04(&rCkwxLJdMyfy*w7~;Ver>aqJ`l_>HoqIQpTx4v#hBq!kT-0 z=bap0!roEFdhim*&>4nBa<%!_-@if0Zh`2hGCHF123A*8n<12meb`#*9?X5JH*`Fj zGQiCii3i*Q=jeo{W#%HnAh-O5N#NRXVu}mj%YqV@?)LVHsVTq9v-LN_(Vx%>G7T88u8F`eM>rE3>IS|4hq*4NipTwH83li0S2!d@{9f;$Cx$%FHD8D&@y^e!NU90Zw+WsQ3- zV5v(a-37By$CClJ5#E*S7tlXu1zV`*JkIvVPA!@$Ia~h|p2y50j9Pq-=f>|C86_np z^)LR>gB#qs7Ac@yKSt*Qw@Q|Vs;)>S;sC5l00mcvf@rH{T)_!(b{0H(?85lTtKawd zps_OF)c~Fl^-Pitq}>{I5ci7i_#kN)qd2t93^zOb?;ZSrZtZlUPJ07!<*Q4QX-Gp= zEnf7J6nD?Xx4BeOIhsx26qx8O%HKjsX68G#RH9Ehc@|{h8YPus-nk>bIXc}Q@-{H! zG-_HX?Bn3Cho&{NQ&!`^c2THU4gRJSfDzpfdME=WFCV~ALOyE}A#I+iTTys9smepE z5iJ$z9NgTavT?XS%@YrgBA^~FN~lS9c@Ek)b$e?4Ms*bz6%$dYJBEgJa~tpH>C2HN zH)w2&n%L0FWd~TA0j@lVw&O5SK~M7nL&;~)-7H5%4&X9mWIqXZqn4Y04d~TO1Rc66 z56-wD z{alOB;v#fh_IBjfM>PJ!DWRrS_8yvgO8t$i=(?FQ9kS=D^WQV|Au;9pEm$%eI%W_C zCj-0)fVdF1(nAZhv7uB2^ZfT?R)X1$=GHSE716Y^_&j-ZqH7HU7wY)~>;+(r8wk~) zVrqaIT^NSIYJ#3}qlw7MxIG=tw>2xB|GP|I?c9chs2`kvAUI>%b-Wp=yew-lHUo#Y z;4wu>Zw2veNDtW_bE_HqG^HqOfxz#Mx=owR6M?PPb6dCqzO-AR-D8J>E^{ zY))PT%EdF5`S-Ix*UP|~OTprqTkqxwa*X)E1<4DUU+=KF1 z!#z>*0FKMLOR8R3=L)Bq7{{i!OKR>`4_qejOm91?vP0VR->o;#TR??R1*}6)^d5?Z zdi(CiSQFjf;!MPd#_eL6RHeIbF$hL3IjujjsV04Yqi}YMwI0#3uzp$Ss?>;e4g1bk z#uSO+=>^%D%UsF$_}wygCWQl;@DC4iuz(xyOdEZRO0|c<%lepoScy z3`T%7RJIn1G>nqy6LQmpl#M>#XbccJ8>yi3lt zzvwUw@ZN76WS}SUp@&SwMLGyetn@>;D!9}F%`@&sA+_&fld5Ugu(w1^Up#w2k!@|E6g~q9>Q>=3pxhuQMsP3DwT1(A$RucW4 zUX+{IxIL42!YLFGDt>I#_oMocJT%6K4xpz5)=QJBetx#sb^QLdpGtJ0nz8($!-04D zK8i5(xvqi>YwRVF6f8c3E2ro<;i|-QlHYH}F8NF7Uyl`;KV@eX_AtneSS{52#PN3K z)CEpwQ3W&IR_3^1E>p`1*h%D<%cJl4>L=cl|KN?kbfmS`;d5r;R{;t%9YE zz2^(NEVY}>{*##iTo0cnGv|N%Q+Vja*=}>u=5oYn$6TI+u|^#LtV zN)3IjWwCcPIcM)h^8*9!Fa@(>YOx)ygdQGR0@MIV+Xj@nZKl4rytcZ+2kgx|a0lYu zDRboL<+T8~EY&||Hx)D1rdK^|pw}hY#7z{{0wCgZEr4FyM{VS2HpRsYl+-+j!okaA zG+f{}P2M*rR{so5HnYR=NV~K$)RsE%<0R&gqo2w;fWJplOkk?;t zzEa&b{$wucfxUM#5K>A4f+0lql^Vn$k@*=Z2Q2~M-l&$XzC7}x`Ir*2LjBz}BU(3H>%m0Wa@|Gu^Dxl3}bbi=b_WNRATPM%E0|q96p(&oh zPe!4T%Xi2oupaEF<@J`aZW()~)idERP57g}lnvaKmDV)g3mX zn*T?dX?>faITe#S@Uh=+!R#~y{cyXXmQqFSnx|g$t^An%57Q@niHTr8S62Pbm5PAm z-dIlQ?LZ@L)_6fspll|L7<+9rdpBI89oP6C*t_S^7bAB3&Ay^;rH;El_vOoHw1a2e zXFfHvT3NTlF+ z&79lO!JNIapwXmvLrX8q(d1#g@T<|MXJaVS5@OioG0x)0+Sj+e5o3o@x%tb-!_RYO zKA4TN4=cX!9J~hQ?gzCTRQE}P&3@S=Wg6?t0iDaTe=d>TZIHCZYff8%kc>(UI*c@2 z47yqGz3_UCY4I%w>`6V9(WRr#=wbtk!83~+)&VIB3JJ%1(Np5ecl9&X<7$U(LlenA zYZ2KiJaAh*t6pErJ5_z%ik%9oHh8fPjHUo zJ1W%QqObnampYQlJAKVJ<56dItBgu;HXvDj5M<^lpBm{W8L|AxX7{>U7aL zT(;9=BZCS55To~A^3Qv#hwt&%#b3&xzSqVvTdpvCi`H6SmR^u8vGWZ7`}~8etOD$6 z+pJAUP0je&SpT9)*3Rp0txw)?I!}o}v=NbYa7&hUvE;Q#To!ip?HR;m<>T3frf@D7 zL2q$72t-=Gz1@iSxGB8+E4}}&&n`OYp5mfRDLb8QeBcA1@{#t`+s?Gcd+VKJJ3z8w zy`1G^^XhA8f>pg!$Bu22PQ)J?|1MIJJV{7FAOZA=uNa8}gxuQ^wwkYviZ2=Sj%2BYyBBBx%Xij~IgO$~oHNg;$U zjlH=#FvUq~T6M9$7GkhJ!tLe7Lyu*o-^2FV34z2j?luFYAYFFFrkp2aWV>4mYqfDK zQMXewb!X0M%(2(g-xZ~pOZL9-nr_9p66a5hI)?~YvN!K+U>|lT^EB$_c5+S-0d}Xv z3K3(IQg=p6(VJ5mXNk?f_SHv6!uG{h5~WPh5_n~rbXu>A=kbBsnhz3bG8DFxkjJBo zJ6``a7IC(&65!(yX139m*0O)cbE8&yU2JE8$NQnnhmfqDh&bnbJ7N@z)jub;-68rB zBFoSc#l&e@nP+K(H}raC_JFnC(@O3^c$nATV6IWy=s}P5_wujR%CIqVyY=wjZJuz! z?>VCx4duG#u1OalABBOF70K$avJ2~vI&0diY^PIA{<6fBIwU=Fa%Z$w&QJyVU-*Xf zN}be(<1eX*4@VUj%bUL6f4cgHi5&7LooRbNi6e zo6w<%GTZ!x9agnKgMIW_n!?NHK5N0JTD?Cy=r#Z%*xlhv3ku@$xnkehz=+ztUq|UC z;#F;{zsp&r=2u+0QKdS9`NXyjQ6FFQo0rXQSkxu-obIwWmG}J`=uU6_NBp z30BzstO|vc>0`@+U3WE;&9eWB_GtcncV*#df7h_ZXA}S7vAsV8Lc>v_3EL7}aRgDL z%RJY2g0Up0=11y!m(*3|U$xV)B=R$x(EZ03A8+6twJl8FX^QdB-MzoED`n8Weoqpx z*XN?B+dVuk%$$}Qf8GjPg#B2*Rn~vHPwjeGCS3QfZN19CNJC0?`Ki5H;qK1_#~w@F zR8}Zp>S3qv<%gZe))C_xnI=w-K$w$5Oj|H77EJYL=zDtw?_(R$&*2WUjP|FehrCA@ z-%LH$%`K`$-Ck91LJVR`9_%k0(TbYrbzX1cQQOixlt)XL&B0}peJ0O#Xak0GS$H4$ zsJrkH(Qr|7Mw)!50NGR%pX2ZYfG^w*9ekfrlEZks^P6$(Po~D{-_Or2>^LLJu@PaL z{5hc~$VS zseRiS;{tC;k3dS~b$_Uxban6prN;Y%#)HgKk%#ATeYyVI*JA|s?o=0UoTaJ_e>m{G z{KI44^|7N%*OoOdiLQJsc+@ZY^M(>i;Na&Y(NT6@?7JVd-sX_V4fo9=zL)c!l6 zOMUH6y?5HESJN+ertJQ~`2D|9Ek_ZGE8|`or_rh^LjozD(kK+Xs-V^JO7LGllcyR z4#xd-vQNJLG@`Tw`v^7FG-u}}M?t@JoMX8?y$M&n|kxNd!@4($6)|GkK;`>F~ufsZ^34;HiQ z7T#AU1L!PqEH|#C~f-g`C?oRb4$gK4Ji@=f%VI<3V5MEDQnh)t?^apa6$9Qj{M*FEKWmO>jc-%b=sz;LZRXyNCAJK< z;th@s-9DOnp1&~1dVBQercGI1%bS4cQXX@@e;O1}GdRKgxk_3Us_k}h0-Fc$hdV2M zeb4*$Lw;uuR|e|n3RSf~aLQj8xjpOwoioVHs$XdwOJlWtFnve#jnE>Zqe$)qJ05{q zTDRN%*{fmys3T9jvK1o}_`1z6O^R)KVt|bxP=#JH3NFGrBcIC&E!RJT+ zb=N(flstJ=rF^zoF%~7Yu|(acyD-lE#Rv=#@W?kF(n@=lwDw+lE4=+vU^9hz@x5+_ ziP7nn*ycpWWW$N?_FL=p>9N-MGR_FO=3%<<-SYHL5zRi%53G$0HT+JOZ=; zmB1J1aHW;t@gadA)e7^L1#fc7N7dRlSpfVzI59EdkUh|&RqHyM9Uo5_B~D+l`{&Oi zXC@{oHRy?ktE(#xHL9EUkvzP4#b{$sZ6!lh_3V9Gwjyqt=UrZ(Yx(DG-O?NkyL-dj zXmP=Am?oHv??NA74lXMzY+EqA&Xh5W>Zd{Pz^v9!*PRaWgsGU5L5ry`91{T<`pk^GG+mq?X5%ycJ8g%V!0){t{Grw{gtsifS)cwxnEL(wRY~4)TpEn{>6Txp*Wn&oj z(|jg*|E^Vkhs2f@9is|#N+zW_>a3fmZqMpaJrU^x__q9^3&4PV2?|xmR8WD*FWY5=NtmUsRuNCWsd! zAE}YE(WB&j`Ert4C)78dde+?Rrn_*j8#+a|5li3!L=@{$B1l|Zj5IyjkKS=#DW##{ znQL#aqnMInfOs9_M-G8duygue3n~a5bjwlPc-5y9%S{`?hhSwUsFqTw>oxx=24fQ@ z023Wu$wu+C;rHQ~?pK`M!*sCTQ<AhniwJWLXV0Tg;UKB;?_q!XNYkxuZoi$jkLL;J4PqoH_~PE zRunjB*Q;W0r=_KZx5vqSiLNuWbk=^vsf}Um8PoXN@;UqNkjU=TkZ{1m-6odA+_UCM zwG+Sc&vq?{2it#Apts%AaAe34!Iymz;__ zugZ&$kJlrTaCCoXTi(~-Pg<#oltIZU$jHe2k?zuMM#=!1uA7#UL{UZMi)WpVuI|l5 z!+e?I1(w=6s-1nT3aYU#4x|UQ-<87QOJgJ3ftz1l(u}@D*#Fpk|CdGC;9AHPUoK)u zWL`BV1td~}03mkmNOy+fLH9!T)-tUK1gLu=ks^@>xBdDApwM48h&r-yb#dQpLp6N` zFJ2fFr!O!Uv=fS=qHiI+1Ue=T1gkU$Du|ZAN2Jvf1+XL_hh+f)6iD3Zt`b2AN^HN& z2hM7P7)Rq~bWny9s-`YJETGcfb5)@2!?X#gF1oc8)x9f*gS%i4-VzskR zuu7>D8@fvdN~DT>whhEC$#?!*E&Aq+QM9Ogr;`|Jy$YBnPrPM^1qupAnCXYSJ|RU( z5@RAl>3-GjJzvv=tdMi3v(@_R8{Lz&)8WHzP3Hifq@2OI& zv4yvD(@6KfEN$KFRPpgmPu7eJjZJV-2*U)jjAOdd#frQV z<;nLBR$k-7ArF&)zT+IFh7gp+0Lu|2-yla3c-D9WY5>pC?}g(46b$74io z00Qb2Nn}PqFLd$o)~Nuo)Tqa{D*M&jZP{<*`41|%?V?jzO9pyg1HG1PU$G?%Om5g2 z!bdJMKYR4sV-^Ej>P0Fdw#EA&q^xOD>?Cg$P%?_S8g@0YuHHBxbsct zuk$bhtrnUL1R$*bPh7Sy7Z2QwmBr*>S6k=h0bl(I@ISf4=OqnDS~H`i4CDagCHjI6 zvA9_a*yFjJFjeV|dLS=&KW~9pFseHl6y!BeWzVp@ZXpYe`RNZQo0R)^x;*0~17j38 z63xq*kIdcL++XGy{l4(8R zWWb!x&8*p^D6exTxtZI2El0_Z_XT*4XI(H^8EHj8%L3iX(5xK1{pwn4FG}+b8-$(BGpGq6^v#rVFR|C({=^}f(-%fXe%$A}rVA-xB2*-dp}iJ@lGOh_vbFG12XO#|fQ_ev z!S!byx5syCy&EgnoA2QC^wim5wa{>eOKP)q`83vE3mZ9ZQ5BL1J_@F$>$Z`YtTZQr zDuQ6laTSfEQHd;fo~Z&-u|03)V8vQ!{FT3V=c~7TpAje8bG~a?4>-RIPrL3VmHfJy zo_$aFt4;ronw$An2<~NGX)y~k(lX_fqM990cv#@D$Tlb^3Wj=H<&P z{j(ZuX#bTDR_=TBv~=HXBcs4Yz3GS;uSju$Mlz*OHUBgUYyCw3mNPAQ71jsSzMhvW zoxkOS&qM^e&dH*$T+9{q*@L&3COeiCPkl1kIQ$co54dT-AIEau|AM6fx^{vZuavtU z#UiL^wc^-~RqY1^dtzeZmE&Y&QvfbwU_B$Hjej)!p{UXaXXL6{i)`3OebBcAmB#Qt z1}dDd^!=_Gs8W(rIgnXh$z0Y<%l||gjOg+=eZMlD@~(M%)xpy{_YH&E_zv&8{{pXT zD6U3@<=*qc1@!&U1i`Jqg7Eyo74J@~uIt|(=Ba}}S=mn;i!~|tW9;Jvr*oehXVA%C zxtNR%H0?J7meeqpLZiAz&nOm)QP%7R;m?E*$#B<>;Ar&6_*%Gas1OKk9=dtYy4vZ5 zyz3l!Ze6d6#>mR{Vxl2h7Cy|L30H=F%(| zF(tAn*U9c()EM78*Tn#3&V;+*_{TE=gIl(5kkST$ZZ?8y<6d=E*T3st<2=&s3a-|& zNQmMyZj4|@pRoPH;&;U%nnGY<-pm9Sh=!$$`ZW%Y*CY}WpUQOTHB}J>x^$zeT$v|jMI62~?Vfu~@g}0Z~RD(Q!f;tSSq}bV4 zcR$|BpcB8c9?;4L99!l}aA$wZ!i`Mp;c4xM<%qr?4$Nv$_E(U%00?g?7nuUR8Eq1{Nzg!Zy6F&XRZLUaS;le1h&28+4UnjOk zMAg2iOdN^1F9?iBumExu6Asl&ksEz~dw%`J-eFl@o^49l>AkK4D|Vms)ZfRId~(CG zzH24N;$O-9sOR$z6%E`DUf^ynuGKt0QZ|(N`))d_^#$Rg9kKUbMF{V0|krZDfZE%mc_9w45cs@AxrX9b$_x!!~;<(#) z*KIQr3T0~fx)TV$`m_x4_qL)xe#1 zYgjhiW6OK~NS`+Ecus&hd$(9-J7Cc3Kw@EOM%YSq33~fvZ7U`$H=-nV>cBSFCF87e zTBxyFaxbpLb?z0N0;QIapP$3y`s}`!dc*960<$I`%`qpOwU+oAo$fQUTfug~3%fU# z&BpWIz$^9%X;DrJo*jP^s4$=Nb?v%&!#O)TS}MT!(P{4EYkh3vz|YCu55J373-xT9 zZBOHnY}M`F`EuHWflaPhcrTdSbZ)bHxV7VHS*`$y(oBeF+;)O(Q^4F|be!AD7m2-6 zpOup1CXWYd;|ACp0WES7Zd>ZK36?3+wR>vg@s9%$QBw=%5}0+a=Y|g(3_bsrhZAy>d4Vu6yJRH6qGK6&&d3)b*B#3G+?6WOnkc7cpN zO{Ym9TWtd zUS3`VpG@uaM!l+v%8xVCE-cO8qcP{od8-QZ!U-Vkr{X~q!EPHhToFgMM-SjZf5W;8 zO73r+?w-w?x%BWunGOZ(J>*pd1N_QJW4PbKH91ELVQ+C1o-J8Z@r2m{g;i%D4uD#m zvF~-P=coXt2h$H?TJSIoJ|ql8{PZ{eyCa*P+j}5(^V0D%A;xPmlPMELls_(Tg zWkruSQvXOkW-cqb>Oqq15)tfKZ@VERmWp#ls!N9b1PQ9>2Y1x$+=O)=nz@_gt?~$|<|c(}ao=t=rC6<&^oC_dUOjCE*mI0=zF~ACV}} zk7Coi+p~x_sPqUTFt66Kod44TRBvcInp7zyv=!>CMHLTN2ltqJrsPjgUM|Ke7kxlE zJ0-;+TIwG*!c4ENM1x%APr?EF%_ z|KSe&&a4qUH-G4*+w%vB__s3j2`|2QlO~OB?I+kuNTMLDP*9N}N~6xJJ}G$p#=vd* z$1Idies8(aj&sY?mFq+B=F^?isdbu)Nv|wPp`2arr_WPPsv~4za@%iYS(PI;9Bn`M zpZXiOym797`7o6?`7)seGnP=V8LOs0cwBKcjN~2CHG}IlVd!NA&#QImcC2-!?C!aFYw~H>&fpZ&3^&YLNpzlP2p*n<+h&nmuG>ko%yf8;G9b?Un9(W{W*FNZU7XC>Z2}vW!1SAz9SjSP&*Zgk6n}Fy;WF0pZ5-x-8lw&n( zOj8|>F5@=nTLi6=UufxX0G_P$ay=^2+3ET1X&!{TJX1U~1wmiysk_ahCwu`9v<}fLp1N4uTnh1xt?>d9P9%9ogZQ|{ZtnSQbPe&I5&}5 z5@9PI^;*rKa4Mz}9RFU!F%bQyInd7E0vX%ia z`~XeBkWx-62bw&bjjkWOEB=8BCaM#KJ9T!f0dUB(Q55J$;{$3d2<}D@N<>W!)5GKb zMoU}=pGx*`FBmbiFF}8Be{2Gnbv*6uYuT0HK>S}@&h}}-D=k73(YY$>wo>OMaHvGC zJaQDcSmS_9Y2BlrD@o;~WdV1bzg0m$ zM4H~14fLhdTK7_H8ZRvoJq3;4Aj5U?*IsIoJJ(V`&dJP375?1@zSgmH{h&h1i$+5K zTTac5TkF!V z5?7@(xC4izZ1H0N6{h&bJVgg}^8XR{mSItSU)b;<2q+RN(jlUBE8QTVG^n7o z(%l_HNJ+P}lz@~&_kc=EN(|E7HFV89d;I;M_j*6P-(NoHTySQeefC~^t$W>j?fY0} zO0u0->1-h;nhwMOuM*0v6vbHxXWX%j9M%EQclnGF&U<m@A1_xa$H2hpM$Z_3lVbT#am{Jvdr$yB4?GPg8NNSfm#*K@!aSoio7V^ z&eA__?SaF#3oq@e2l&6ERkyb?`-Mb93p-`ZMV|}kX3mg`4t~Z}{Nszo(<#(0J79bG zu{_EsrSs!l)Q(Vu^n|s1N`^#-T_)ZbM-k`kd8%6#H$Dhv-vWqH7c2NFYP8JgEW7>L zjAqn+hA}|{f}W!3e51BT2^BCC7y~MA{n$=s25K5Sdqtk!u^qjhNNBC@tI+b3A-QsP zlbBKLHwr8?*hd%ExMj$DXwlNUJc^Ls{uq~5^>kvRGWI#t(gF+M(?(aB(}{_VKMwG- z!@ceLZprJ7pRTX>v9HOmUza{@+1hS0>h0Ecp)Q2|Q6L-Wj=9}}dTkT8%_An#*w0Mk zynMV*^`$E5M|RXzd9ZD7i?t$4h zu`*`n+WXR{;ZAyxq*_NLLCjw{_I=CKZR)|0NiXPt{XES3bORIFABK6Ue!}YT; zo;W{PPm|=DcOBPR7`!lCgu>w?>6yq4;ZX8rSL|p%?>bw%^tm*eZK zFK2g-^~z#)j+meDz^2Th6s+i}2t5}K3aK|!>UR4Px^DcZ%rBm+DVD>4;~}}6Y1q^! zY}e%GQ_=!Pw{$koms?`{(VF)~f$-BY@GyTF>TvykeD4L47gm6v3@^Nf#D$^(SO%S#G>%D0s)Gz{ ze3}0AfV~`!3W*Z|x2Df(`p^0NMTTo-;+@T$D^!m8l+nk3Yxoq=JjH=aU)tInTpYBL3=63C+T>&DLrjO}Cu3v32Olj`pT-7gdXgKXY=j-;@vM`7kRR@M^^SXV-oAy!Xgk9Y#FY zGT{>j-trRlR%73`%A4%YkzKWj4~H$Mv(`R7&CaXD5@+)jey&rW9>j3#RPT3i`yGty zO6ECJl^xsGU0)1{?zzyoH|=EAOxba(__qCCULHlR^mTQ03C?=tEwfIP>o<8EEIv`9 z%Mf+nRTXV<2&Jvv%UC_=(w_A>R3R3xw%MI-5ZyhyqII935-aFfqL(Q^U_qhWS~gHLi1WVi`s2G`3Y1;n<^2TTOy>}T8+~Vrt%(x%#_wVTEC=q4M&7LTE8@JohJ73f741gJ*ot>2!UT+S#gMY5MThHQw z&9OK=T-1g)?u0Q$>s6UWp$An2CePC6t}e86Z67EPS=F@e2Xh;^40m*X1_#QtN(vH1 z_S>)}55i+Y>BX8{HYypTg^;^Vdqf7Ft535%kv+`fOO$GZY#*$wHi2DU#z3$BXv4j% zoUnZQIP3g2Uu7G>77yl=T2B5RB8+2FKYzwI5MIkTY(1%|P*YL4SB8JQJ%zk)7&*`Jx?;zhp`-3>g-Qqbobwy1O=LdI3@0u^~*W* zF6wg6{rXri!)2tW{n=VJbfJeCYMG!SAaOj(ZRp&eL~h_Q>v!3*IRQuYFN8w#0Vr9k z1w|U@^KA5tX^MX9<;kTz`I$+Fuey319j|I=Q zEbkL*gTpkiBG0zXV`y#qnm6B6h@4JW#fWS!DHel;zcuYVC;@)qKDAQ-+xZS01F_Hi zchTMYCE7iYsV6B{>zgGNSJP|JfZ33|SU0w-Um%YQ2kYmsqG9!~arF6b%r#qvk~`;v za|^KPrM?W(UjP z9w0!(@B$O*H{hI=mk1u|FonZ&eyqapxJ$wn`3`leX3oCC?02X^FYbeY6pQYKb_@f+hA^u?&v7=%PJ^mS|VB0%1dL_*GX0^m;3>VaBPi43Ea4jkn9E z6TOxbN>>RQBjBpv#{?uO`Z}QTK(YAgk-gNFOlYZ8Jvw5`WwPw9Zo0&8#$rANI-CtaU-kC~<0x~=EzNo?eE@3vkB zFN8yZ9cQ`E3QyTxDi@3b-{RWKYB=jYcU|7Nxrx50IAyMLEBkIu9Gjt*J1TSo#8yye zMwYEss^28fS~KglWs2?w0oee35flg&po$`IHo_|y#U>V8haslEiKf}&#K!g9 z2u8z$*;ADHS@x7YS`Xdcy+AHFX_62`bTP3%opm{F&R&25i*W5P-;76=`}vBbmSW2i ztvxXu^@Du3zK=^3y+n_$UrE;eS+Uo&C*gOzrCaZCjfv=cP-xNF!6UbiKd3tH`M;*Y z0#c3nqHecUuN4#&u9z7LR4K)BEkXREr=})=*BG_Spf~l<<0|m$oxMcO7%^n`AhdNL zC>U6s6szlmzWe#aIM+L5+tvAD-IQI^{RKf%nEmJJ=8B4EipL$HmZ(3 zDwJd|FgVK1eB`CW~Eqd1JC1)Lg-eMz0XhJ zmi#u04aK)g?cF=ccK#AE|Oltsk(fpys>@mJB>ZnHwjo?RoL4CRRzV{4=7zre%#3Z|;Xsqolx_C88u}=HJ_!aJ{z!?77h2J@ zyNPJR6OE?+{kSjAykHd5aer(gq0J(2V$Q{`V)4QF_`LR7d z)?U^1~(D!)CBq8eBaMTljLh=OY zu?`2FgUzQ7VcdVZp0IR!Simi9pC;SJjj!H2$G0&fls$XCd)n$At=S77c08d%aDM}! ziWPYQFqZRTxMBVE$sAijTK_6AGBJMsV-Wcy#QHhwrY~AguO|dsVX8dzmx#Nlr%vQt zdgvW{bR7sp^&q(4yMX|BN?BR?3Uvy7+vJvM?E>6DGgu_ChA1bPW-s*P9%zQQ$dXZ-DVLTD3ef>hnl-3=we0*SVwM^bv%c&d*f?7rRN?1`Rz- z={Ra)z3cNqbESzQOOf`W+`O3?8Db#X=sUC%kC(n%f+HIzX!8?AcB(6Gs$GL^G1M#b zpMzwYV4bK<T14a43 zk7hu2V&8lqUpMPD6(uNfEj0b6L@V2K)jd%&lm=Oz<>w?AC(YBAJZX_fn4}4UWo8dn z^FdUFwxaI}yWw$+_az1(h0^I|%ZY81a+OAa6N-CgVQs z*o8iefl40!u9`)ixVJ74R?K;AnR6JT)(cV$fYTxdQAzg%v9y9R0nro)7OIt+KRq* zruVYguxr`@z%Ax_KU7i=!0BNH=wCRWn~BNttL%&AP{}Je{q=emk;EA|>4Q3?_dAOt z;J#|FH0_%`%yQoY@LbFGonD^}tb+`|{9|8`4gf3l069jh*YW1{NkoI>N&4(jNC^mu zz(snm=b+C!1zY`GT#jm|{G2-;Xxo4(0f}X?nBS6A*bL1X%n|02^u74TGB|;N5xe;= z8>&N+wR`7DGX^^SehNQ6u7ElpET&(D9z09$$H3K42^WOGl#Ma(Ph!7)YyfvAQ_ZuE zckH2EP*V(MJ{ry@%92?Y-N?VMCcH8VveoSfL$OJ?<)5ZG-*xY~%dF*8_;H!NF#3w1 z^|YmG?&zza%WOTO4t22wT@#&?K%EU5y3ZVgd2fLZ?>Tl|8=fJMlIX)-!}H!^0B8!F z2ZRTqricB4m)qu&hkqwpncT9ld5e-AY1WGJP;++`Xgu3*O4iQ@($|+$>q45!FOw=V zkSpBTPLrmRsOdG;eQ&pH-;;BI$>tn>%7Jp6^Seg#h}T%%KY$1Yp(ySbA-@rBXKKqx zt=>3F-7}4XTngjAJGIc6uRyYk(imC)qY5e;W80s`-Z8jEPOm9OH5^)br4y`s75hh;pJuJ;=7_a00n-!Nw8{PGo}T*|MqF;@88NXQF8RTqD9H6X=xW*hI>yt zO94vLwQG=*c?^m!iN%jj-KM6jO3GFTGPf!w|Jtf;KYXLuqt{R~WeX@5Ivl$i9X`=f zd%;v|eKeUPA0Sb!9$zUFCXxctKslt2f%TxHUMfrjqD$cwmn!8nJ7-lxMJRaX@KE^P z?fHdGEr`;Wnl(=yplB#c@|e60u;4u~pp}(1&LzyB3z4UL94(uex%^ZTs3cJC{3r1W zl&Sz#%fcNl+5<`vkxasM$w2psh7Bk76C=>n(6u8Calw-Ua$r@kaD}+XF*g%QfktM2 zLi0Jp1MH9Yn{M2trT~h<-NXtQsjxq^fL{M&jp3%Dd)c5cSUSG>i7AGomJPrZ_P$V1 zd>@ebgJ?&K*QE$>aV4nVEK6SvhPoeBbc z9o(ogLbkJ_ghr_$Z-a3t{BJML2uv>UPqsm%0_CmndXwaZ1t5@;Sa7ycs_X$d`$Xxv z@yT|>oKMw$TR@Pjq>u9Tln^G04(wPt^@3b55mjT^H|~(n+xuYuu%(F2QQ<8Qgk2-hFkIK7zKc8Q+X{ zD$tN`5N#827PZv89r>C2XUrCBPEf=8_ zf`jGcmr>XAd4c7(s(Jx%C@M(bRo? zeKk6^@81vCSWg561!Z|{-fGY@(9}ep&I$glv)@`<<4)-Vg@&lB5!Md`Fy79Qk&$w} zhHoWU#ZKU(=Bo;zrQ$rs9<2TLZN8*NF$eEifvKq}Rn*tYO138}PA^}E@0u&ZML0Qk zE{@@ZRK|9828z9=*H*l50Zrc1+xxA!xTmj=n}ub_&^kUNV;XdKW#vI5tDIa%BrGuS z2k^mLXt$>KT?A4zUY+qsq9r zEjU?N#C*KZh*hNGqHVuaw z2Fn~A4wg7OIf=Shm^*FcvT*4f{Vdmi`Q%9fU{+etsLK#S@7Hhyt>`_qYzbX;buf1` zm8KDGZEeR}f~b=z`lO^J=ygqTeZ3GvP8I4(&@Nvk^I{{2coxhSr7^XDjiTD1&0OnF zXOym1xt_khev7a72Q4kFIy-$zW9K!$JgpM=C7BxNnDe7{G9_q7RX)0Nmuh%;W zPX)I3<;(op!Tq|K@mGo;m{l|QEQV$@dpcpoW1Gv%1REQatgI=~^pc+2h;Y$eiZ+VA z!NF|d!xC`VzqB+10A#n#Q3)27ZlgE5Q+3ec+>U|*)2(sa*uDp4x^*UzWrlFa<4yht z4=@j*ajIlKkfE=k(O@@oQCeCGzFli^cP{^BLIgQ?na;7bV($vLCmu1Ku=DD+Ki2g> z{T>2xKR6WK-rM_W4r)kFLPA15G40_P5D);uuY#SO-OKRG=H}*Ch0%>QTCZLKSVBNd zE28=KZME02{oT8Fd3lGaqE>h66u^?R|69G%n^Xb@4Rp3HCZ^6eo!3~I1gFwlUz)^( zkFp=py*nA!CGU2|kVBbH`k){29TC`VQ(TDR3_ zC6t&7&*FKL6RQPz9o<8TcJ8bKlX}R+w7QaFxa{e- zIXYNpKX;5UM)#OSrDSFJ9E=q79j-xFHa0vLWO|V+lH$m2*$YF@tAiW^0|PK|d##sD z#vH=ui-0x<<4$((o{omb`9f&6%TC%*gR{cSmF?eZB7A(XBz(a7p)l@>$)*yta{(*j z*ROogalD&1t)8XDNM^-n_-OPppYBOM4t|_DViFleaWEbuS&mOk?7afINKPiBr$=9(DtVpmK6%p- zfQ^^JdFlDL`sD>2R#apGY$Y2i!O6#0SyZ&17sE8;x#n*@ftszcKFtuW0!zHFkNpU` zaemm7zzpR`=}U+|v72kHa$Zx7gjr9NOP!G z3zJmkiXtWL4-9y#^`er8^#<+qOljvKv^6`T4$+ zlas;0!Awhl;tLpoUL0>ZqkJmc?{q4ydCtzwsr9nS%F2F#bsM_UtX2-y8d7`UIkeMNUSlR^w#OjMMrl9D78^`k(kI4(|QAXAjN zf4W4QnDuc&nm|-!w4EEESUM4Lj0$*n*3BJ*c|bjEF%zh|O@9{7!otGF!cy;dogEg& zW2x`q;h_gd)uC#BMZ&-e@95x|M9pBh4R(kThyfrNmSo+z@>mt2`-A)l>jY5 z*-*EU9z=C@b?Q^Nh{L?PscDjOXTjI6n`T?UIULdJS27ctR_oYWS{CbZflwd&_448( zO>Zb0T0YlwDwQqK^5e%3;pjhBHuplfx1k}D>jH|oTOc^VUnYD7UUsQVP4T6Ur)Ps=!N{8zHa5zKtFRF? zA=k3Nc!eSSXJ`!5W0#joN_(TlK`W`UGJ&ozPcJY0z=tc10BtlrK8#bKyVrppHE@LS z@$jghnorf*y1-uxYM@g#wHXJk(a;Z{?aXG5Dl3scetg&njmSk*7|sn14H1xYQE?As z^!Bn{6VzT`T`4N|-bwCu%y^ppdBr3V?>h0cF>#9!5+-%y&aql!a79j@g-_T7^(3Mj zcl8I@70C0wJeg^n@p`M_RTNzM z#T*Cw>NvQ#W~Qcul*m{YqCbKA`$Io|K-C1u$jCxNsIuTd(#>kTbaLa5c?`WIzvH3@ ztMSn(@z9$v#RB9>tLx8i%4VibI};VBd+>^x8upFaDKDLw?kMxvc!YG&uI0#MnKVr> zYYvGF&z_wfZ9wOQtKIidvn}55&I*`*@(A;aiE%LG9JU3x^K?GfVBzE>CL-z`7~nI= zaM7H3`}S>-X7T9tJH66T(f83z#PeK39YKuq_bedaZ>`9pOd6~P!NvyMW>T%VSUaC^ z33z(&0hkA(#k(#oZ8Hdu_~jq%(zmQFe<$+4d}#w%a(gOLk-B0!`qB>eaHRN>LxF1?}(y5isLREbI$@dpc_*Lk|{~pP>D$ z$oXWQ{odhP0&vg;U3O2H4eJjFusRJXdA`hr5T@V2#+H$hp)dwS%q2it`3h>ldR4L| z4yT2G_Eq)YA|kSzDDMXsDjT3n>C+o3m>;`L);mVLHoiRB$>!f;5Atku-%CzOfo_bL zb|S_~CdSLA=67~DuQfLc)XDscO))JGWHuGNI-sOXrH6_Y^KZ^PQA)O-u9wRjcG;cY7*+@c0^`yoIMSn5HH}X|t{x?S zi0tDYNUxh8Eq)sK5Nxu~17HahE3+m(%p2X+iMwO_9dWR92XOInUpXNflvQzD0!Vu&xV4!J=H7_%BHsX%eFu_-F1 zAD7g*?_~i}({au>D8Hklt4sXwcS1H4C8w@#WNNwsP~}{K5_2(~he7OWTsdVjiu)7j zL!&zI&}G%t9Bgd8^UT!0#&hMwj3d`>veIdgdS*>nJgaEE7+LI)jHcor@9Gle;W7FZ zR(GfO;P}{gDM}j*=l0qf$aUl+J(Ng05C}vUJ-vYIra3PBHEHU@eKh@3?oYYkc#LNj z47#O7Dp!^anhe65OGrr(blVQi$zcEqj(w}JUSsiFs3Sl%Umt>znf5(x1D_d08YjUP za#=|LSk1lN-5z@_j^GC_SM|Z@csL2&;rY+ z5$A7FYZcxjno~1*CLdi3Rmse_2^*ZNI}bhx$;C z!%Y8z3@2+zx%udaO!n)kUKs2~l(*tWFb&zKN}VuTV+CqZ8Rjjn+*0)yPnQmPJMas6 zdJ^H76O63Yv|&@3ORi*v(%7kCBf_798T%st^bal`9`&!h*U6w90D<(QvRw6tKsT`9 z==i&(n48P@_%UL-j-#=rzi4a|z8ldJQyUv!a~(VwRPHlUc1@(S>}B`cCl^xw>LpGXJt_H zaf@fSgMa9mo4Cd-4^_ACwvh*#*-w)w}aCx&j*Q>)dK8O?)9m4>fEqxwTMPUq~58MNj!^iFS1 z>Q9->U9e;}!nkX7-h|NdGbg-M2rMOj&q+>4Y9b6-KnB?RI#2x01jEtll9Mt9HR#4ZWA|T&ut`rj&FDN8aYHk+0zP`3@Dg&CT zil;nNyT}!GkmFA^xzClp%TAs8cx1dJq)OtE;7kR-obXp>dg}Km1^gU+SZmxrK0Z7? zzAqYPNaqAY35Kc4#OWn*tR4R{*it6HI z%6{}jx1t%jm?Iks!fAnKv5&7WNL{Dk@FZI%n#8D|!8z}ioSa|Mwy;9efOx|$!ic=bM>$j4z ztSkc@YOn^WRH6#a^#>t^k zoL(3fa33Zh%401G0754y_6LhMeG9wA*2 z@JHQGihKpUi>OT+@KRIlL-ZLTNJN^GO=8?kfVx4B2|FR=u|@v87HHMqHL+_v(HPr- zs&YwNgLL@Cj=v%{#DN6!aM3ct%$DmUR8K0|6VF7R=-MY{-%x3oZR=2<9bws<{Bj_3 z_-GXi#y=7zT>d5{yg0l(Lnc2T=P zvs|Q7-O}xgo@F96>!cT>t}#Rk%#Z{AQS=D|=$c0PGcd$LcwLSbzzn4Dz=(V&TGGbM z_`EU#G=8Pd_pV3~@(44OOY)vCqy+?e?V$|1mcNV{Baq^4CfJO0)GOF{CfL{zk^lM@ z`XMn?)WjIriGaX_Y{LjkmptVrA%uz_aH4KQ&e-Z9WSd?r6|Z{n^dK1_PK-B;+`$Cn zGl-HhPVvVRMoQS&KoT|V@J&wXei>t9ZXGlwy=OsRiJ87dSX{88vv}eJ>!er$GU~#J zzEu5R@BCdU;Ei;n>FhbvGI8AvbbxrZu`$6;P@fHjjEaI=8-$%oCFX2eMIh_5k#9q7 z042*(q5cf#QoUTTSKa>KxP1vkI^gNu5fC5`dSFFAn!Y6+e+#0;fQ5S+SUxNP%Kpnl zDg!WM#-dFHtSI-2&ZzX&<$PNJ?e_w#p#Ti23kEhvj5Lpix>iuM+hqOOj&uVXW_EgHth>a~1tPHqDM_mxoex$@?2VXRRIYZ{7yNN1TnNuNB zG%K|DlPz3;CZ@r;#ZymvgF3# z7)7qv&slONPC2I0zF5^9X~=b=hCgz5c;Wiq+np=qW?1GTBJ0f#=kL{Qi^|L(2+0gxbv`CPnDPVE{-&*TqTT`sn>+m z_>WZ$U+Qm|EkniIlNFuav?~UPM0e|mYpLMD>;S3)91Qo-m?G)b5%x1Q9RLN7a zj{*Eb1Oft(@3+DDk(+#;SN<6KVRwq8zKpqnz><*m=7Z^A>gn->XnmZnxS4}{b~*D_ zEcP~-^^JqE+3$IHUw#kw+^btFmF=^4QD*?Y1>*(O1Ab2*$BG}U4Aff-6Vl$`R4pRu zB@94Dr#Q7p+lJ@9@C??p!-pIb-2%E5^$8zzv(LA=Xbc%l==bpEpLO$KtxIL&*o7h) z-u4c8Hr?yBPFND^T?nAX-*er_N|a@H5Pw_v&n!=68ZI&3IT|T~@dbarFhJCI7g@V- z=kdYH`Qn&2?VJ!5Wn*-2aY4F_1b!UH@j7j*UF7D&8{p0lgkIk8=gr+}&+4};5&?#N zTk2zO4tPTaG{@bA_iav8dutXdQ>Y2xeYRjX`)7Ya#^?Fc)~jz3H`ni)Sn;P2t6vD9CQs& z;q=V*^c;mL+r5RZuIZ~`@i8Uv5~s%)db7QqTKCee*6*&pSs4JFnjSG%3szm=)3%}Y zm)^FPrC+9ksIOST3DG}iTQ!^5cZgQfIJ%JdooDP9rkfH;OgcoRTwO`7bNof(ov7`&APMbY}XS;3;((ml8DhA^z>)+}$lzC@rPE ztCq1q7T2{QiE;&%^Hlm6(t0fV&g#S^yzMaez=~fGYTK8hi_&?`vNa}8A*Las%brpH zDE(Swi)K(2Y?SjhMAle!`m{_IxM<~>M)DF1A0C=LNh-B{cKG^x;+?OTJABbKQSWJt zQ=ay7sd5>Y<-Lui@8d9P=wLF#Cot();dm6=A@IJKva~Xdx07p{q{3A9otFi~dNOi_ z-E=A2sF@}5y8TBCfNEyr!Pm#7l0huD8&!TpXC|RS%uhQlD@`n)CiIwGP{X5ED5{?) zNPesRbF8%L*~+E%T(F_yDZEaQw?if)@ZoxjS-I5HcHKRpJ6_^_>>Wl^XZH4{CtV`z zXMtmzNhss#%XAx|8rs)y34bT^#xz(7T7;YgV@cuMGqN~3a{q-ZYPuxZPw40}6SLFg z>Kwdy9udTdwp2?Jos^$+cy$%&$X$mysrD)2r4ZHWb0k2xm&5Oz+b+@Y59#G!< za;s~cy8B!u>fr@R$`d+wTs|fEqCR=BW+uIHycP-DBBC@LL|*?{J8R%`E*YLkugssjlP4g9s275|O3GaM^E5|65cjN>UXNH^)Xq{{><^~p%FDS?LgZFI_?`|8ylL=@n=)$TQo03-5)sW=$^Y$*U8G? z+ZxH=q`V$l{_RFAa!g6X|DExUf-}mYJ^6`ZD5FsM``thoO^!=PfbzK1&(caZ$8)Yp z@}KV%8$B-4JQi<@I%gi-FjhXre7`?VrUX}rBKTNyrxUomJN3HwO4SmE6fM4l?c zr7M~08fKcpSFgQy-#OyfOUb95_1;ZYm)=y|pSC8X^{QV^`zG@;b@In?dd?l$ws5B3 z=I%F(bX>}cjq=o^Xjqrbrs5VC&-S)2+W#w-oW^=_*)5=Eu93shHM(J{|2TSDQ7$e) z74{ePps*%QL;xqW)6%$rfAl>Eo-2Fwr7)pEcH zfVi+(4O6NOZ&G!3cUkqSS;?a8zO5L1mw>VhHgK>#C|hce*Gcim;#~?E#}IJ(ulcv< z<-jkH9Ce@hZ<&F|ngVBtYG>H?=jBv+c=sQq>>35M+DI{Tam0GEzWq*rh%_3b`Z+mH z-+G*F%Ur**bjSXMfPvSA3R4O!{YfORR?de_Dzdva!T?r59OyAvR78Wc=c`g8{fxA* z!1u?ThRS{9+D~94(8=by2KSIK>yxLQZ?{->(e|oj+Ph{WmYZTLa6{7P1F{T!osR+$ z{Uf^fOZb)h(%<&_t5#Zae+tz^M2UUcJw=bV0IEE1a;WO|m(3y6U z5!iX+*vC6wfz8fl&br}R7Ax`vfREnL0Een45dR=d`2Wzw{{q4lzAa4{RM;*|DOise zL3AIg*nC`9DWfdXH#7Y1w>DqrFIPOYc+%!tKiimQH`8r47Sp))?MJ|)LFrd5bL*0# zpfVY!DfK%z#*zHDRy!pvMVL0ko*Hv-_`k7!>v#^86wqnZ>+A@LFh`r&=7QJs{YbG^ zDV!S!Y~g}9np8w$JCHQv-kK&lL2`v4Z-a34j?m}YSyANlhxl}!Z>nA=h4De$G^1}v zg7!N{VNtJC?_-ILT8-V|)=D`>j-;xs$nEf{0-1VkT(mctcD9TkyKj=?bUHDw*+q|G zCHd}qI;T#G(_ge7MufNE48O8#!ta9joldER7nbt{C#@NXrv0I-GsDZs1Rz*e2>T9X z{5=cq?xVsN9#ELgRRVY~@Y|Y@9wEmijg5BgALj60v*sVRSp#fi%I_GI_1@=DI3ClL zJ7?JZe63YXc7JfXk?|D%ksB4O;+I7|o_+J;Q=eO9)4k7dVs>pxGFM)eld0!SDOHO| zDE&*@fNvoyq`fG2?!yzKRs)VlZ^hyt`CYv#E4e{MwL;51Ksin}Da-k*?ioa3-pVS{ zPVk0*h}3yBxAeS9>)YJ+mwSI}_Cu0zA7hx5*Tzef#?K~-k1k%7*h69IS0|1F{cBqt zS1s=}8jkSSp*PQYqhaxi>9=nknDeLUa_{knFTM46_)6<@lj6JDYv+ZQy?0&`hsTwU zIn&g{mHSeT<9l_elas`1yYiJ&x7S`DO3>Sl9mb>o-+wufna1ZHp_Ozk6?r zt6_4pKx;KFE65c2$SQ%4pgz>Qs$Kk7wa@BG`?1wek1SJC#D%N7OmiGi2x9~m`w&O? zmJr?wXOxr80J9H@x{vkSnD#H)!e2xDGaAKW*@bQ|ysr!ZNr0qLvW*eQG(^?y@c&DQ7~-)1)nR~~Vi$o2 zo5ZR!ZKOz3Q_78TDu}}Xt$#@rgbgG!LU{}yafNEHxQEmJKaq3AuqW_S-6h25?R}f!nr_cF}9WCG?L;++9 z+W0Hp{+LHy@yhd24{~b3!aOa+h_=yI)QCnt!NZ&!rq&b^9eDd7dSEeApQeSh`le_s zVdUBEf=H(pjoDeqCM`(wxDg#wp#W5h3rdyY?-4!_UnZoL`+472ESNWj--XnO>ZI{R znwuD+<;c&U77~rs=E(xzR^6<%3}FW7UArniN`+KTgVcoYQ+V>j{Yb}WRK6hLhWz)A zfUT%C&owc@&;PGHL4iAhl$S$lo3v|JnS_3Q<73CIz=wdURfREx0r>UY_&!d64Uw{2 z1^bSaMY;MSBMGDqq$d8id-e1FINcMCrTsLl>VI?N`WYc@*ey^aQl`b8(sv6}sW5m5 zz*`PBFGwJyNi0?$=dy%7qLL}oP~~Gi#W#s@d`0I65xUXg3*Kh8(`~R1Qbh;x-K*D6 zwdI8Ar7KP7LDkLOf4;kSMqmZOBHOzBVTfegUu)j$yXuaA3tl}n7~mnq`}$YbiCtkr zska6`M)h~753GELVUd8$4pJ&xN2n_6X$(@kcSX-Ghd|Z{yDKOB6wg8MU%N-s<#=18 zL?-M}Zk5j|haY@W0(k-r9zfuTkK04p9KHOQ`W|x`T|h8A!+fW`#fY3(ie}P1J8FB#L74PJ+;#j_shGexk$}oV)e}{}Nmb z0|E*FqzG1i(`*O1-o{`}Km)ZqbaeopB@A4v2D=AW3t);~!sOXIg#XgpFM&>VDu74; ztjt-Gla0yyy#=o!l7FREkfC}Fx?S~X!ZzVBINoQl_#po>qoOvU-v)_;3*awSQOKLg z_`f?qvi}Pg5=Dh!sbaAw$T~&1!fj+9Qd6ujbKZxiU>c5_L4^J6#QoPRwjusTibZX| zc)13KhXR-tCbTWSEn7R=jf*=U0&+l-z_?uY_P^D{EAJnzAS`5CCIfbfaCX`x5CM>X zW&^Qkhhd8v*$_&3?nwh?P4FXzIn&@-bmUFcW&xJSsB15j`yOMT|5b$n`<&!`e(`B3R@8iB4m+sJ%Ie!~WCiRQ&~5|2)yr8hQW@B@X$+k%2Y zV|-B)fzR}{jmcJdPpMS~jP0VfCX&YZ3Sz*nNso!WBIAvx~x*Et-j6SS*X z?)#y}EzN+=LxMmf1`%q($IgdSEsj*9=_^j#cK)68lUhv1k+%X_5_+Bwe`laT%R9qo zJQ`7&zxOVF?-CyuZq?oFg3;7Au;SYO+N{esyq{mh@k?t8h66!F{g1_Ph-lEz2m&r^ zyMY^1%<_r6>V>g&8ti3|_BYr84Rc-t>_Ch=K_gN{@x`)}r!3wF5Vvz#x=l=s^QK;~ z?z#4K%GXlggov>09%1=YMKZTpeun{`v2~X2e@lpg;v(oz>bq2q8mSRESNW#BfV`6`m?ZSp|BokyjBC(^YD7nIEI$S%b7pvpplDF68gPl8fUIB# z>$*s40^dJm-Yd-9@z+m~pwbSoeH?BD(tKqgaaQLspe@A zBma4nBB?=J;7w>*%;AZ~j7`8HD9gBIE;?a5Fabgj!%9B&{L^cQ& zH^x=hzmK$_UzCNWJ=b{L)B@bk8DN^hU;k007_QQW&@CrDR$VoDh(wQgob^A<0dWwe zcpLOIPTWBXLnZ=?{x8%^Erl#Z`*x30$2(sFh?5we8@AKmVHAAB{|y+`FL(3A{0go< zX#l`5(=C7lSV*~sk?+`8{_=$23H)W%>EN2+jw+s+n*Z2DHz`@je}8*s2iVZW&~Pv& z5y(z?NaFAuJ>r!XJgOW2TjgJd88hNz2QYb% zz~J)&EGY~j52`KhxNrL_R*}y*N+JTB9szA%484c>{d=sipIGFg>KIZtN8x|9r1i9s zcd^3YC4iv+M{Q$xT(>JlF@ZZ3SYeD)F96K&sUY|_L4Y=1R4eM_x$Do@%Zc$jr;U(* zZt#0F;6=CnAKdnz09wcO%>T@zGYoRCI{0s{4=WBpE3Ax7GLA095F1Raw2|@rC7ZzV ziQ*HM(ZqZD-s2E)>i_ZhA{opu;zoMV9+TZV(6_aGH>Ll0>|U7t7nSo|$~| zE}0|c&l-nbVEq^4-i9)8K#Y}1H@-fx$Ul*8`CkV>?3#R>08pa_1_faK&$PC9SI1iQ!MEDGrutfGRF#XT*rM za~y*gpMokO;pn_+GBEXIU5+4>|8FuO42k#eRl@EL%7=yG)dWBIpI?W#KNm>Jiv-N} z_U!^a%cK8ie(F8M{4?Eo4cxSpqR`h~x^t??k=@BFh`Q2OZ^83wDN?bf0?Sk7>= zF~aKw;k}Iw!v;tB%jbc&ut}2)tzjVmh)h*pUC7zrcjz=Awtf3zAnsL@vW=HUfkTIs($A$GGbko zTWG%X*T#0xBQN`AQB&hl)w~@@{hwI5*64Aa9v|DSOF{IWze)PEcC+cyEXV}mkpT)Z zg9HfFV}94XD~TGGx2S>~mwZ#~ILxgRbTZ;0xW)6iVAujj`sno37V~9AIk^I5ro;XH zqSN?cGb|b`&}&CWz4V(C{_M7G98n>s$RDlPU$Q{ykJYngMn(Dbn+`ajbDbTIwq$zP z_l$UyNN^hTW`{H&^;E$!T=;?ipVyGeiA7<5ne8#*!xLYBU`y6^_ut5L)x2{cMC{xE zHVWoe7Xyd>{n%lFk^lEoe-ljjzn^(BV4VLx{r_+wX5H1NfyyONM%&U8W8imQ0Ls78 zt}KwtmMZD;(G&+n#vHu7mNSjVdvLS?C{&@L;ebs4IJ^RNETFU(9DoO>6o1X zHrmh464H{pX8Z6x5fA_+qH{LuQf*O)iNU|?YuVbFlG$SJyV|a-`MX+WI=-8u8}_ZK zQ{UfXf*++(k$Fc)he9+BP|^P;@Z+$#!9G*McNLUmj-acms$yt_bu={}T{qazp%`RC zX6qe)zJo*Bc3x{~wVwP<|5FJ4s-;a#<#K9UcP!$*dj$@2w)mWXY9vk1h8`YtWnXMn z5FV4W3Kyzow)&hq$tqy&cC`A9%L4@jQMYXmaXBma)&yF@ZTl_w6Qul>xqmx@fzJUb z1@s=D-IG8c{ZaGs^n3{=d+@;A#Kfw`b!QZ-EkjKr)oJxND3vM%$If^xd(RG%j5{OI ztLecrO`s~UCEd3}OIzCzla|WgOUMJhLxpD;8F|XNN-@li3adA;m>0jw926gO_FmN~@i9~+>{FzDR(Vb6? zo>gDIePff5P=$Wb&;VKlKw%^irTntN<=p98XsE*J?o66s%vischK7c^I?00v1$}jt z#+H^FD3ssA!a^i$@q;NSc~@3;v9sF+Th#aO-TPZ(ZN00Y-{f}P8AYvV`ttd6j|zj9 z8`#+S8~ptIphgm$5;{CO67xj927>0GQmw*3+|>}&SF&?)MaRTw7rzcAre|em-}}p- z^QI*EWSmQilSb0-!(dj6Y=~HCpC6^MpP%1TGaoPXHF1T(b-Le;(lINw458GdBw7lJ zAy7;A<;z3o4)9jAwdG~LPEe{fm?2C}Lo*Gkxc?7lZyiw8l)5{ zX#qjHySqW9q(w?VKuScqyQBo91*Bskol=XwlfBRV?ilx+zYfE(hi-AP*84v1^UV3H zIZ2cWDvkRhUIpL2l|kBsh?mQXfa2B2=H{o7+yS^YAbG5_9=~;A0g0Y8)YPDF<cAHik4mYJLFa#ubRye*X9_+8R)SrTPi%$O7U;IPxVQq|-ta!& zen3WMbU6M70SO(UnZFx6BjAEQ>sq8$iAL<~P~to0*DraB!hB9vR^dmFTAH>ybtu27 z*2Z10?{4jIE6fhZMAC%^o& z$b*9j_yj*pbjFMBpdv8RRzSTD+TQK0t(j8e5D-0TMxv30kymLth|;zUq8x2ai8(IP zKAlhDvHaUXTt4Ts?|Pwhyg9)Qb`F^C6Uli5;VaMYf{qx&*yeknBS+)G+ozV=)pjZT zJ1HRaR&B=gUrCK|pY`ne5wnvJA0J71wNPr@1$xNQb*p&ULx>shIT%74M)8_!x&W=& zN%fo`sI;@T>j#AQr@?0a$oG`6^EU`Da41E&j5~8dvs5RWLm_nJ8+*q=OKTFmj_{;e z$;($nN85XO)rEv~eG=5iAsI~PJOUwcn%`+{5N5MxgGYBqIMc8O7zi2-T7@mYHaa4f zi<{G`&HC2eXG!zam~THdh-f@r?}bOJXgmz<(bGDKlL_;5zK<06_t+}{(e5lqbJI{-rnB7dE=baIFqqIiF+qeU)}rPFQ#}pkT`$jw^sxikjq1x)jxlqgJk?^Q9Is35oL5cBd1i*J6A3i>%qXI#1stX6Lg#wVtZ#+ZelO@;;7&5Qw!AvGH{C zh3`8A1aNSKe+t^$+g}(zd3+P~AUeW^`l7k!)ybIl4k)#3rm8Fl(sbcW+#WC9$$qXnkgmk1 zxfw%)R%J8sgCs>@FJNhjTjfZ5N<90`P8X%p_H5%+lkaml%==Xjzb*{|}bX#QLtQrtY z_gw#FIi-I4ml!2w`p1uqh3Y9uNo;i9>`EER@=09J^H`I_{NK3f9h~j>lZg2hTpp@c zx${>YWQd*b8ozq=JM5mE`A=jVXO+v^U${^dVt$+3GeUeeua3v{t>b0?NK^V6u^SYs z3z_{Ch?l(>*Y~%ZD095J%>MoRs#!_h9P;A0l11Z^%Lkd?6-_DXwA6w|8F6(u z&N~G}jp1-Mla=e?SRpxlAkW?TCQ@!u1=-1ZlkVX|Fs3q<3Sou)752&>lGM& z#E*yI(SFH%xYV64!{8wP>=`_sR=R0Z_@ty0<>t}0Q&k&ZY-SsBjJt5agqCI z8~#(p(H|wAz^2^_bG7%&-@iBLd^Ok9sPB9D$9BpMjyy0pacEa9f(#p*@-j>wa1Rj2 zx4=&UcT8Jd$U0|dPfw3||2ZoUPavWOM4f9xS@rBR3eRiXNxYY0sD{@H-o1OLSuUE+ ziQvY?4LXq2?ioLG&BwsN5IwW;8u_A-^PnI0l?uBVE3g^b6jOh7Z)y_d~*ZWJ9l)UM9e-@y^@N`l8&yn8dbTX&jcB5Nl8f^>o$bIAUDgCVEft` zE6jh04#$i4$)79EPMimif0ESi_X{1EL(Wuv|0h9j_@uLq_T@Pah3aSDF$Xk1KFv_Z zg1d0A6kJi^lB1%jtD71dOCyrcO;4|tyw!?EIe0ZDa*QRfs3_`vqyrVn<&}o;*E1eQ z#;@SYfSuuG{^;!NY-JwSYpp>Go=isBle8speYS-(XNV2726*R;Ep2RoX%2Vhprz$? znuzy(VGkLwXnJsFJP5ijjN}sgGcq#jm|)p$&7ARpyJh`X-uk-Lj7Z3b`W*^n8?M;) zOnost2z5+pX=!z&@UVbuj&kkF(a|xY6iV82D3)C}#&~IIYb-~;sGRnYl$ZLX!ub~z z91P2KWo2c99!AAJ=5*vA*sn<#7`pWogv@60JSTbLR8ap}TEaYn7e%G)tOw;0rN^`6 zNpTh6?3dnaaJx7a!)~%Rb~iSrJOww>YLmyoDrcqE4A@~V)bZ|($}<cn2CNso5siZR>1`yo0bE8f0++eJbCO@BG?4uS-cjrkos5^xAFBK&J05<#oV zhAFWZ+Yg_I(A~QHTG8h44qSEy+iP(rC!X*bcs^SE{rPQcyWW1cK(oBsZU#!Lad35}TDmx(eC^zdnFn`}` zS{HmWA8?#5;OGR49oB7(>^2lEm>k0C?*}`M{<=CjIayg*q2l@tBD21Kr@r9Unn#C< zy`w|6cVA6IV;r`M(k`m+;$pod`^U#{Ape3R3u4Oj5DBLL-jQ!}lCo(2D+5<JH66)#d_K_e2%Ezy)d9(tUP6yuc!HkCxT>q?+5{u#NXf)g=jk*f&ompS_@Rhr4 zX~fw9BMIgucnnV(Eyjy%!DW}ikC|P-B`oao>nr}SH0<)Dk(!N-jkjBO1sYvGOVcAF zWO)m#f*1)g0?h0xbXWt&zR}eOOaz)4^@UFUAqu#Ah!H+h~NJ=U}OZNdj{<0{|+D$YZ zr~8Q?KgNA<>RwW~e7syoPJX6*AwN7a63)qI-k*q?sQAhvG%bx@>h|eY|6jj0E5!O( z;RYPz1F1sS8z|T|!D4s;F;q%v!j&F-)V=z*q@Se(1-~uj70?D#lb2WUC1IqLs_)6SofNcw3w)Tk6(^E6X{R4eX}>0>7=Bj5u6uoTeoiqbA#iIr1|L1XTKTeftiR> z@L!O~gP9;H>8dJqxz8A06{|B3tdUJkO?B0UB#-RsE7>I26Zx&jctkG53fI77og%1Tl0Lw&D(gYYHJv=_1HN;y%FBuwI4}~n0zyiLf=BT)nC8hPHlObfWO&<~y zcMkR8-%dvol$LS|4{V5WwasId|G5bM6-joh*0`%5jKHgx;FX5$z+aBP@?=Ww>v*xbgZz~w6KR}dKFhx$s)`gSTWBzbwsI$wDp zo2xnV)z&Mmv3F`G>s(VHtp2ii)XIHp@&O@Xw*RwVb*}zvm+7ggv7dZ0?B_&1a47^P zW0gel$;iq~y1_}!bFl`l6R@RboH_5#dxO5aNH?^O$h$&N*WYD-<`{;E2ppRV^-nPX7{%&y8RGOxgJ2^bq^b9liP^r3>|PIP9Bz z?Gv{TdFfpzPL;~QLNV^|R`=&ZN=>aC+J&v@TDe53{LyvjN@V#Tr9XD{8vJ|)fY7(I zB9QdE?6~NrHQk9qq}dXE!b*G+@a(>*kJ>Q>eyU>XV@TzD4bEY!!}U2)q`@K-a4_X1 znnI+`V#w%X$`z}^c1p@C(hy2Xo@BB$RAFOL(OHN}x;9=>daeF5Gs;z{RS9h{)yB9z z`kgzTo27=}(4X_I7JvYOvmIjDaidDBQHVwK>;8}fz=-Ww!CHT6C_K5mCtvh2!IJw2 zfoO!%6)qhdG&40ZN2410)ix7cEG*hr?_3ok&6a))Wl2Tu9vtYDF0O!i^3L7PHT`ypJ|x5)$~pEn8CZXy$OZ6N)ezniQMqnl&g%c8`zY zK&Z}o4?o5e(b)&vD>NH65)$oD{emADqU=1@j^03RtC%KiyZQSLxP+>5gNLq%DgUPh z$Xx*du-bMC!bk08lzb}J)4aFS1RRF}m!4k5YJF<}+S+zV^?y%4z`Z2gW(RRhBTz|QoNlxMI^qPt0<0W}u&veLXzTKq zsdG~xdGz^eu|rN={O#HCjz3ft@Gj(KRslmOG#*huCWkBF1>nd|v)FcFwcd)SA>3U! zN7okOpmu!w_6GDq2nd2gK+S#o)-75FhO51BMUQo(FtTtLdq+L`wXuPeJq$0 zd!|3(3OiiEN({mBeC59w#}o@dm3q;+q*s!xj?O-yU*I>^IE_QpK!@YI+bRwCNTMS0 zXR46-=I_1A(^5l2L+6$LB)+Y3Vm)N~!D^I--pL%qAUV&sv_gR}V{_dG!|tr_=UMX* z9Zqm)gMwSFBb$+n`zQO&5cXr9UHz;tX+P&reHF^O++33{1R`*n_fHo$C9^14f=Tpe zQhG;wd&9vn%P^t6{SKwS4&Z{n07R2j^*U~{UHJYyp?Em`k>PoJXgVbDJ?i|eB*wNe zl;v%U38^spRW{vFgMf>@j|40^4|uKG!#PAbd>^~4WDLE&0ao{|B_M$VI@~ru) z(E@?%4|RipBDlseb7t_3S?TG$#tKNULn)&E`ua2g0JBg&VCJ*eVY61?1ioEjj!Q+a zAS@pAS{;f9Ge76%iZgEJ`7fVp*6K7lBEEEl6F={ID;Sj`d=i_R=H=EI!Qs+nWV2(( zdYnOzP0knhuRs0yKe)`S`GV#iG;-F^rCvW<>(8VkJf!h%>E5t$UEwx( za>dc~Q4p>WvA&O_m(}?BiN0K}CwRFbRK*ak7u=6TMOhZYFz=lQ2MZwxN8=j`?9)&v zDb{)2ZF%MCX=-8dJ^+JQk`tm$@<-b{J176ToIwSenYlPW|1hBI#kzL=(ST%l(_Eu> zt|N!`%a`%7u^Ss3ER7MZ0w1TRg~9TB<@9BeK=MVMzrf?iJ@XSEz#n@Jwhf4;@WF)z zcnGYl5n*9}_s`r{njlRUfIF|VBetQT7tqtjgI?CL&(^0Hi$|)XBj=xg4(IB}8~Xqt zgu4Ia^b{QRAh#my$jr=E4$mBH@N_<#O^AUrNK1?8{(Y0bWU-S8`s(Tl>Hge|jOfR^^;l)L zRaKiC|K0{jdWnryRn3Cgl~v~tpQkRumyxN93!sW?E8N<%_7;_so16F7z8E3Ur%*a!(A3S->{{CQ8GCIkx+arvM~V`5>!gFAT9?fB{G5lbq^w^7yS04sxsOTSAz zyE^S-{Y~)<<@T=eH7lu$@vE`MbLigTtFHa#{4HpH6V8-l!Ob4&PQ4l!9K7DVH}wlE z;fNe8c;X!3ARPJjvRA9)CQ1>w6L`Gfn30j4bUtxnHr|AgJG)hf|A-+*z`^hwLlMzyGWk7&A-Z0e%Z%+ z?o;*P|NApa1gG1<>K;H5bgsGA;U3KwZZEFs9r&7mycElzWVuT6`fD;-WOTH#r$}Upavt z6b!-+up;jgnbYX&r`OahD)=>g z1`g@N$^-DOLyi&|C8dV`?a8-lDWiwwfHXoCPfe5J0sI!_+Rhl`*J533qs27vpHJ7s z%ob|D?IyiM8bkQR$L41LMtggl%uy@s3653yJQ)~9)+b7h8GzO*ps=@Zo4weaa26D-jo^HWSD{tN zY4oNAs@{$=HAv`8_v;(nP{ zUL^zuFQ4M1wfsPUUf=olHGfrrwPnuZ1fs6?%P{pL3BhpXvkP55iPh?kq& z8Y<8<52N2Vak2RL2Qr>X$Mq0M+t}lL0B8g#6bK|1A1|U;Id$FOqov!bcVQYGGF)|= zsoZkD=C9;>9ZW1xZ~}=##4k2}4wT5&&6i$P2SdPY65MqszIQJu@Rr!o?-IM2 zI<2BcXmTMwKH%UAQW_%2cmc(QKQ_8;ok9!9!BGs*8pPpbKrg2wI58pk=g%M69AHOS z`1qufKgPyt>l8$UTT|f8+yB+3qoackabdxbMZF-d+l11WNAFu^M1(wGOLeX+&`#!W z$iewzV)9LG9k5g+(j(=QK680PL#AfAjAa-&we!?2FE10>b%6`wU}XircF)hBRuE?K zxb}sXRx)st6cj|HqgXjyjGKgL-pF!eE_ju@LW#W?{LII zG8YS6Azm^CGc$E%<Eh8Xb<>=Z@?zF^n$IIkB#`yVJ>Fjx|O&un!?t zhK3o#xtWX!adE+Lr7n?KmzULi%$V9SMfWkagY4wE-(^>&xELD7T&icM_#vdUphAX1 z$Al~C(w?VUC$!>3Qc6lzh$vJ=^FwOtU3~l^A#4?O^<@MC&d^#%8a8@*W&wd$8ZRJU z3v$8$#igR6sw04B5-2)or}mg0hH4c;V$Jd2zhTkQ+ya8e(htV=s!2FX8ycp&x^lC# zD{5+LY^V6>drP0>sVy%pF%wd-X?MyECFeiU{oR++rNuV(z-4vt>ALw&v;QuvBVtyh zUF%!Hx|w92=+>fF$GVoP&;iYkk8AJ`eP#k~4cO>9g|8JAP@uuWCAT+#Eo8ax$~Lre zvQm}`bOETh&R4uRJ6}Qb1+>Y>kE?*9fdO2NIi4|0Whs-9h?oVE8EgdY^VAY7#v%c#tqgd-u zTT!t{B@amV@#e76d4kpN-!q|N_gn~C(eqjh+TGoSH0PtWk*V?VLc9|zcz|-qrHG}|KzO!I4mdgRL(tpcP+s=z z4~U)lZ_T*tsA^mZ?8Zudmwo*ZT>$UJtqF9l+lAi0y3{GBE>C^y3bMw6RDwj+D+V81 zv1k?87FMa}8##vGL}PyUPX1{^Ph9GI5gBnm|NH6I$+sD83w>ret{(0!PkRrDAkw@$ zE32!a4NGrm6p6&WTUlP7!e#om!cw)quI|>2*59R1+x=~4>U?|Ro`aCZPUgq&gM(f* z=12=vgXD4Ro!N_MictrFPtwuf+P;fF780tqSE_TQ$uBItfb>N-H@5&(%jjc8x%!Wv z&I8n#1wH>_#afb*lLz2gSz1n1to6dgiQwR_5S#~x2M4R0n>;R!c;c0nu8xl6T3T9^ z!X6&0gNDS!#DVX}j$XdRMIi1@PEJluPDb4qgfu+qNHSPQmG*NSw{J@(a_HZ~kc6$k z)^_Xg5WA>|9du0Xp(AHU58n6wS?SmPT5JzVDjjS|NTh^ldU{IA@#+iS#3^9E)%pS^ z8ctYLRY#yLW+Dvh2v0;qMSYYc5A6;l!~wp!J>~uxh6R_GbF*z3SzBw%$>rzet$lu~ zn3u=E#>(3N={fva#EF(pSXj-Su+7;kBUn|h=Dn!@<@Ddb7#Yg%R%mpZeER0*68ox# z8dZvl+QcpeCfke&Q~ElY&RgEhbw*-dEyXH@;JTMqR*t5GxwOHL8^1UJL&X+iXiw{NK%p6cnfLNx~x z2+N9GB}TYIm5U{Ld4P#=pN-8KRVWj&5SSos|;yC#m^o)&h?${3>{y0JL zsNo?@5%3fj>;A}8_NB1iSK<7Z132W%pOQSoC8%p_BU?I>L%JAeGD*ZxC(Bp5KqOLG zRYf7}(cWj9UC5?wuYP*EksXDTR$ou<*AEE7B;4hS>go+MqQ1x}xlhb?d2!!XZxayAfw)nxjt5`?NH_oWjD!%*>w7&d1#Q>W+>~6zFV;y#RA$ zLEWgD_iKNJ<2EYl@5#w^1$*=i*j!>|vgiDSwA9rdH(z}#F2)V-81~-oWWi~q3(k;v zg1$D>fh)FI?_OL{Tbq-;)C9?ZX4d!h^`)iJaB+{FSJ;1k62zYSv^jh6>{%NEGMj*% zfNaJWd{bttgH#}Q3JujJCr<(4QCgDcMrY(t2&sZJ$hpRpklYtG6MtWt=YAg@y-gMO zzQpsu;y7P}dwYgO&|$v%?6{Xf@qV-0mOrdOC>o2*EG@tGkGjBP15DVdid^OZgf1+*vXbqwSK;!}O|(hK96A-d(kyXUK)Z=X<}>&XudKB6axwhYdCjl! z@sQTfh^|>nD#6k9I!yRClIf}NBs*9WMpJ8`bPvcU;*M!x3*kibGWZ) zW;XV_^bL(v&0>@zXf8K5r}mfoc%7nX3GRp$(8XLndbZ$?>y-q&X}5{r-)=I52GwJ) zljXjtn>TMda6GkIf1V_t_K`%1ItDV#n$De4s;hIC5&n5}~BvFg~SM zTYGAA6LoX)P}jj>aHIGNlPWI85fMUpL`Q4$$f=QCPaYQo!{h40efPpwSNCFdXj^Lv zYQN^gvGc(}gXzL*!?qQ;of7DyqrCFTJcfUIqVdI%^dOF+^EksK9Kr2t&UUo$I`z-w$tfj#<7wUViE-pO0yv;|GHq#z@ z6&9M1Jy>GcKeL_Kws2p-!J;?e76^uLLdv}k>%M+{*VBWxGN3OjJ85QUNJSWynKgcT za^m3`*;zOiNj|R17zcM1a&Mv5`q|r?*lhwyk5cHB6%}bPGN4dBTw)RGmEXU4x3{zY z?Lx+483ul)UsX8ZVFU@e`P2g=?|%FOJ!7W6SNgcEY(| z(#|i>()nz)=0a8o$jP%(j^XJqb+x8Wu~k)b8Bi9eo1B{JFxp%0OGi9KgO^N6P31Q2 zp>4$fv4RN-llSjS3K0m0&-SgzvD+D#D{nM6_-MI_2T#{OC?YAg6DF&7DAK!AZ-~_?4if*JbsXPDwk=dG9v-{ zEf1@M!;K{9Xpa7RJ?@gtui#Nzv3js_arP%PbPqI;ki|;Q?}txBL_kE8oaAZ!J&-YQ zFp@k8AK!4mrxXRL+Irt$@Oahb?*3JV@>leX^Ya`2{`z@;EEYj26;KChMO(e1eWmVi4}n<+3`GqX~x?gdUcfbax^3pFTD87FwC; zPZiIQPkl@$=s5t-KFAox$Hr#!(?M++927L~)>L1g5`Qlrp1WmK2wTfhS$OesdD#pL zHMRFoRb}PNmm>EcwN#aq)RdHbl*)oX;$cWixtZA8r>FX^d=EN+E?ZxJ2MXQ|J8t*w z`>+u;G`wZe;DQ|*>Kdoo`~2y<7YPXf`cpkaeF^735m9170``52^mP4)c<2y3O?dx2 zJ4E=A@&MuXYm{byszGe_>XoqT`cgx~jX-56jJMlt*j~Iq>{31p#@e=3yLyIA$|WEy zyi`3GW#yN5EDIVy6Z5$>*c;c^dj>eLFuW%5bNTO))-DE`%UI6PTk6u&)7RkJ85tSb z+lP2(*U#8HI_8z{D+{B)?=$P_?0nz7B;@Yo>)W{Z)m~069Nq@tiVeOF4tNy*tj^E3 zEI2rRv9Pgu>?4o^pRU`4{nai-s01^6lew8$FnKjC0TUP3C-i@y2mCy;=H$sB$_E13 zPp`2T_B^2c``-3!uj}hqM_4(@9lGhC!izqC=0t{(-;o}F{m)JHV!am{8vgrcV^AHy zE5!`CxO-jsk*3%Ncq1)&k~2)-pK`0Jss@Q?oJ7uM3UCxZ6c9icGseckY8g;HFw21# z>+55~#*W_8CJ=uX5NSg6OtPANCX<(iWop7x#?kS)p`oLk9817ch!?wXroN+)e1IX^ z=ZK(|mFtDt_b{zKCJaGJol#Bq5t46p3-uxR{uJ2Vb?=6Nw24y}ZRNTOSJwT2=YEt`$1F z@?&CRwhr8%nU;d(39T;3DqgCK&N~U!)zvvU?_bMroqbV|8&7Z_t$NSS>S;N@uARow z7|&Jp=k>Pq`U9lMN`DTUZkh`P9h$IJQUZ?_qRwgoChLa1MDuRjCzu zH8(iBcY{FU;Z=%3z;5GY1nCH{bF*j8qsGfW*D^9%J35ZT!gq8VJS-Q!-+nVI|JFkJfPonTY>yI0%s&J@4Q=ugM^rMLP7p=m^v6wH{ zl@endER*Rtud^+F1|>g9>l9cVOG_fq-@^H{Fw`M148X!czZM~Spi17suVxw%=@7%aHU;Ev>-P<6*p%4VJt^b zW3WAN4ATE%FPv(1`YbVP_@!7AK>CEI4{Zt$fAxHGC?^xt3mnosM(w++tAC(e21JXU z5F;y-nutwnaBC0qmN-|E1g&=#DEFc^Xy!@+-=1@VWq`txTL}q12Sp{&mtogv{=M8*urXcws#lUJ#Ip+ zpIAMZLa3lXYTStgG4IueemNAoHAWr7u&+Ez>QEjkl&iM5haR}Kb)Py_S|ZT$3pK*) zY+2k0^`BTv2%Ch6wOMD}JEYcM7h5*mkC+L(v{h!BQXvQxn(8P#D?7WQ!oo8cM?!hY zVHAl<5Ncv*$W9b)YdZmT5#tyu8(X^(XQ;&Aq>;S8&J>v{`W(8uxFzl7)a2xpcNcz7O^uF^-=!3KT*I0WzdS6&#>PfO zlu!rK{139=zTg8c27X;@>Ref-JZ`=!*R{9q9c#FCA4kKPnn(2G@-mUTd-dexWB%$G zOri!k$kgxaS?azUda6PlL*nlTmlC3uxty1I;$`Dw9F2Eo(`leu%6}d>czMy>wBJ8f z!?o-n0B3T`Tx|Dgy2S6vGI$SjUe=dai%)wD9^JZ;A)5ooCo*_eITJ2tz0XN9kT^Lx zLB-eEvFRn{&MvonD3+cye& zdOMsucd9B)oogQ?Q&~J{E@VrQ|AN=uR9+s-`1qi#s){;BMrS)#V}8DE#D@Q9wAO;E ztT4vGWu`8EWVZigJ)gr7BQM{(?o+|GqODBR7`dn9 z72IJIy3Eb1tH%72I%dAv(Ya!!eUYJ(FS;vt$U>feWU!X}n?M@X^%900cTl?u3G9Hd zT_L5(16ksQ+%-(@Dptu0gji?yv55&-DvqLydq`w299Pg|q2GGTKX+a&LEYWm-RR$Z zo)t~BnC_lZd^lEHmtxt(`g&jmApkV2h~r{KR#w}bz!~vtyH-4l!Q(jDAo&2<91Lvi z?*68cp`nZu99lLA8lP#>3h1j^a1emagE|I~p${EK_=o?9(`V=D{l8j(Rgi4l$B|}^ z*KP8-ZnCgCW2K^WW<4Q@9BRH7FTidRq}||Ddp?}w^31m!()i*3K(ugAyfu1^gIH)r zL0@&0lszgJFOG@4!9OQE)VKuYAm8ZAtTfs-a1uqS) zl9WUsSitaf%Yc@Gxc2Y3lf`;{$n*U^iFvQ$@haQ7PlB<*A;yA2xO8?Cyww_caquoz zJY{(j*ZRE$O!VkPR!qs<18&H zNix~HHnQTji6lC97kvEq5eXJo(&isttwQy=22V_4>9sYD-FCsg3pckOs1KhvJUjdm zW9)^vm7WDQqvqxv=y&#xpf#Zs;(PxpxxTo+L#E(}`F_0Y^XVXsmm&?`)laJN9wJXq zU0imoI zI=x3zEI#PEET0UNYyK!PhaL_N4m`lY{WHoPdss1pu^+;qG?S!$p*;9;p)CZPQuJq= zaa?XLTFg8*m)-o?*NBJ|R-%y(QxZF@bYV|a4krJL)4uobOVO$9?FWFx;!KiPRSiy0 z-+^HZA=u<$xf!IxwyF7NK%zMBbuhEAK$GSX|_A!4*ZWHE%Lq^A5}(SC{Sq z@$sV#p2brW6RENU}*$k$Mi1G3B3keDW9`G7D3Q5OEA3vBs7aFMN>za_C z&r&4w+omdmcnkIRZM`O+%7TLF@86yF7H`+w9QiQ|lfMe%;*4S7B@hHjJeG@js^$&? z{r&y$_OA7XP;+Q&mR`ToEY3=rgM$Mo;RMAlDx$j(+$1ib4-rnAU(O=OTMLPFF+l!ko zi(wcsfzrVpXvn0aFDb~+G~sp%I_vpDS;cQ7Q&MIW+OY`gsQE)SVs;<@{pDgP8liMF z`}xsrv7Ko~24p}T27`_Yn_FAY;as!l#U^=Va9|M)_;h`Z0D|txDH~VP`K(HBG+al|8hr%Eo@qHIm4hS5fH*1B^MR9n5^gq2pGg=<>llph&>Qm1F;}O ztKxF-H&rMW;$Y!|bw)>jf<{x_q_e#MI=_L?ySeu;f~u>>VMVN=7LkIB3OReU^&H~< zxj8e0gm5)0Rs%%Ehzmum{O0lq$x29K5p(!I~B zT0NKRq#_)2%jwl~R7zjLKoWH_L3f&a()KhN40QLX7#R2;wkYkVynjCrK;~lqZ`k8t zW-m*Q3R8>>FxSE4S1{g=IUc6kDg}ziG&kc?SUk%e5fPSJFh7O^2=L~5-IGiP*CKk1 zpfZ=W?}ggU^s!9B6#gAyImiYWPT$rqOAz|5_BlbQM9%u~8NocD))djz z4hykDW`Mjy-BVLTBD^onNce5ks*qS^~F-0Z`v)aqMsP>{Fi)K?kRsL^T4$?;4_n_BTqcxQ;v z2s5C)M~WfMa&Jw+GTAFFo3l{OM5g+zgX2k6nUUq}D0n4eWTxAQb$d*>LM6h`nD6Sl zxw`Io_%zhj&+0FDaD!0X>0Hb4$I3n_R`6JPXp;yrjc2pQf=rGI98qWM8jVmLD>Dv< zwKV?}V;s2T^bwIP)nQMRp-tY&+J3G+j9#AdGczwlGGCtlZaAHYPZkG-jS&EnvErcs zj1Je-D1G{LA6{y#_27K^PGeZ0`BYHC1EHn|oZms~2zPLxXZ<<5$9c`(h!O&dK(x|V z;`FfpcO{sly7hFUooiw z7f@{fssZ^7iq@p0ypx1fy%?En*rDXVm;%%c55!8J_k|#?JWGG1{G6);i3jZe1qC(? z3|+7{4iCcvE#I&vvGd?t%tlUi^7wR`1hr+K;xEY@72ff6AoJ=}F}w*BmEOx_+nK*V zQfdq{q`7XhwDP`ARVfd|@_dO8!&-A|Zoe<1z(ZCT!X^@k7tlJGDlJVP8!2tCw5ZR5 zQ_m)oex(z!nW?dGusY)9jOsKe#4?(vDt0CDc-!cEKqxR$fihV9&9dD%l2 zbxLyShKBumlHd0WF7krlO^MXDL@0gQ&Pe-JTgRgfJmkdjP9p5}7h8q;C=mguk5GIK zCM%c#bG|$n!R8?w?d^32C&&O%lrrP_LP#TAaj3Wfs}fFHDF#Kh8Gzl&${qleKb~pt z?q=e2%!*N@MR9q`d;_K7<8|v7Yi`I<08?z%MEjwyIYA;i{73NDs1ke8aPA{P`FTEr zcH;@Z0Xom4)d7m+(Yqp)H?R+I-k@@P@n3b2MLA{rQI2w6-hyXgaI4@3ww}esUsIj| z^u>SmJ18iK&;MZQS(Jns3_R8OPj+N*pXYTeW@PK@i2#w?j{k~*P+mI@qM&l{+`XHe zlOq@}d!jw-y%+t2I_9fdSWZrDXt#4C8YCIR*Xu-z>mTC6Ll!UPeWbDS-%FyPpo+Bo z`}gpl)7uvR%Xp81vPuj;9%MlsIY0C`I6?7_Sx_1q8$eCoUPVit*@>k_qu$U z<1zecqpmoyGK{PW7nzJ;P%cp6I`b*Bgcz>T-x^a#W@p)n*Tn;9q_kjqSut><536XsR8M?F@o%ILXylJ3 z#~7G)cp)PF0i5ZICJz!f1QH7W-QIo&Ex~h5%?==+DwA32#Fm_d@Wi1ffD^lJJX`>4 zQ2<`My4sni?N?*66b?#*@^0mbJT8rNonqQKhGKp)>6-y!7+VJ8jz))u{-L719!r-e zn*%kX|9Q;|?w@eYypLr;bc9$Gt@q@XvyO(Tir4&&_+ZK;kXy2{+n7Pr&&)GYobO@W zPQT*e+&{T-uCMh+zPi+K38mui+YFIK55%4aRA0yx*qxvb<}GiYmH(BGtaUHW1`H}O zOlt~{m~dltK0a>R-qMn;3Hlmv#jygWHodav5ll^r@;SWB@50>wnDaHU8XSGU;)e3m z-$^V&JLJ6Fprx%1%$td^u>yUpqwi;BMwm#~B)04%B(Fe9OJE5yYa6~4dHV6uQTR3~ zDXDn}_&ggM~fLKM1Icr2=7X%SsRG9MAzRBi%D=f6OvxC!WK}ho7wG`^U{`cet zY{wbO*(ymu)-vCNNoYq$Zd%2rA7pcYt_Qg4V@gWW$B*;g7PSqrQd#P1Fba{2DEy?E zkzk>w@atE2Jj~3#3^<0u#AY>S1{GsUuVCSDJfIbRmze@m{_{r-bUXJ5xT>nE*xBvc z033AqBBLOpsj9lNy$!YGn(rA7G~Cck{~G5(Z_-(K|4sG&+lx@0#;|6(bY-6&yYdMLylXWeIbC>rn^IFt3p{Cw@{rxIGgPMs zHw*a)@Bc^UKQ?N22z)=ApLdMnm6E9^%b}Lm=qJL>#H7ids9)z2)P>+A0c#^C2);nX zA082b{2zY1C=^OWn7^5@)X?yEb!ZM~fF$`|c$mSe;y0YlfZdEl-U1XSD=WZ^2orsGhmQ~I!D~CcM-^T0 z7ypV1N?{j-Mi&q^*uvpBx%}Hpr7^zX=5`ayG&8L|TA48{TB#-S2>3tuo#l{_5G&hf zI?BSnh(vvyP>D1C#Iq2frxjhe<5P#mzxa40HIP-S{~>TU2eiMt8*UM|0Qd z_4va#P3}O@!sgatRQlxMbj@Bf+7;Dt@<#rc;TO$Hv0Y-`mppEl+sEoO~X4D$Hw@`$v2>cgWd&} zEmZEFUUeF3YJe*gXje;d{{;SAxBiHh!9Re!2uApUE0sRB`+PnB0jvOB-SxVc6^a}? zCW;q&(6!_XC{!f90H@;;fydTnCi#cil!@8whyR_P&4l-+TWwcae+G=-_RJhW3a5SiSH7FS9YKf& zOgeomtR=)EA8Z|f661IA0U-t(q^fGY%>;pPUS(e1N#(d1VroM&JOQXMEb`?B>v4Bz zzmR?+G>A^nP{_&r{_LHMjg3t~0UYol08)4Z2t08R25DE(LKS*(hI{_ExAkzt7+)MD zeS3T9kA-mmI0&$4_;K0kKe4ALIDs3%*>f(WwJY?zr0cWwbpMA01~u0=l_t(unj~;&@DrCABtA+%ERM# z688$s>bpl3m#)8xKuBi0v2t^h=Z?ZMHZ~SZ;a(Zd%=D(CJC2QQ22VT_JNxyZMYDl4 z%Ow(*XM?g^AXqZx@&pbUXg8L81eE@gJoyscRYg&jB-*k7ks;3!$(oCiuxGp(kW!#*RKbE z$u7pU_Z|6>U6jS&itPs{gO$}>cFs@aopWSadG{3Jk>KCu^C5D;`XT8@~5vtgE93w)buuU?&Z5R-#P#i9EHZY8RP zAz-0!?_oX8meDQjAvyt*c%Cy16$-eezC{D3;^{1|sVQk~owBSX73uU$MpsYojkF3c zA0G&X+ADV$(j~6NDDfJZfE6EYoXgF!i2*+|O5(9q`=7LP95U;_lb zQek1?xAleB|5OP$kVUG$JbdE*!_;nD7r1qRIu|&@WOIn_&CpzeRYzlYzu&i{>UW=iat7lkfsEqk zul|`Pa(Awu?9>p1Yu93ws>tob;hOfG{{Cn1`S#Yp>H?9K&ooqb{&`K@b8%*BQ-!@f z01aGI)2tTxt=o~7c6O~B)+XjF^Q>9jGc(w^FQFW*)d2=B2~sq*n@u^?KIlxe z9>#uZ9ROo}L67pOe0pXEimv3OZGW073cQ<@nVFr?eOTd0j^*%&lZIh8qJ8G)Aeo@S zPX_XjIMJ}C*l+M*ZWInh|Ht64b~}XmPm(LoeLWVYTK}!d$lkna2~l_FoUOApT3hs;(wva;XyCda$GU+iqn!(y(ksxmcSfoc_`l@h#P zym2GX>Oh|9P^|=j;7q+gBS&%jK*T@DjnEDPmi*kC!GOzo2#bm{Qd8S(OJRY@>L@g#8Jst?=tuN!L0?WU znw*qm*>}amYZ^g{NRN-dDE9BW7H~mwOQkqqln5~gI~C^mv@{Cn4Ck6|0ts@x?HP(^ zkMX42JsX^CQEttw{YU)W$3g>3%M9LniV>TTjXtAbqh~w+$j}nZG#HpfyF5-6P*}9* zN{YzsB4;9~k4zsMt3o2%p9WmB`tS~@BvhEO*?&V%$J2`eA_AZrXCAbMX`&oJSO1g} zvIX1AK*&Ev5UOT<6EP6~FTcOgE?tPPO7ddyZctNFlBU|P8Yn9wD8-6EYSLk3iTW0F z9d?rk9cMdND^Lu;j(Cn$S!@=rH8PadtPeaO0fC9NwH&obVW-PZ#@-xgM+H1EF`Z}3 z+fdF;ZEPnz*ysuxQv>!Pe+g8}&+P0j0k(&!pdr=Zylpo=+HG(*a$`(7KhT>PABVYn zC{$`M4}4&UKwX&cEk8f4LaL^UU+yzx)1vKllB^5Qm@6 zq3-bTaP`%rOCH__>a<;+K2a-Iu_wWU@rF45F83NRSos*s>5h}&L{660N+e4&g3moeUOXyN?fgATcT(I(i(vM>hF zey%qAy+G&|+L!8cDnvz9>l?`A(0@d!IX`GeWVpJ=J}+)^auRp1lqk=-4u6+Hk0ep4 z(C6)$MXU8XI!r;^HVy#W=)b|96n{uYN}&1(>gg+omu`kIeXpLUmpUbzOZR?=;1BPq zH3mXxY#bS>V-coH(hWH5E!-q>O7TP|<&|yzkLF4?!h`@GDAIyT<4tz z?=#)+NLAm_L~fA69hn6arXwZNd%}#o1T^B=_!2sdTyYz1Mi*0Pz!T&j3N;g zFHA{Uc6he`hqTKH$+Pz;@%PITJsI@$>T2$>vgstS7^OvAlJkaNMZ0h!UoXTMKm%su z%2nePCos~VKHYxka&g!0IW2;+@M2?uAU+(2Gm){qN|s> z24L-?pEkH25u5EYcK*PmFwN})^np7lI6#o2TzURn5!(=l4bGrq&aqsVQhGfde)p$` z4@$s&`1w7B<{zAN)T%6=HZzz|__(2Q6O~32((zk6uYRUK2&lImopxL06iWCk(-Tj9 zG{E&ZeM8QRJct(7KR#}t^*&~6*E=$Udq>f#6dR+{DD>erl@nTCTEVZOu$blEnhvg-Wt8MzA}brlwC#jdT(7 zi-zJ9Jl9E)`=m5cW?`0@@iRX<6=+f?vPZyeu#kKo3p$^=KlRgIr8}-y`6d9nUTDa{ z(ecyR7{=rHTFT$izHo8)%??h^D}d5rBlKGa(E3mMXHDxW@e7~l+2L+IG$OiOTb7r* zCek2HYE%aJXkB9q(QVgHSM!;-By=l~x3v!xv9YPV_M8C^nfcQE^79LE4cYS;O_KFbbs{q~Z2bCRL#W z=_YgTxvgSB!NIAisr~cZ$x~KE)MaIbIb~&5#$WWBT#VMQf-o08q*^=OjHpQ${a!Xb zf4Kr)|CUp&EJWhhMN^UQ$!*LesCDdK#d(;S+IhFdxuvDid(=qhA|uskgRriS$BXE2 zI%qaM%n`!8JQKzlFuxe8=lMK@Wz(ixX7VI8Vn{_rj;S+T9OO603!HCbWfhGS6cXzH z{CTHDl%1XFNLS$?Dm!$Kf*A%bu}V+H=^*ME^T)1g32u_HjZgv}mQs$O&PPbBtGy_| zuUgyQzI#ko-hABlNb3{BTk`5^YA5sJ@ZS||t#4?MHjS%$pye15fR>WTNOheYHKN^7 zxiwd6Eb<+tDYb2F3tQ}h^1viDANX$~f3QgeX=&}*vewH_Y;4hAJ8Qqg(8T29Ero9E zwdw@v&xJr(LE5js(}YZFADM}di<7r1QIb9NBz*Z*BNT=o(+mYCm6E-31OoxiS(V7h z%D%;Ak&D^0cQZcEknH=EW!D>05fVMUye1|llrEO{ZMJcFVOHYR0u}H~(wFLRz5;89 zU@=XI9|MDeK0Xm&_QxSjUa0pqJdA)rrL|sLNjdJ`l%(Ttkh9WAr4Us>adtMwG^{~J z7GrSr$}9`nwG}_an1v2zPM z3i++l8&%oxutUS$rGxkt(O%C%F=d2v32d z=9nB6j^+Rj4Glz{S8-cL>Z77ReiQ*+083Ve-vBKpcspTthHyo*JDLxmqacd?w)-B4 z1LA9a>+5Xv^5I~KW1rYxjRVIm;-`Ic$=99o5D!Ho$#z>x{rgi)&s0t+93qP|H+CiuXj*hAoq%oc}H5F4n;#A?MV$suwJ1>mPS?1Mgc|31-}~0 zzKNXlMFjn{+}v#&9M>d~)p2;-*A|a4*ewwi6SKxT>tXjd|4(7c2jLw1`1Sj{$5Ap) z4H3Q<3o4N1_|XHC8q-KJ_w##(yD&^T9dbKH4to5UAG;uY&C~^snA}l8lC1+r64^;< z?S!sQ!7YQ69O>aRtUb#A9`Dqq#zX1vzp;9igm)y=7Cdgz+8edc)r~vj(e&%2Xs4=1 zCZWp8yH440G&2L7#?eep7G|`rh??>yCa%ajwta2XjTF>*`BE9 zK&cojs^lCTc$E*F8X^mp+54b#_DPkT8woLy3zjF$x4SRI84nL3bwx|h)I*-0hK`>UkR}JbB`V;Sp7EQnkLq$?mHzO& zGcwt?+t}xt8+#I1DjV6yj%x7cP#tWR!`VbLEyIe z^?kw|v+A$bdII5p_7|Szt6qZSif-Y#5&6k0B25fTz8VJ$VHM#A8oxS~#*Li5)>b=9`)U^vE^qQOLZN|NaWum*f zO3+^YVxe8g1Wk5!HFYU_OQ+jy|K$x2EHjhgM+*v}I)a=B9hAnm(13VC%l63qa4GY?5mRRE$9Uu4k`! z5HwQ<2OrdedhpWo-fpEELIs&Rw^Fa3Lebei`zn|dbCnAr4`9_Fy=lMnUr}4lij^UE zm8}S74nU(Cjkdg{g_yMQW%*|&fh*F$L;F)uvq#qL<-o|P0qT4HU_H=}(8(~rQ&GOJYYUV47mi9pn=h~T3kZBF_5ML^Keip85Z*stZBtzYEr6tyl*c3=>-~2PFGKyd z(bTvQCYQKVo~~KFS}wM+tu4nWZdLFDI+iWl31=QGvnvJ~{`~6JU5*b(?ZT81q{LyZ zXrs{t+c!V|8i8QF0S23@kbCWC;0-a)SGsiqkhW)r}vz?a>iqq&XiU7r1M z+$paT8@SkZ!Ic*i({QH{IqqSq(Y=zATYFMoICzLgy4%~AUG&eiR5Ug)0Oq;~D^<{H zpN56y4r*Z&q@Ty;v{0T5UoC5-Ti@6x<+*Kk!F7cKais><6UV*Q)>+;8q8|zFkHPtB zjr-0{Num7>@wYEH1%0Vz@{T7)7WO?@Jd~2jlw}chdfji#_{6Z5N7fk|mTq&277h=B zEh?)0kfbtrlu-c+#=nTT^h=dR<-<%JC`V(_FZld@fAAw@QXwlv#s^1MF1<$SBiZfu s{Ylu&VHUXV*N^`Hg3^B#SgAjlhHG`wT)3(9g$uEN_d)$^J)6J&36L50z5oCK literal 0 HcmV?d00001 diff --git a/public/images/sites/templates/starter-for-astro-dark.png b/public/images/sites/templates/starter-for-astro-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..5380b32b7d236d9488c52e019bdd8bfa52f380b8 GIT binary patch literal 48885 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 S_0=aGx#aa*;%Q>K|NIXh7Po)^ literal 0 HcmV?d00001 diff --git a/public/images/sites/templates/starter-for-astro-light.png b/public/images/sites/templates/starter-for-astro-light.png new file mode 100644 index 0000000000000000000000000000000000000000..5380b32b7d236d9488c52e019bdd8bfa52f380b8 GIT binary patch literal 48885 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 S_0=aGx#aa*;%Q>K|NIXh7Po)^ literal 0 HcmV?d00001 diff --git a/public/images/sites/templates/starter-for-remix-dark.png b/public/images/sites/templates/starter-for-remix-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..76e80dde71846475a6812f4ef746a2fff65989dd GIT binary patch literal 54339 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

An>1-|a->HlEg+ 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 literal 0 HcmV?d00001 diff --git a/public/images/sites/templates/starter-for-remix-light.png b/public/images/sites/templates/starter-for-remix-light.png new file mode 100644 index 0000000000000000000000000000000000000000..76e80dde71846475a6812f4ef746a2fff65989dd GIT binary patch literal 54339 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

An>1-|a->HlEg+ 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 literal 0 HcmV?d00001 diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index ea6cac8374..7e429defca 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -565,6 +565,10 @@ class Builds extends Action // Some runtimes/frameworks can't compile with less memory than this $minMemory = $resource->getCollection() === 'sites' ? 2048 : 1024; + if ($resource->getAttribute('framework', '') === 'analog') { + $minMemory = 4096; + } + $cpus = $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT; $memory = max($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT, $minMemory); $timeout = (int) System::getEnv('_APP_COMPUTE_BUILD_TIMEOUT', 900); diff --git a/src/Appwrite/Platform/Tasks/Screenshot.php b/src/Appwrite/Platform/Tasks/Screenshot.php index 918ce60e11..79593ab39b 100644 --- a/src/Appwrite/Platform/Tasks/Screenshot.php +++ b/src/Appwrite/Platform/Tasks/Screenshot.php @@ -147,7 +147,7 @@ class Screenshot extends Action 'installCommand' => $framework['installCommand'] ?? '', 'outputDirectory' => $framework['outputDirectory'] ?? '', 'providerRootDirectory' => $framework['providerRootDirectory'], - 'timeout' => 60 + 'timeout' => 30 ]); if ($site['headers']['status-code'] !== 201) { From baa1cb19c64f76492016537713aea8718b9742c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 14 Apr 2025 12:28:22 +0200 Subject: [PATCH 78/80] Re-add test, simplify --- composer.lock | 24 ++++++------ .../Services/Sites/SitesCustomServerTest.php | 37 +++++++++++++++++++ 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/composer.lock b/composer.lock index f33d83dc4c..f151523ffc 100644 --- a/composer.lock +++ b/composer.lock @@ -3705,16 +3705,16 @@ }, { "name": "utopia-php/fetch", - "version": "0.4.0", + "version": "0.4.1", "source": { "type": "git", "url": "https://github.com/utopia-php/fetch.git", - "reference": "46e791ff6a95864517750b9df6bbf4a17e3c9c4e" + "reference": "65095dac14037db0c822fb5e209e5bd3187a0303" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/fetch/zipball/46e791ff6a95864517750b9df6bbf4a17e3c9c4e", - "reference": "46e791ff6a95864517750b9df6bbf4a17e3c9c4e", + "url": "https://api.github.com/repos/utopia-php/fetch/zipball/65095dac14037db0c822fb5e209e5bd3187a0303", + "reference": "65095dac14037db0c822fb5e209e5bd3187a0303", "shasum": "" }, "require": { @@ -3738,9 +3738,9 @@ "description": "A simple library that provides an interface for making HTTP Requests.", "support": { "issues": "https://github.com/utopia-php/fetch/issues", - "source": "https://github.com/utopia-php/fetch/tree/0.4.0" + "source": "https://github.com/utopia-php/fetch/tree/0.4.1" }, - "time": "2025-03-11T21:06:56+00:00" + "time": "2025-04-14T07:34:27+00:00" }, { "name": "utopia-php/framework", @@ -3996,16 +3996,16 @@ }, { "name": "utopia-php/migration", - "version": "0.8.5", + "version": "0.8.6", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "0dd95b148c581579ec05d2abbbdc13c2b4702331" + "reference": "84163e16edc0b2e64c34ad7b7c4cc5f05d762daf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/0dd95b148c581579ec05d2abbbdc13c2b4702331", - "reference": "0dd95b148c581579ec05d2abbbdc13c2b4702331", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/84163e16edc0b2e64c34ad7b7c4cc5f05d762daf", + "reference": "84163e16edc0b2e64c34ad7b7c4cc5f05d762daf", "shasum": "" }, "require": { @@ -4046,9 +4046,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/0.8.5" + "source": "https://github.com/utopia-php/migration/tree/0.8.6" }, - "time": "2025-04-09T05:21:09+00:00" + "time": "2025-04-14T08:22:09+00:00" }, { "name": "utopia-php/orchestration", diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index 35bc9bb410..ba0242bb20 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -9,6 +9,7 @@ use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideServer; +use Utopia\CLI\Console; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; @@ -2503,4 +2504,40 @@ class SitesCustomServerTest extends Scope $this->cleanupSite($siteId); } + + public function testEmptySiteSource(): void + { + $siteId = $this->setupSite([ + 'siteId' => ID::unique(), + 'name' => 'Empty source site', + 'framework' => 'other', + 'buildRuntime' => 'node-22', + 'outputDirectory' => './', + ]); + $this->assertNotEmpty($siteId); + + // Prepare empty site folder + // We cant use .gitkeep, because that would make deployment non-empty + $stdout = ''; + $stderr = ''; + $folderPath = realpath(__DIR__ . '/../../../resources/sites') . '/empty'; + Console::execute("mkdir -p $folderPath", '', $stdout, $stderr); + + $deployment = $this->createDeployment($siteId, [ + 'code' => $this->packageSite('empty'), + 'activate' => true + ]); + $this->assertEquals(202, $deployment['headers']['status-code']); + + $deploymentId = $deployment['body']['$id']; + $this->assertNotEmpty($deploymentId); + + $this->assertEventually(function () use ($siteId, $deploymentId) { + $deployment = $this->getDeployment($siteId, $deploymentId); + $this->assertEquals('failed', $deployment['body']['status'], 'Deployment status does not match: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); + $this->assertStringContainsString('Error', $deployment['body']['buildLogs'], 'Deployment logs do not match: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); + }, 100000, 500); + + $this->cleanupSite($siteId); + } } From ed95ac764380efcac4e55b5357a2ae2b342b45ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 14 Apr 2025 12:54:56 +0200 Subject: [PATCH 79/80] Re-add test, simplify --- .../Services/Sites/SitesCustomServerTest.php | 61 ++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index ba0242bb20..160b2a018d 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -2535,7 +2535,66 @@ class SitesCustomServerTest extends Scope $this->assertEventually(function () use ($siteId, $deploymentId) { $deployment = $this->getDeployment($siteId, $deploymentId); $this->assertEquals('failed', $deployment['body']['status'], 'Deployment status does not match: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); - $this->assertStringContainsString('Error', $deployment['body']['buildLogs'], 'Deployment logs do not match: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); + $this->assertStringContainsString('Error:', $deployment['body']['buildLogs'], 'Deployment logs do not match: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); + }, 100000, 500); + + $this->cleanupSite($siteId); + } + + public function testOutputDirectoryEmpty(): void + { + $siteId = $this->setupSite([ + 'siteId' => ID::unique(), + 'name' => 'Empty output directory', + 'framework' => 'other', + 'buildRuntime' => 'node-22', + 'outputDirectory' => './empty-directory', + 'buildCommand' => 'mkdir -p ./empty-directory' + ]); + $this->assertNotEmpty($siteId); + + $deployment = $this->createDeployment($siteId, [ + 'code' => $this->packageSite('static'), + 'activate' => true + ]); + $this->assertEquals(202, $deployment['headers']['status-code']); + + $deploymentId = $deployment['body']['$id']; + $this->assertNotEmpty($deploymentId); + + $this->assertEventually(function () use ($siteId, $deploymentId) { + $deployment = $this->getDeployment($siteId, $deploymentId); + $this->assertEquals('failed', $deployment['body']['status'], 'Deployment status does not match: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); + $this->assertStringContainsString('Error:', $deployment['body']['buildLogs'], 'Deployment logs do not match: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); + }, 100000, 500); + + $this->cleanupSite($siteId); + } + + public function testOutputDirectoryMissing(): void + { + $siteId = $this->setupSite([ + 'siteId' => ID::unique(), + 'name' => 'Empty output directory', + 'framework' => 'other', + 'buildRuntime' => 'node-22', + 'outputDirectory' => './non-existing-directory', + ]); + $this->assertNotEmpty($siteId); + + $deployment = $this->createDeployment($siteId, [ + 'code' => $this->packageSite('static'), + 'activate' => true + ]); + $this->assertEquals(202, $deployment['headers']['status-code']); + + $deploymentId = $deployment['body']['$id']; + $this->assertNotEmpty($deploymentId); + + $this->assertEventually(function () use ($siteId, $deploymentId) { + $deployment = $this->getDeployment($siteId, $deploymentId); + $this->assertEquals('failed', $deployment['body']['status'], 'Deployment status does not match: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); + $this->assertStringContainsString('Error:', $deployment['body']['buildLogs'], 'Deployment logs do not match: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); }, 100000, 500); $this->cleanupSite($siteId); From 0b6a8ec2cac42feaded0d25d4e5ae5fd9ca95729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 14 Apr 2025 13:39:23 +0200 Subject: [PATCH 80/80] Update tests/e2e/Services/Sites/SitesCustomServerTest.php --- tests/e2e/Services/Sites/SitesCustomServerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index 160b2a018d..0b3b1710c1 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -2575,7 +2575,7 @@ class SitesCustomServerTest extends Scope { $siteId = $this->setupSite([ 'siteId' => ID::unique(), - 'name' => 'Empty output directory', + 'name' => 'Missing output directory', 'framework' => 'other', 'buildRuntime' => 'node-22', 'outputDirectory' => './non-existing-directory',