diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index f0d0abdda2..94fde774e2 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -28,8 +28,6 @@ use Utopia\Migration\Destinations\CSV as DestinationCSV; use Utopia\Migration\Exception as MigrationException; use Utopia\Migration\Resource; use Utopia\Migration\Resources\Database\Database as ResourceDatabase; -use Utopia\Migration\Resources\Settings\Key as ResourceKey; -use Utopia\Migration\Resources\Settings\Platform as ResourcePlatform; use Utopia\Migration\Resources\Database\Row as ResourceRow; use Utopia\Migration\Resources\Database\Table as ResourceTable; use Utopia\Migration\Source; @@ -216,6 +214,8 @@ class Migrations extends Action $dataSource, $database, $queries, + $credentials['consoleApiKey'] ?? '', + $credentials['projectId'] ?? '', ), CSV::getName() => new CSV( $resourceId, @@ -325,6 +325,29 @@ class Migrations extends Action return API_KEY_DYNAMIC . '_' . $apiKey; } + /** + * Generate a console-scoped dynamic API key for settings migration. + * + * This key allows the source adapter to read platforms and keys + * via the console API endpoints (same-instance only). + * + * @throws Exception + */ + protected function generateConsoleAPIKey(): string + { + $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 86400, 0); + + $apiKey = $jwt->encode([ + 'projectId' => 'console', + 'scopes' => [ + 'platforms.read', + 'keys.read', + ], + ]); + + return API_KEY_DYNAMIC . '_' . $apiKey; + } + /** * @throws AuthorizationException * @throws Conflict @@ -344,6 +367,7 @@ class Migrations extends Action $project = $this->project; $tempAPIKey = $this->generateAPIKey($project); + $tempConsoleAPIKey = $this->generateConsoleAPIKey(); $transfer = $source = $destination = null; @@ -361,6 +385,7 @@ class Migrations extends Action $credentials['projectId'] = $credentials['projectId'] ?? $project->getId(); $credentials['apiKey'] = $credentials['apiKey'] ?? $tempAPIKey; $credentials['endpoint'] = $credentials['endpoint'] ?? $endpoint; + $credentials['consoleApiKey'] = $credentials['consoleApiKey'] ?? $tempConsoleAPIKey; } if ($migration->getAttribute('destination') === DestinationAppwrite::getName()) { @@ -430,8 +455,6 @@ class Migrations extends Action $migration->getAttribute('resourceType') ); - $this->migrateSettings($migration, $destination); - $destination->shutdown(); $source->shutdown(); } @@ -520,142 +543,6 @@ class Migrations extends Action } } - /** - * Migrate platforms and API keys at the worker level. - * - * Settings (platforms, keys) live in dbForPlatform, not dbForProject. - * Instead of passing dbForPlatform to the source adapter, the worker - * reads from the source project and writes via the destination adapter. - */ - protected function migrateSettings(Document $migration, Destination $destination): void - { - if (!($destination instanceof DestinationAppwrite)) { - return; - } - - $resources = $migration->getAttribute('resources', []); - $credentials = $migration->getAttribute('credentials', []); - - $hasPlatforms = \in_array(Resource::TYPE_PLATFORM, $resources); - $hasKeys = \in_array(Resource::TYPE_KEY, $resources); - - if (!$hasPlatforms && !$hasKeys) { - return; - } - - $sourceProjectId = $credentials['projectId'] ?? ''; - if (empty($sourceProjectId)) { - return; - } - - $sourceProject = $this->dbForPlatform->getDocument('projects', $sourceProjectId); - if ($sourceProject->isEmpty()) { - return; - } - - $sourceInternalId = $sourceProject->getSequence(); - - if ($hasPlatforms) { - $this->migratePlatforms($sourceInternalId, $destination); - } - - if ($hasKeys) { - $this->migrateKeys($sourceInternalId, $destination); - } - } - - /** - * Read platforms from source project and import via destination. - */ - protected function migratePlatforms(string $sourceInternalId, DestinationAppwrite $destination): void - { - $lastDocument = null; - $batchSize = 100; - - while (true) { - $queries = [ - Query::equal('projectInternalId', [$sourceInternalId]), - Query::limit($batchSize), - ]; - - if ($lastDocument !== null) { - $queries[] = Query::cursorAfter($lastDocument); - } - - $platforms = $this->dbForPlatform->find('platforms', $queries); - - if (empty($platforms)) { - break; - } - - foreach ($platforms as $platform) { - $resource = new ResourcePlatform( - 'unique()', - $platform->getAttribute('type', ''), - $platform->getAttribute('name', ''), - $platform->getAttribute('key', ''), - $platform->getAttribute('store', ''), - $platform->getAttribute('hostname', ''), - ); - $resource->setPermissions($platform->getPermissions()); - $destination->importSettingsResource($resource); - } - - $lastDocument = \end($platforms); - - if (\count($platforms) < $batchSize) { - break; - } - } - } - - /** - * Read API keys from source project and import via destination. - */ - protected function migrateKeys(string $sourceInternalId, DestinationAppwrite $destination): void - { - $lastDocument = null; - $batchSize = 100; - - while (true) { - $queries = [ - Query::equal('resourceType', ['projects']), - Query::equal('resourceInternalId', [$sourceInternalId]), - Query::limit($batchSize), - ]; - - if ($lastDocument !== null) { - $queries[] = Query::cursorAfter($lastDocument); - } - - $keys = $this->dbForPlatform->find('keys', $queries); - - if (empty($keys)) { - break; - } - - foreach ($keys as $key) { - $resource = new ResourceKey( - 'unique()', - $key->getAttribute('name', ''), - $key->getAttribute('scopes', []), - $key->getAttribute('secret', ''), - $key->getAttribute('expire', null), - $key->getAttribute('accessedAt', null), - $key->getAttribute('sdks', []), - ); - $resource->setPermissions($key->getPermissions()); - $destination->importSettingsResource($resource); - } - - $lastDocument = \end($keys); - - if (\count($keys) < $batchSize) { - break; - } - } - } - /** * Handle actions to be performed when a CSV export migration is successfully completed *