diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 71ab9da8..d176bf60 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -50,6 +50,7 @@ use Utopia\Migration\Resources\Functions\Deployment; use Utopia\Migration\Resources\Functions\EnvVar; use Utopia\Migration\Resources\Functions\Func; +use Utopia\Migration\Resources\Integrations\Platform; use Utopia\Migration\Resources\Messaging\Message; use Utopia\Migration\Resources\Messaging\Provider; use Utopia\Migration\Resources\Messaging\Subscriber; @@ -161,6 +162,8 @@ public function __construct( protected UtopiaDatabase $dbForProject, callable $getDatabasesDB, protected array $collectionStructure, + protected UtopiaDatabase $dbForPlatform, + protected string $projectInternalId, protected OnDuplicate $onDuplicate = OnDuplicate::Fail, ?callable $getDatabaseDSN = null, ) { @@ -270,6 +273,9 @@ public static function getSupportedResources(): array Resource::TYPE_SITE_DEPLOYMENT, Resource::TYPE_SITE_VARIABLE, + // Integrations + Resource::TYPE_PLATFORM, + // Backups Resource::TYPE_BACKUP_POLICY, ]; @@ -419,6 +425,7 @@ protected function import(array $resources, callable $callback): void try { $this->dbForProject->setPreserveDates(true); + $this->dbForPlatform?->setPreserveDates(true); $responseResource = match ($resource->getGroup()) { Transfer::GROUP_DATABASES => $this->importDatabaseResource($resource, $isLast), @@ -427,6 +434,7 @@ protected function import(array $resources, callable $callback): void Transfer::GROUP_FUNCTIONS => $this->importFunctionResource($resource), Transfer::GROUP_MESSAGING => $this->importMessagingResource($resource), Transfer::GROUP_SITES => $this->importSiteResource($resource), + Transfer::GROUP_INTEGRATIONS => $this->importIntegrationsResource($resource), Transfer::GROUP_BACKUPS => $this->importBackupResource($resource), default => throw new \Exception('Invalid resource group', Exception::CODE_VALIDATION), }; @@ -445,6 +453,7 @@ protected function import(array $resources, callable $callback): void $responseResource = $resource; } finally { $this->dbForProject->setPreserveDates(false); + $this->dbForPlatform?->setPreserveDates(false); } $this->cache->update($responseResource); @@ -3055,6 +3064,68 @@ private function importSiteDeployment(SiteDeployment $deployment): Resource return $deployment; } + /** + * @throws \Exception + */ + public function importIntegrationsResource(Resource $resource): Resource + { + switch ($resource->getName()) { + case Resource::TYPE_PLATFORM: + /** @var Platform $resource */ + $this->createPlatform($resource); + break; + } + + if ($resource->getStatus() !== Resource::STATUS_SKIPPED) { + $resource->setStatus(Resource::STATUS_SUCCESS); + } + + return $resource; + } + + /** + * @throws \Throwable + */ + protected function createPlatform(Platform $resource): bool + { + $existing = $this->dbForPlatform->findOne('platforms', [ + Query::equal('projectId', [$this->project]), + Query::equal('type', [$resource->getType()]), + Query::equal('name', [$resource->getPlatformName()]), + ]); + + if ($existing !== false && !$existing->isEmpty()) { + $resource->setStatus(Resource::STATUS_SKIPPED, 'Platform already exists'); + return false; + } + + $createdAt = $this->normalizeDateTime($resource->getCreatedAt()); + $updatedAt = $this->normalizeDateTime($resource->getUpdatedAt(), $createdAt); + + try { + $this->dbForPlatform->createDocument('platforms', new UtopiaDocument([ + '$id' => ID::unique(), + '$permissions' => $resource->getPermissions(), + 'projectInternalId' => $this->projectInternalId, + 'projectId' => $this->project, + 'type' => $resource->getType(), + 'name' => $resource->getPlatformName(), + 'key' => $resource->getKey(), + 'store' => $resource->getStore(), + 'hostname' => $resource->getHostname(), + '$createdAt' => $createdAt, + '$updatedAt' => $updatedAt, + ])); + } catch (DuplicateException) { + $resource->setStatus(Resource::STATUS_SKIPPED, 'Platform already exists'); + return false; + } + + $this->dbForPlatform->purgeCachedDocument('projects', $this->project); + + return true; + } + private function validateFieldsForIndexes(Index $resource, UtopiaDocument $table, array &$lengths) { /** diff --git a/src/Migration/Resource.php b/src/Migration/Resource.php index 2fc102c5..eb579ebe 100644 --- a/src/Migration/Resource.php +++ b/src/Migration/Resource.php @@ -71,6 +71,8 @@ abstract class Resource implements \JsonSerializable public const TYPE_ENVIRONMENT_VARIABLE = 'environment-variable'; + // Integrations + public const TYPE_PLATFORM = 'platform'; public const TYPE_SUBSCRIBER = 'subscriber'; public const TYPE_MESSAGE = 'message'; @@ -109,6 +111,7 @@ abstract class Resource implements \JsonSerializable self::TYPE_ENVIRONMENT_VARIABLE, self::TYPE_TEAM, self::TYPE_MEMBERSHIP, + self::TYPE_PLATFORM, self::TYPE_PROVIDER, self::TYPE_TOPIC, self::TYPE_SUBSCRIBER, diff --git a/src/Migration/Resources/Integrations/Platform.php b/src/Migration/Resources/Integrations/Platform.php new file mode 100644 index 00000000..c81b6f04 --- /dev/null +++ b/src/Migration/Resources/Integrations/Platform.php @@ -0,0 +1,104 @@ +id = $id; + $this->createdAt = $createdAt; + $this->updatedAt = $updatedAt; + } + + /** + * @param array $array + * @return self + */ + public static function fromArray(array $array): self + { + return new self( + $array['id'], + $array['type'], + $array['name'], + $array['key'] ?? '', + $array['store'] ?? '', + $array['hostname'] ?? '', + createdAt: $array['createdAt'] ?? '', + updatedAt: $array['updatedAt'] ?? '', + ); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'id' => $this->id, + 'type' => $this->type, + 'name' => $this->name, + 'key' => $this->key, + 'store' => $this->store, + 'hostname' => $this->hostname, + 'createdAt' => $this->createdAt, + 'updatedAt' => $this->updatedAt, + ]; + } + + public static function getName(): string + { + return Resource::TYPE_PLATFORM; + } + + public function getGroup(): string + { + return Transfer::GROUP_INTEGRATIONS; + } + + public function getType(): string + { + return $this->type; + } + + public function getPlatformName(): string + { + return $this->name; + } + + public function getKey(): string + { + return $this->key; + } + + public function getStore(): string + { + return $this->store; + } + + public function getHostname(): string + { + return $this->hostname; + } +} diff --git a/src/Migration/Source.php b/src/Migration/Source.php index cd38ebbd..bf129b2b 100644 --- a/src/Migration/Source.php +++ b/src/Migration/Source.php @@ -46,6 +46,11 @@ public function getSitesBatchSize(): int return static::$defaultBatchSize; } + public function getIntegrationsBatchSize(): int + { + return static::$defaultBatchSize; + } + public function getBackupsBatchSize(): int { return static::$defaultBatchSize; @@ -114,6 +119,7 @@ public function exportResources(array $resources): void Transfer::GROUP_FUNCTIONS => Transfer::GROUP_FUNCTIONS_RESOURCES, Transfer::GROUP_MESSAGING => Transfer::GROUP_MESSAGING_RESOURCES, Transfer::GROUP_SITES => Transfer::GROUP_SITES_RESOURCES, + Transfer::GROUP_INTEGRATIONS => Transfer::GROUP_INTEGRATIONS_RESOURCES, Transfer::GROUP_BACKUPS => Transfer::GROUP_BACKUPS_RESOURCES, ]; @@ -149,6 +155,9 @@ public function exportResources(array $resources): void case Transfer::GROUP_SITES: $this->exportGroupSites($this->getSitesBatchSize(), $resources); break; + case Transfer::GROUP_INTEGRATIONS: + $this->exportGroupIntegrations($this->getIntegrationsBatchSize(), $resources); + break; case Transfer::GROUP_BACKUPS: $this->exportGroupBackups($this->getBackupsBatchSize(), $resources); break; @@ -204,6 +213,14 @@ abstract protected function exportGroupMessaging(int $batchSize, array $resource */ abstract protected function exportGroupSites(int $batchSize, array $resources): void; + /** + * Export Integrations Group + * + * @param int $batchSize + * @param array $resources Resources to export + */ + abstract protected function exportGroupIntegrations(int $batchSize, array $resources): void; + /** * Export Backups Group * diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 94368e5e..2e245cf6 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -54,6 +54,7 @@ use Utopia\Migration\Resources\Functions\Deployment; use Utopia\Migration\Resources\Functions\EnvVar; use Utopia\Migration\Resources\Functions\Func; +use Utopia\Migration\Resources\Integrations\Platform; use Utopia\Migration\Resources\Messaging\Message; use Utopia\Migration\Resources\Messaging\Provider; use Utopia\Migration\Resources\Messaging\Subscriber; @@ -200,6 +201,9 @@ public static function getSupportedResources(): array Resource::TYPE_SITE_DEPLOYMENT, Resource::TYPE_SITE_VARIABLE, + // Integrations + Resource::TYPE_PLATFORM, + // Backups Resource::TYPE_BACKUP_POLICY, @@ -242,6 +246,7 @@ public function report(array $resources = [], array $resourceIds = []): array $this->reportFunctions($resources, $report, $resourceIds); $this->reportMessaging($resources, $report, $resourceIds); $this->reportSites($resources, $report, $resourceIds); + $this->reportIntegrations($resources, $report, $resourceIds); $this->reportBackups($resources, $report, $resourceIds); $report['version'] = $this->call( @@ -2211,6 +2216,23 @@ private function exportSiteDeploymentData(Site $site, \Appwrite\Models\Deploymen } } + /** + * @param array $resources + * @param array $report + * @param array> $resourceIds + */ + private function reportIntegrations(array $resources, array &$report, array $resourceIds = []): void + { + if (\in_array(Resource::TYPE_PLATFORM, $resources)) { + try { + $response = $this->call('GET', '/projects/' . $this->project . '/platforms'); + $report[Resource::TYPE_PLATFORM] = $response['total'] ?? 0; + } catch (\Throwable) { + $report[Resource::TYPE_PLATFORM] = 0; + } + } + } + /** * @param string $databaseType * @param array $database { @@ -2235,6 +2257,27 @@ public static function getDatabase(string $databaseType, array $database): Resou } } + /** + * @param int $batchSize + * @param array $resources + */ + protected function exportGroupIntegrations(int $batchSize, array $resources): void + { + if (\in_array(Resource::TYPE_PLATFORM, $resources)) { + try { + $this->exportPlatforms(); + } catch (\Throwable $e) { + $this->addError(new Exception( + Resource::TYPE_PLATFORM, + Transfer::GROUP_INTEGRATIONS, + message: $e->getMessage(), + code: $e->getCode(), + previous: $e + )); + } + } + } + /** * eg., tables,collections * @param string $databaseType @@ -2265,6 +2308,35 @@ public static function getEntity(string $databaseType, array $entity): Resource } } + /** + * @throws AppwriteException + */ + private function exportPlatforms(): void + { + $response = $this->call('GET', '/projects/' . $this->project . '/platforms'); + + if (empty($response['platforms'])) { + return; + } + + $platforms = []; + + foreach ($response['platforms'] as $platform) { + $platforms[] = new Platform( + $platform['$id'] ?? '', + $platform['type'] ?? '', + $platform['name'] ?? '', + $platform['key'] ?? '', + $platform['store'] ?? '', + $platform['hostname'] ?? '', + createdAt: $platform['$createdAt'] ?? '', + updatedAt: $platform['$updatedAt'] ?? '', + ); + } + + $this->callback($platforms); + } + /** * eg.,documents/attributes * @param string $databaseType diff --git a/src/Migration/Sources/CSV.php b/src/Migration/Sources/CSV.php index 762ed2fb..678b3b9e 100644 --- a/src/Migration/Sources/CSV.php +++ b/src/Migration/Sources/CSV.php @@ -430,6 +430,11 @@ protected function exportGroupSites(int $batchSize, array $resources): void throw new \Exception('Not Implemented'); } + protected function exportGroupIntegrations(int $batchSize, array $resources): void + { + throw new \Exception('Not Implemented'); + } + protected function exportGroupBackups(int $batchSize, array $resources): void { throw new \Exception('Not Implemented'); diff --git a/src/Migration/Sources/Firebase.php b/src/Migration/Sources/Firebase.php index f01b7729..007797b5 100644 --- a/src/Migration/Sources/Firebase.php +++ b/src/Migration/Sources/Firebase.php @@ -818,6 +818,11 @@ protected function exportGroupSites(int $batchSize, array $resources): void throw new \Exception('Not implemented'); } + protected function exportGroupIntegrations(int $batchSize, array $resources): void + { + throw new \Exception('Not implemented'); + } + protected function exportGroupBackups(int $batchSize, array $resources): void { throw new \Exception('Not implemented'); diff --git a/src/Migration/Sources/JSON.php b/src/Migration/Sources/JSON.php index 8f2a81ae..0b3e7829 100644 --- a/src/Migration/Sources/JSON.php +++ b/src/Migration/Sources/JSON.php @@ -222,6 +222,11 @@ protected function exportGroupSites(int $batchSize, array $resources): void throw new \Exception('Not Implemented'); } + protected function exportGroupIntegrations(int $batchSize, array $resources): void + { + throw new \Exception('Not Implemented'); + } + /** * @param callable(Items): void $callback * @throws \Exception|JsonMachineException diff --git a/src/Migration/Sources/NHost.php b/src/Migration/Sources/NHost.php index 1a8d70cb..c72ca812 100644 --- a/src/Migration/Sources/NHost.php +++ b/src/Migration/Sources/NHost.php @@ -953,6 +953,11 @@ protected function exportGroupSites(int $batchSize, array $resources): void throw new \Exception('Not Implemented'); } + protected function exportGroupIntegrations(int $batchSize, array $resources): void + { + throw new \Exception('Not Implemented'); + } + protected function exportGroupBackups(int $batchSize, array $resources): void { throw new \Exception('Not Implemented'); diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index e8c5ee5d..7925fb32 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -22,7 +22,7 @@ class Transfer public const GROUP_DATABASES_VECTOR_DB = 'vectorsdb'; - public const GROUP_SETTINGS = 'settings'; + public const GROUP_INTEGRATIONS = 'integrations'; public const GROUP_MESSAGING = 'messaging'; @@ -60,6 +60,9 @@ class Transfer Resource::TYPE_ROW, ]; + public const GROUP_INTEGRATIONS_RESOURCES = [ + Resource::TYPE_PLATFORM, + ]; public const GROUP_DOCUMENTSDB_RESOURCES = [ Resource::TYPE_DATABASE_DOCUMENTSDB, Resource::TYPE_COLLECTION, @@ -124,6 +127,9 @@ class Transfer Resource::TYPE_MESSAGE, Resource::TYPE_BACKUP_POLICY, + // Integrations + Resource::TYPE_PLATFORM, + // legacy Resource::TYPE_DOCUMENT, Resource::TYPE_ATTRIBUTE, @@ -139,6 +145,7 @@ class Transfer Resource::TYPE_SITE, Resource::TYPE_USER, Resource::TYPE_TEAM, + Resource::TYPE_PLATFORM, Resource::TYPE_PROVIDER, Resource::TYPE_TOPIC, Resource::TYPE_MESSAGE, @@ -401,10 +408,10 @@ public static function extractServices(array $services): array self::GROUP_GENERAL => array_merge($resources, []), self::GROUP_AUTH => array_merge($resources, self::GROUP_AUTH_RESOURCES), self::GROUP_DATABASES => array_merge($resources, self::GROUP_DATABASES_RESOURCES), + self::GROUP_INTEGRATIONS => array_merge($resources, self::GROUP_INTEGRATIONS_RESOURCES), self::GROUP_DATABASES_TABLES_DB => array_merge($resources, self::GROUP_TABLESDB_RESOURCES), self::GROUP_DATABASES_DOCUMENTS_DB => array_merge($resources, self::GROUP_DOCUMENTSDB_RESOURCES), self::GROUP_DATABASES_VECTOR_DB => array_merge($resources, self::GROUP_VECTORSDB_RESOURCES), - self::GROUP_SETTINGS => array_merge($resources, self::GROUP_SETTINGS_RESOURCES), self::GROUP_MESSAGING => array_merge($resources, self::GROUP_MESSAGING_RESOURCES), self::GROUP_BACKUPS => array_merge($resources, self::GROUP_BACKUPS_RESOURCES), default => throw new \Exception('No service group found'), diff --git a/tests/Migration/Unit/Adapters/MockDestination.php b/tests/Migration/Unit/Adapters/MockDestination.php index fe1da710..2aa528a8 100644 --- a/tests/Migration/Unit/Adapters/MockDestination.php +++ b/tests/Migration/Unit/Adapters/MockDestination.php @@ -51,6 +51,7 @@ public static function getSupportedResources(): array Resource::TYPE_ENVIRONMENT_VARIABLE, Resource::TYPE_TEAM, Resource::TYPE_MEMBERSHIP, + Resource::TYPE_PLATFORM, Resource::TYPE_PROVIDER, Resource::TYPE_TOPIC, Resource::TYPE_SUBSCRIBER, diff --git a/tests/Migration/Unit/Adapters/MockSource.php b/tests/Migration/Unit/Adapters/MockSource.php index d4504c88..a309f91e 100644 --- a/tests/Migration/Unit/Adapters/MockSource.php +++ b/tests/Migration/Unit/Adapters/MockSource.php @@ -80,6 +80,7 @@ public static function getSupportedResources(): array Resource::TYPE_ENVIRONMENT_VARIABLE, Resource::TYPE_TEAM, Resource::TYPE_MEMBERSHIP, + Resource::TYPE_PLATFORM, Resource::TYPE_PROVIDER, Resource::TYPE_TOPIC, Resource::TYPE_SUBSCRIBER, @@ -200,6 +201,23 @@ protected function exportGroupSites(int $batchSize, array $resources): void } } + /** + * Export Integrations Group + * + * @param int $batchSize Max 100 + * @param string[] $resources Resources to export + */ + protected function exportGroupIntegrations(int $batchSize, array $resources): void + { + foreach (Transfer::GROUP_INTEGRATIONS_RESOURCES as $resource) { + if (!\in_array($resource, $resources)) { + continue; + } + + $this->handleResourceTransfer(Transfer::GROUP_INTEGRATIONS, $resource); + } + } + protected function exportGroupBackups(int $batchSize, array $resources): void { foreach (Transfer::GROUP_BACKUPS_RESOURCES as $resource) {