From 86a5f68ce6eb75a3bdd0faf255cc97b28b3e0773 Mon Sep 17 00:00:00 2001 From: shimon Date: Fri, 8 Mar 2024 18:11:08 +0100 Subject: [PATCH 001/185] first commit --- playground.php | 2 +- src/Migration/Destination.php | 2 + src/Migration/Source.php | 4 + src/Migration/Sources/Appwrite.php | 5 + src/Migration/Sources/AppwriteInternal.php | 1218 ++++++++++++++++++++ src/Migration/Transfer.php | 2 + 6 files changed, 1232 insertions(+), 1 deletion(-) create mode 100644 src/Migration/Sources/AppwriteInternal.php diff --git a/playground.php b/playground.php index 571a768..a049668 100644 --- a/playground.php +++ b/playground.php @@ -70,7 +70,7 @@ */ $transfer = new Transfer( $sourceAppwrite, - $destinationAppwrite + $destinationLocal ); /** diff --git a/src/Migration/Destination.php b/src/Migration/Destination.php index bde243e..2b291c5 100644 --- a/src/Migration/Destination.php +++ b/src/Migration/Destination.php @@ -35,7 +35,9 @@ public function setSource(Source $source): self */ public function run(array $resources, callable $callback): void { + $this->source->run($resources, function (array $resources) use ($callback) { + $this->import($resources, $callback); }); } diff --git a/src/Migration/Source.php b/src/Migration/Source.php index 45ba29a..e187bea 100644 --- a/src/Migration/Source.php +++ b/src/Migration/Source.php @@ -24,8 +24,11 @@ public function callback(array $resources): void */ public function run(array $resources, callable $callback): void { + + $this->transferCallback = function (array $returnedResources) use ($callback, $resources) { $prunedResurces = []; + foreach ($returnedResources as $resource) { /** @var resource $resource */ if (! in_array($resource->getName(), $resources)) { @@ -71,6 +74,7 @@ public function exportResources(array $resources, int $batchSize) // Send each group to the relevant export function foreach ($groups as $group => $resources) { + switch ($group) { case Transfer::GROUP_AUTH: $this->exportGroupAuth($batchSize, $resources); diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index b5e8651..82f0333 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -272,11 +272,15 @@ public function report(array $resources = []): array */ protected function exportGroupAuth(int $batchSize, array $resources) { + if (in_array(Resource::TYPE_USER, $resources)) { + $this->exportUsers($batchSize); + } if (in_array(Resource::TYPE_TEAM, $resources)) { + $this->exportTeams($batchSize); } @@ -680,6 +684,7 @@ private function convertAttribute(array $value, Collection $collection): Attribu private function exportDatabases(int $batchSize) { + $databaseClient = new Databases($this->client); $lastDatabase = null; diff --git a/src/Migration/Sources/AppwriteInternal.php b/src/Migration/Sources/AppwriteInternal.php new file mode 100644 index 0000000..13dd443 --- /dev/null +++ b/src/Migration/Sources/AppwriteInternal.php @@ -0,0 +1,1218 @@ +project = $project; + } + + public static function getName(): string + { + return 'Appwrite'; + } + + public static function getSupportedResources(): array + { + return [ + // Auth + Resource::TYPE_USER, + Resource::TYPE_TEAM, + Resource::TYPE_MEMBERSHIP, + + // Database + Resource::TYPE_DATABASE, + Resource::TYPE_COLLECTION, + Resource::TYPE_ATTRIBUTE, + Resource::TYPE_INDEX, + Resource::TYPE_DOCUMENT, + + // Storage + Resource::TYPE_BUCKET, + Resource::TYPE_FILE, + + // Functions + Resource::TYPE_FUNCTION, + Resource::TYPE_DEPLOYMENT, + Resource::TYPE_ENVIRONMENT_VARIABLE, + + // Settings + ]; + } + + public function report(array $resources = []): array + { + $report = []; + $currentPermission = ''; + + if (empty($resources)) { + $resources = $this->getSupportedResources(); + } + + $usersClient = new Users($this->client); + $teamsClient = new Teams($this->client); + $databaseClient = new Databases($this->client); + $storageClient = new Storage($this->client); + $functionsClient = new Functions($this->client); + + // Auth + try { + $currentPermission = 'users.read'; + if (in_array(Resource::TYPE_USER, $resources)) { + $report[Resource::TYPE_USER] = $usersClient->list()['total']; + } + + $currentPermission = 'teams.read'; + if (in_array(Resource::TYPE_TEAM, $resources)) { + $report[Resource::TYPE_TEAM] = $teamsClient->list()['total']; + } + + if (in_array(Resource::TYPE_MEMBERSHIP, $resources)) { + $report[Resource::TYPE_MEMBERSHIP] = 0; + $teams = $teamsClient->list()['teams']; + foreach ($teams as $team) { + $report[Resource::TYPE_MEMBERSHIP] += $teamsClient->listMemberships($team['$id'], [Query::limit(1)])['total']; + } + } + + // Databases + $currentPermission = 'databases.read'; + if (in_array(Resource::TYPE_DATABASE, $resources)) { + $report[Resource::TYPE_DATABASE] = $databaseClient->list()['total']; + } + + $currentPermission = 'collections.read'; + if (in_array(Resource::TYPE_COLLECTION, $resources)) { + $report[Resource::TYPE_COLLECTION] = 0; + $databases = $databaseClient->list()['databases']; + foreach ($databases as $database) { + $report[Resource::TYPE_COLLECTION] += $databaseClient->listCollections($database['$id'], [Query::limit(1)])['total']; + } + } + + $currentPermission = 'documents.read'; + if (in_array(Resource::TYPE_DOCUMENT, $resources)) { + $report[Resource::TYPE_DOCUMENT] = 0; + $databases = $databaseClient->list()['databases']; + foreach ($databases as $database) { + $collections = $databaseClient->listCollections($database['$id'])['collections']; + foreach ($collections as $collection) { + $report[Resource::TYPE_DOCUMENT] += $databaseClient->listDocuments($database['$id'], $collection['$id'], [Query::limit(1)])['total']; + } + } + } + + $currentPermission = 'attributes.read'; + if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) { + $report[Resource::TYPE_ATTRIBUTE] = 0; + $databases = $databaseClient->list()['databases']; + foreach ($databases as $database) { + $collections = $databaseClient->listCollections($database['$id'])['collections']; + foreach ($collections as $collection) { + $report[Resource::TYPE_ATTRIBUTE] += $databaseClient->listAttributes($database['$id'], $collection['$id'])['total']; + } + } + } + + $currentPermission = 'indexes.read'; + if (in_array(Resource::TYPE_INDEX, $resources)) { + $report[Resource::TYPE_INDEX] = 0; + $databases = $databaseClient->list()['databases']; + foreach ($databases as $database) { + $collections = $databaseClient->listCollections($database['$id'])['collections']; + foreach ($collections as $collection) { + $report[Resource::TYPE_INDEX] += $databaseClient->listIndexes($database['$id'], $collection['$id'])['total']; + } + } + } + + // Storage + $currentPermission = 'buckets.read'; + if (in_array(Resource::TYPE_BUCKET, $resources)) { + $report[Resource::TYPE_BUCKET] = $storageClient->listBuckets()['total']; + } + + $currentPermission = 'files.read'; + if (in_array(Resource::TYPE_FILE, $resources)) { + $report[Resource::TYPE_FILE] = 0; + $report['size'] = 0; + $buckets = []; + $lastBucket = null; + + while (true) { + $currentBuckets = $storageClient->listBuckets($lastBucket ? [Query::cursorAfter($lastBucket)] : [Query::limit(20)])['buckets']; + $buckets = array_merge($buckets, $currentBuckets); + $lastBucket = $buckets[count($buckets) - 1]['$id']; + + if (count($currentBuckets) < 20) { + break; + } + } + + foreach ($buckets as $bucket) { + $files = []; + $lastFile = null; + + while (true) { + $currentFiles = $storageClient->listFiles($bucket['$id'], $lastFile ? [Query::cursorAfter($lastFile)] : [Query::limit(20)])['files']; + $files = array_merge($files, $currentFiles); + $lastFile = $files[count($files) - 1]['$id']; + + if (count($currentFiles) < 20) { + break; + } + } + + $report[Resource::TYPE_FILE] += count($files); + foreach ($files as $file) { + $report['size'] += $storageClient->getFile($bucket['$id'], $file['$id'])['sizeOriginal']; + } + } + $report['size'] = $report['size'] / 1000 / 1000; // MB + } + + // Functions + $currentPermission = 'functions.read'; + if (in_array(Resource::TYPE_FUNCTION, $resources)) { + $report[Resource::TYPE_FUNCTION] = $functionsClient->list()['total']; + } + + if (in_array(Resource::TYPE_DEPLOYMENT, $resources)) { + $report[Resource::TYPE_DEPLOYMENT] = 0; + $functions = $functionsClient->list()['functions']; + foreach ($functions as $function) { + $report[Resource::TYPE_DEPLOYMENT] += $functionsClient->listDeployments($function['$id'], [Query::limit(1)])['total']; + } + } + + if (in_array(Resource::TYPE_ENVIRONMENT_VARIABLE, $resources)) { + $report[Resource::TYPE_ENVIRONMENT_VARIABLE] = 0; + $functions = $functionsClient->list()['functions']; + foreach ($functions as $function) { + $report[Resource::TYPE_ENVIRONMENT_VARIABLE] += $functionsClient->listVariables($function['$id'])['total']; + } + } + + $report['version'] = $this->call('GET', '/health/version', ['X-Appwrite-Key' => '', 'X-Appwrite-Project' => ''])['version']; + + $this->previousReport = $report; + + return $report; + } catch (\Throwable $e) { + if ($e->getCode() === 403) { + throw new \Exception("Missing Permission: {$currentPermission}."); + } else { + throw new \Exception($e->getMessage()); + } + } + } + + /** + * Export Auth Resources + * + * @param int $batchSize Max 100 + * @param string[] $resources + * @return void + */ + protected function exportGroupAuth(int $batchSize, array $resources) + { + + if (in_array(Resource::TYPE_USER, $resources)) { + + $this->exportUsers($batchSize); + + } + + if (in_array(Resource::TYPE_TEAM, $resources)) { + + $this->exportTeams($batchSize); + } + + if (in_array(Resource::TYPE_MEMBERSHIP, $resources)) { + $this->exportMemberships($batchSize); + } + } + + private function exportUsers(int $batchSize) + { + $usersClient = new Users($this->client); + $lastDocument = null; + + // Export Users + while (true) { + $users = []; + + $queries = [Query::limit($batchSize)]; + + if ($lastDocument) { + $queries[] = Query::cursorAfter($lastDocument); + } + + $response = $usersClient->list($queries); + + if ($response['total'] == 0) { + break; + } + + foreach ($response['users'] as $user) { + $users[] = new User( + $user['$id'], + $user['email'], + $user['name'], + $user['password'] ? new Hash($user['password'], algorithm: $user['hash']) : null, + $user['phone'], + $this->calculateTypes($user), + $user['labels'] ?? [], + '', + $user['emailVerification'] ?? false, + $user['phoneVerification'] ?? false, + ! $user['status'], + $user['prefs'] ?? [], + ); + + $lastDocument = $user['$id']; + } + + $this->callback($users); + + if (count($users) < $batchSize) { + break; + } + } + } + + private function exportTeams(int $batchSize) + { + $teamsClient = new Teams($this->client); + $lastDocument = null; + + // Export Teams + while (true) { + $teams = []; + + $queries = [Query::limit($batchSize)]; + + if ($lastDocument) { + $queries[] = Query::cursorAfter($lastDocument); + } + + $response = $teamsClient->list($queries); + + if ($response['total'] == 0) { + break; + } + + foreach ($response['teams'] as $team) { + $teams[] = new Team( + $team['$id'], + $team['name'], + $team['prefs'], + ); + + $lastDocument = $team['$id']; + } + + $this->callback($teams); + + if (count($teams) < $batchSize) { + break; + } + } + } + + private function exportMemberships(int $batchSize) + { + $teamsClient = new Teams($this->client); + + // Export Memberships + $cacheTeams = $this->cache->get(Team::getName()); + /** @var array - array where key is user ID */ + $cacheUsers = []; + foreach ($this->cache->get(User::getName()) as $cacheUser) { + /** @var User $cacheUser */ + $cacheUsers[$cacheUser->getId()] = $cacheUser; + } + + foreach ($cacheTeams as $team) { + /** @var Team $team */ + $lastDocument = null; + + while (true) { + $memberships = []; + + $queries = [Query::limit($batchSize)]; + + if ($lastDocument) { + $queries[] = Query::cursorAfter($lastDocument); + } + + $response = $teamsClient->listMemberships($team->getId(), $queries); + + if ($response['total'] == 0) { + break; + } + + foreach ($response['memberships'] as $membership) { + $user = $cacheUsers[$membership['userId']] ?? null; + if ($user === null) { + throw new \Exception('User not found'); + } + + $memberships[] = new Membership( + $team, + $user, + $membership['roles'], + $membership['confirm'] + ); + + $lastDocument = $membership['$id']; + } + + $this->callback($memberships); + + if (count($memberships) < $batchSize) { + break; + } + } + } + } + + protected function exportGroupDatabases(int $batchSize, array $resources) + { + if (in_array(Resource::TYPE_DATABASE, $resources)) { + $this->exportDatabases($batchSize); + } + + if (in_array(Resource::TYPE_COLLECTION, $resources)) { + $this->exportCollections($batchSize); + } + + if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) { + $this->exportAttributes($batchSize); + } + + if (in_array(Resource::TYPE_INDEX, $resources)) { + $this->exportIndexes($batchSize); + } + + if (in_array(Resource::TYPE_DOCUMENT, $resources)) { + $this->exportDocuments($batchSize); + } + } + + public function stripMetadata(array $document, bool $root = true) + { + if ($root) { + unset($document['$id']); + } + + unset($document['$permissions']); + unset($document['$collectionId']); + unset($document['$updatedAt']); + unset($document['$createdAt']); + unset($document['$databaseId']); + + foreach ($document as $key => $value) { + if (is_array($value)) { + $document[$key] = $this->stripMetadata($value, false); + } + } + + return $document; + } + + private function exportDocuments(int $batchSize) + { + $databaseClient = new Databases($this->client); + $collections = $this->cache->get(Collection::getName()); + + foreach ($collections as $collection) { + /** @var Collection $collection */ + $lastDocument = null; + + while (true) { + $queries = [Query::limit($batchSize)]; + + $documents = []; + + if ($lastDocument) { + $queries[] = Query::cursorAfter($lastDocument); + } + + $response = $databaseClient->listDocuments( + $collection->getDatabase()->getId(), + $collection->getId(), + $queries + ); + + foreach ($response['documents'] as $document) { + $id = $document['$id']; + $permissions = $document['$permissions']; + + $document = $this->stripMetadata($document); + + // Certain Appwrite versions allowed for data to be required but null + // This isn't allowed in modern versions so we need to remove it by comparing their attributes and replacing it with default value. + $attributes = $this->cache->get(Attribute::getName()); + foreach ($attributes as $attribute) { + /** @var Attribute $attribute */ + if ($attribute->getCollection()->getId() !== $collection->getId()) { + continue; + } + + if ($attribute->getRequired() && ! isset($document[$attribute->getKey()])) { + switch ($attribute->getTypeName()) { + case Attribute::TYPE_BOOLEAN: + $document[$attribute->getKey()] = false; + break; + case Attribute::TYPE_STRING: + $document[$attribute->getKey()] = ''; + break; + case Attribute::TYPE_INTEGER: + $document[$attribute->getKey()] = 0; + break; + case Attribute::TYPE_FLOAT: + $document[$attribute->getKey()] = 0.0; + break; + case Attribute::TYPE_DATETIME: + $document[$attribute->getKey()] = 0; + break; + case Attribute::TYPE_URL: + $document[$attribute->getKey()] = 'http://null'; + break; + } + } + } + + $cleanData = $this->stripMetadata($document); + + $documents[] = new Document( + $id, + $collection->getDatabase(), + $collection, + $cleanData, + $permissions + ); + $lastDocument = $id; + } + + $this->callback($documents); + + if (count($response['documents']) < $batchSize) { + break; + } + } + } + } + + private function convertAttribute(array $value, Collection $collection): Attribute + { + switch ($value['type']) { + case 'string': + if (! isset($value['format'])) { + return new Text( + $value['key'], + $collection, + $value['required'], + $value['array'], + $value['default'], + $value['size'] ?? 0 + ); + } + + switch ($value['format']) { + case 'email': + return new Email( + $value['key'], + $collection, + $value['required'], + $value['array'], + $value['default'] + ); + case 'enum': + return new Enum( + $value['key'], + $collection, + $value['elements'], + $value['required'], + $value['array'], + $value['default'] + ); + case 'url': + return new URL( + $value['key'], + $collection, + $value['required'], + $value['array'], + $value['default'] + ); + case 'ip': + return new IP( + $value['key'], + $collection, + $value['required'], + $value['array'], + $value['default'] + ); + case 'datetime': + return new DateTime( + $value['key'], + $collection, + $value['required'], + $value['array'], + $value['default'] + ); + default: + return new Text( + $value['key'], + $collection, + $value['required'], + $value['array'], + $value['default'], + $value['size'] ?? 0 + ); + } + case 'boolean': + return new Boolean( + $value['key'], + $collection, + $value['required'], + $value['array'], + $value['default'] + ); + case 'integer': + return new Integer( + $value['key'], + $collection, + $value['required'], + $value['array'], + $value['default'], + $value['min'] ?? 0, + $value['max'] ?? 0 + ); + case 'double': + return new Decimal( + $value['key'], + $collection, + $value['required'], + $value['array'], + $value['default'], + $value['min'] ?? 0, + $value['max'] ?? 0 + ); + case 'relationship': + return new Relationship( + $value['key'], + $collection, + $value['required'], + $value['array'], + $value['relatedCollection'], + $value['relationType'], + $value['twoWay'], + $value['twoWayKey'], + $value['onDelete'], + $value['side'] + ); + case 'datetime': + return new DateTime( + $value['key'], + $collection, + $value['required'], + $value['array'], + $value['default'] + ); + } + + throw new \Exception('Unknown attribute type: '.$value['type']); + } + + private function exportDatabases(int $batchSize) + { + + $databaseClient = new Databases($this->client); + + $lastDatabase = null; + + // Transfer Databases + while (true) { + $queries = [Query::limit($batchSize)]; + $databases = []; + + if ($lastDatabase) { + $queries[] = Query::cursorAfter($lastDatabase); + } + + $response = $databaseClient->list($queries); + + foreach ($response['databases'] as $database) { + $newDatabase = new Database( + $database['name'], + $database['$id'] + ); + + $databases[] = $newDatabase; + } + + if (empty($databases)) { + break; + } + + $lastDatabase = $databases[count($databases) - 1]->getId(); + + $this->callback($databases); + + if (count($databases) < $batchSize) { + break; + } + } + } + + private function exportCollections(int $batchSize) + { + $databaseClient = new Databases($this->client); + + // Transfer Collections + + $databases = $this->cache->get(Database::getName()); + foreach ($databases as $database) { + $lastCollection = null; + + /** @var Database $database */ + while (true) { + $queries = [Query::limit($batchSize)]; + $collections = []; + + if ($lastCollection) { + $queries[] = Query::cursorAfter($lastCollection); + } + + $response = $databaseClient->listCollections( + $database->getId(), + $queries + ); + + foreach ($response['collections'] as $collection) { + $newCollection = new Collection( + $database, + $collection['name'], + $collection['$id'], + $collection['documentSecurity'], + $collection['$permissions'] + ); + + $collections[] = $newCollection; + } + + $lastCollection = $collections[count($collections) - 1]->getId(); + + $this->callback($collections); + + if (count($collections) < $batchSize) { + break; + } + } + } + } + + private function exportAttributes(int $batchSize) + { + $databaseClient = new Databases($this->client); + + // Transfer Attributes + $collections = $this->cache->get(Collection::getName()); + /** @var Collection[] $collections */ + foreach ($collections as $collection) { + $lastAttribute = null; + + while (true) { + $queries = [Query::limit($batchSize)]; + $attributes = []; + + if ($lastAttribute) { + $queries[] = Query::cursorAfter($lastAttribute); + } + + $response = $databaseClient->listAttributes( + $collection->getDatabase()->getId(), + $collection->getId(), + $queries + ); + + // Remove two way relationship attributes + $this->cache->get(Resource::TYPE_ATTRIBUTE); + + $knownTwoways = []; + + foreach ($this->cache->get(Resource::TYPE_ATTRIBUTE) as $attribute) { + /** @var Attribute|Relationship $attribute */ + if ($attribute->getTypeName() == Attribute::TYPE_RELATIONSHIP && $attribute->getTwoWay()) { + $knownTwoways[] = $attribute->getTwoWayKey(); + } + } + + foreach ($response['attributes'] as $attribute) { + if (in_array($attribute['key'], $knownTwoways)) { + continue; + } + + if ($attribute['type'] === 'relationship') { + $knownTwoways[] = $attribute['twoWayKey']; + } + + $attributes[] = $this->convertAttribute($attribute, $collection); + } + + if (empty($attributes)) { + break; + } + + $this->callback($attributes); + + $lastAttribute = $attributes[count($attributes) - 1]->getId(); + if (count($attributes) < $batchSize) { + break; + } + } + } + } + + private function exportIndexes(int $batchSize) + { + $databaseClient = new Databases($this->client); + + $collections = $this->cache->get(Resource::TYPE_COLLECTION); + + // Transfer Indexes + foreach ($collections as $collection) { + /** @var Collection $collection */ + $lastIndex = null; + + while (true) { + $queries = [Query::limit($batchSize)]; + $indexes = []; + + if ($lastIndex) { + $queries[] = Query::cursorAfter($lastIndex); + } + + $response = $databaseClient->listIndexes( + $collection->getDatabase()->getId(), + $collection->getId(), + $queries + ); + + foreach ($response['indexes'] as $index) { + $indexes[] = new Index( + 'unique()', + $index['key'], + $collection, + $index['type'], + $index['attributes'], + $index['orders'] + ); + } + + if (empty($indexes)) { + break; + } + + $this->callback($indexes); + $lastIndex = $indexes[count($indexes) - 1]->getId(); + + if (count($indexes) < $batchSize) { + break; + } + } + } + } + + private function calculateTypes(array $user): array + { + if (empty($user['email']) && empty($user['phone'])) { + return [User::TYPE_ANONYMOUS]; + } + + $types = []; + + if (! empty($user['email']) && ! empty($user['password'])) { + $types[] = User::TYPE_PASSWORD; + } + + if (! empty($user['phone'])) { + $types[] = User::TYPE_PHONE; + } + + return $types; + } + + protected function exportGroupStorage(int $batchSize, array $resources) + { + if (in_array(Resource::TYPE_BUCKET, $resources)) { + $this->exportBuckets($batchSize, false); + } + + if (in_array(Resource::TYPE_FILE, $resources)) { + $this->exportFiles($batchSize); + } + + if (in_array(Resource::TYPE_BUCKET, $resources)) { + $this->exportBuckets($batchSize, true); + } + } + + private function exportBuckets(int $batchSize, bool $updateLimits) + { + $storageClient = new Storage($this->client); + + $buckets = $storageClient->listBuckets(); + + $convertedBuckets = []; + + foreach ($buckets['buckets'] as $bucket) { + $bucket = new Bucket( + $bucket['$id'], + $bucket['name'], + $bucket['$permissions'], + $bucket['fileSecurity'], + $bucket['enabled'], + $bucket['maximumFileSize'], + $bucket['allowedFileExtensions'], + $bucket['compression'], + $bucket['encryption'], + $bucket['antivirus'], + $updateLimits + ); + + $bucket->setUpdateLimits($updateLimits); + $convertedBuckets[] = $bucket; + } + + if (empty($convertedBuckets)) { + return; + } + + $this->callback($convertedBuckets); + } + + private function exportFiles(int $batchSize) + { + $storageClient = new Storage($this->client); + + $buckets = $this->cache->get(Bucket::getName()); + foreach ($buckets as $bucket) { + /** @var Bucket $bucket */ + $lastDocument = null; + + while (true) { + $queries = [Query::limit($batchSize)]; + + if ($lastDocument) { + $queries[] = Query::cursorAfter($lastDocument); + } + + $response = $storageClient->listFiles( + $bucket->getId(), + $queries + ); + + foreach ($response['files'] as $file) { + try { + $this->exportFileData(new File( + $file['$id'], + $bucket, + $file['name'], + $file['signature'], + $file['mimeType'], + $file['$permissions'], + $file['sizeOriginal'], + )); + } catch (\Throwable $e) { + $this->addError(new Exception( + resourceType: Resource::TYPE_FILE, + message: $e->getMessage(), + code: $e->getCode(), + resourceId: $file['$id'] + )); + } + + $lastDocument = $file['$id']; + } + + if (count($response['files']) < $batchSize) { + break; + } + } + } + } + + private function exportFileData(File $file) + { + // Set the chunk size (5MB) + $start = 0; + $end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1; + + // Get the file size + $fileSize = $file->getSize(); + + if ($end > $fileSize) { + $end = $fileSize - 1; + } + + // Loop until the entire file is downloaded + while ($start < $fileSize) { + $chunkData = $this->call( + 'GET', + "/storage/buckets/{$file->getBucket()->getId()}/files/{$file->getId()}/download", + ['range' => "bytes=$start-$end"] + ); + + // Send the chunk to the callback function + $file->setData($chunkData) + ->setStart($start) + ->setEnd($end); + + $this->callback([$file]); + + // Update the range + $start += Transfer::STORAGE_MAX_CHUNK_SIZE; + $end += Transfer::STORAGE_MAX_CHUNK_SIZE; + + if ($end > $fileSize) { + $end = $fileSize - 1; + } + } + } + + protected function exportGroupFunctions(int $batchSize, array $resources) + { + if (in_array(Resource::TYPE_FUNCTION, $resources)) { + $this->exportFunctions($batchSize); + } + + if (in_array(Resource::TYPE_DEPLOYMENT, $resources)) { + $this->exportDeployments($batchSize, true); + } + } + + private function exportFunctions(int $batchSize) + { + $functionsClient = new Functions($this->client); + + $functions = $functionsClient->list(); + + if ($functions['total'] === 0) { + return; + } + + $convertedResources = []; + + foreach ($functions['functions'] as $function) { + $convertedFunc = new Func( + $function['name'], + $function['$id'], + $function['runtime'], + $function['execute'], + $function['enabled'], + $function['events'], + $function['schedule'], + $function['timeout'], + $function['deployment'] + ); + + $convertedResources[] = $convertedFunc; + + foreach ($function['vars'] as $var) { + $convertedResources[] = new EnvVar( + $convertedFunc, + $var['key'], + $var['value'], + ); + } + } + + $this->callback($convertedResources); + } + + private function exportDeployments(int $batchSize, bool $exportOnlyActive = false) + { + $functionsClient = new Functions($this->client); + $functions = $this->cache->get(Func::getName()); + + // exportDeploymentData doesn't exist on Appwrite versions prior to 1.4 + $appwriteVersion = $this->call('GET', '/health/version', ['X-Appwrite-Key' => '', 'X-Appwrite-Project' => ''])['version']; + + if (version_compare($appwriteVersion, '1.4.0', '<')) { + return; + } + + foreach ($functions as $func) { + /** @var Func $func */ + $lastDocument = null; + + if ($exportOnlyActive && $func->getActiveDeployment()) { + $deployment = $functionsClient->getDeployment($func->getId(), $func->getActiveDeployment()); + + try { + $this->exportDeploymentData($func, $deployment); + } catch (\Throwable $e) { + $func->setStatus(Resource::STATUS_ERROR, $e->getMessage()); + } + + continue; + } + + while (true) { + $queries = [Query::limit($batchSize)]; + + if ($lastDocument) { + $queries[] = Query::cursorAfter($lastDocument); + } + + $response = $functionsClient->listDeployments( + $func->getId(), + $queries + ); + + foreach ($response['deployments'] as $deployment) { + try { + $this->exportDeploymentData($func, $deployment); + } catch (\Throwable $e) { + $func->setStatus(Resource::STATUS_ERROR, $e->getMessage()); + } + + $lastDocument = $deployment['$id']; + } + + if (count($response['deployments']) < $batchSize) { + break; + } + } + } + } + + private function exportDeploymentData(Func $func, array $deployment) + { + // Set the chunk size (5MB) + $start = 0; + $end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1; + + // Get the file size + $responseHeaders = []; + + $this->call( + 'HEAD', + "/functions/{$func->getId()}/deployments/{$deployment['$id']}/download", + [], + [], + $responseHeaders + ); + + // Content-Length header was missing, file is less than max buffer size. + if (! array_key_exists('Content-Length', $responseHeaders)) { + $file = $this->call( + 'GET', + "/functions/{$func->getId()}/deployments/{$deployment['$id']}/download", + [], + [], + $responseHeaders + ); + + $deployment = new Deployment( + $deployment['$id'], + $func, + strlen($file), + $deployment['entrypoint'], + $start, + $end, + $file, + $deployment['activate'] + ); + $deployment->setInternalId($deployment->getId()); + + return $this->callback([$deployment]); + } + + $fileSize = $responseHeaders['Content-Length']; + + $deployment = new Deployment( + $deployment['$id'], + $func, + $fileSize, + $deployment['entrypoint'], + $start, + $end, + '', + $deployment['activate'] + ); + + $deployment->setInternalId($deployment->getId()); + + // Loop until the entire file is downloaded + while ($start < $fileSize) { + $chunkData = $this->call( + 'GET', + "/functions/{$func->getId()}/deployments/{$deployment->getInternalId()}/download", + ['range' => "bytes=$start-$end"] + ); + + // Send the chunk to the callback function + $deployment->setData($chunkData); + $deployment->setStart($start); + $deployment->setEnd($end); + $this->callback([$deployment]); + + // Update the range + $start += Transfer::STORAGE_MAX_CHUNK_SIZE; + $end += Transfer::STORAGE_MAX_CHUNK_SIZE; + + if ($end > $fileSize) { + $end = $fileSize - 1; + } + } + } +} diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index 6049ec9..e24ee69 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -142,6 +142,7 @@ public function getStatusCounters() */ public function run(array $resources, callable $callback): void { + // Allows you to push entire groups if you want. $computedResources = []; foreach ($resources as $resource) { @@ -155,6 +156,7 @@ public function run(array $resources, callable $callback): void $computedResources = array_map('strtolower', $computedResources); $this->resources = $computedResources; + $this->destination->run($computedResources, $callback, $this->source); } From f2272c34833bac5089198e4efb1fe057b82b6cea Mon Sep 17 00:00:00 2001 From: shimon Date: Sat, 9 Mar 2024 10:48:05 +0100 Subject: [PATCH 002/185] first commit --- composer.json | 4 +- playground.php | 6 +- src/Migration/Destinations/Backup.php | 145 ++++++++++++++++++++++++++ src/Migration/Sources/Backup.php | 80 ++++++++++++++ 4 files changed, 231 insertions(+), 4 deletions(-) create mode 100644 src/Migration/Destinations/Backup.php create mode 100644 src/Migration/Sources/Backup.php diff --git a/composer.json b/composer.json index d943d28..3c41742 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,9 @@ "require": { "php": "8.*", "utopia-php/cli": "0.*", - "appwrite/appwrite": "10.1.0" + "appwrite/appwrite": "10.1.0", + "utopia-php/database": "0.48.*", + "utopia-php/storage": "0.18.*" }, "require-dev": { "phpunit/phpunit": "9.*", diff --git a/playground.php b/playground.php index a049668..6613f30 100644 --- a/playground.php +++ b/playground.php @@ -11,6 +11,7 @@ use Utopia\CLI\Console; use Utopia\Migration\Destinations\Appwrite as AppwriteDestination; use Utopia\Migration\Destinations\Local; +use Utopia\Migration\Destinations\Backup; use Utopia\Migration\Sources\Appwrite; use Utopia\Migration\Sources\Firebase; use Utopia\Migration\Sources\NHost; @@ -63,14 +64,13 @@ $_ENV['DESTINATION_APPWRITE_TEST_KEY'] ); -$destinationLocal = new Local(__DIR__.'/localBackup/'); -/** +/**xx * Initialise Transfer Class */ $transfer = new Transfer( $sourceAppwrite, - $destinationLocal + new Backup(__DIR__ . '/localBackup/') ); /** diff --git a/src/Migration/Destinations/Backup.php b/src/Migration/Destinations/Backup.php new file mode 100644 index 0000000..732b306 --- /dev/null +++ b/src/Migration/Destinations/Backup.php @@ -0,0 +1,145 @@ +path = $path; + $this->database = $database; + $this->storage = $storage; + + if (! \file_exists($this->path)) { + mkdir($this->path, 0777, true); + mkdir($this->path.'/files', 0777, true); + mkdir($this->path.'/deployments', 0777, true); + } + } + + public static function getName(): string + { + return 'Backup'; + } + + public static function getSupportedResources(): array + { + return [ + Resource::TYPE_ATTRIBUTE, + Resource::TYPE_BUCKET, + Resource::TYPE_COLLECTION, + Resource::TYPE_DATABASE, + Resource::TYPE_DEPLOYMENT, + Resource::TYPE_DOCUMENT, + Resource::TYPE_ENVIRONMENT_VARIABLE, + Resource::TYPE_FILE, + Resource::TYPE_FUNCTION, + Resource::TYPE_HASH, + Resource::TYPE_INDEX, + Resource::TYPE_TEAM, + Resource::TYPE_MEMBERSHIP, + Resource::TYPE_USER, + ]; + } + + public function report(array $resources = []): array + { + $report = []; + + if (empty($resources)) { + $resources = $this->getSupportedResources(); + } + + // Check we can write to the file + if (! \is_writable($this->path.'/backup.json')) { + $report[Transfer::GROUP_DATABASES][] = 'Unable to write to file: '.$this->path; + throw new \Exception('Unable to write to file: '.$this->path); + } + + return $report; + } + + private function sync(): void + { + $jsonEncodedData = \json_encode($this->data, JSON_PRETTY_PRINT); + + if ($jsonEncodedData === false) { + throw new \Exception('Unable to encode data to JSON, Are you accidentally encoding binary data?'); + } + + \file_put_contents($this->path.'/backup.json', \json_encode($this->data, JSON_PRETTY_PRINT)); + } + + protected function import(array $resources, callable $callback): void + { + foreach ($resources as $resource) { + /** @var resource $resource */ + switch ($resource->getName()) { + case Resource::TYPE_DEPLOYMENT: + /** @var Deployment $resource */ + if ($resource->getStart() === 0) { + $this->data[$resource->getGroup()][$resource->getName()][] = $resource->asArray(); + } + + file_put_contents($this->path.'deployments/'.$resource->getId().'.tar.gz', $resource->getData(), FILE_APPEND); + $resource->setData(''); + break; + case Resource::TYPE_FILE: + /** @var File $resource */ + + // Handle folders + if (str_contains($resource->getFileName(), '/')) { + $folders = explode('/', $resource->getFileName()); + $folderPath = $this->path.'/files'; + + foreach ($folders as $folder) { + $folderPath .= '/'.$folder; + + if (! \file_exists($folderPath) && str_contains($folder, '.') === false) { + mkdir($folderPath, 0777, true); + } + } + } + + if ($resource->getStart() === 0 && \file_exists($this->path.'/files/'.$resource->getFileName())) { + unlink($this->path.'/files/'.$resource->getFileName()); + } + + file_put_contents($this->path.'/files/'.$resource->getFileName(), $resource->getData(), FILE_APPEND); + $resource->setData(''); + break; + default: + $this->data[$resource->getGroup()][$resource->getName()][] = $resource->asArray(); + break; + } + + $resource->setStatus(Resource::STATUS_SUCCESS); + $this->cache->update($resource); + $this->sync(); + } + + $callback($resources); + } +} diff --git a/src/Migration/Sources/Backup.php b/src/Migration/Sources/Backup.php new file mode 100644 index 0000000..e3057b2 --- /dev/null +++ b/src/Migration/Sources/Backup.php @@ -0,0 +1,80 @@ +path = $path; + $this->database = $database; + $this->storage = $storage; + } + + public static function getName(): string + { + return 'Backup'; + } + + /** + * Export Auth Group + * + * @param int $batchSize Max 100 + * @param string[] $resources Resources to export + */ + protected function exportGroupAuth(int $batchSize, array $resources) + { + + } + + /** + * Export Databases Group + * + * @param int $batchSize Max 100 + * @param string[] $resources Resources to export + */ + protected function exportGroupDatabases(int $batchSize, array $resources) + { + } + + /** + * Export Storage Group + * + * @param int $batchSize Max 5 + * @param string[] $resources Resources to export + */ + protected function exportGroupStorage(int $batchSize, array $resources) + { + } + + /** + * Export Functions Group + * + * @param int $batchSize Max 100 + * @param string[] $resources Resources to export + */ + protected function exportGroupFunctions(int $batchSize, array $resources) + { + } + + public static function getSupportedResources(): array + { + } + + + public function report(array $resources = []): array + { + } +} From 22e456217a14fc1e808e33d8b62aa96687a994cb Mon Sep 17 00:00:00 2001 From: shimon Date: Sat, 9 Mar 2024 12:24:53 +0100 Subject: [PATCH 003/185] first commit --- src/Migration/Sources/Backup.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Migration/Sources/Backup.php b/src/Migration/Sources/Backup.php index e3057b2..60510b0 100644 --- a/src/Migration/Sources/Backup.php +++ b/src/Migration/Sources/Backup.php @@ -2,7 +2,7 @@ namespace Utopia\Migration\Sources; -use Utopia\Migration\Resources\Database\Database; +use Utopia\Database\Database; use Utopia\Migration\Source; use Utopia\Storage\Device; From 7249f598795271f79caee956d916a04845f8e296 Mon Sep 17 00:00:00 2001 From: shimon Date: Sat, 9 Mar 2024 12:30:26 +0100 Subject: [PATCH 004/185] first commit --- src/Migration/Destinations/Backup.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Migration/Destinations/Backup.php b/src/Migration/Destinations/Backup.php index 732b306..06310c5 100644 --- a/src/Migration/Destinations/Backup.php +++ b/src/Migration/Destinations/Backup.php @@ -2,9 +2,10 @@ namespace Utopia\Migration\Destinations; +use Utopia\Database\Database; +//use Utopia\Migration\Resources\Database\Database as DatabaseResource; use Utopia\Migration\Destination; use Utopia\Migration\Resource; -use Utopia\Migration\Resources\Database\Database; use Utopia\Migration\Resources\Functions\Deployment; use Utopia\Migration\Resources\Storage\File; use Utopia\Migration\Transfer; From 952dfd1e6a431258be57a0ca18b5439a657b0d92 Mon Sep 17 00:00:00 2001 From: shimon Date: Sat, 9 Mar 2024 12:38:05 +0100 Subject: [PATCH 005/185] first commit --- src/Migration/Destinations/Backup.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Migration/Destinations/Backup.php b/src/Migration/Destinations/Backup.php index 06310c5..506aeab 100644 --- a/src/Migration/Destinations/Backup.php +++ b/src/Migration/Destinations/Backup.php @@ -3,11 +3,8 @@ namespace Utopia\Migration\Destinations; use Utopia\Database\Database; -//use Utopia\Migration\Resources\Database\Database as DatabaseResource; use Utopia\Migration\Destination; use Utopia\Migration\Resource; -use Utopia\Migration\Resources\Functions\Deployment; -use Utopia\Migration\Resources\Storage\File; use Utopia\Migration\Transfer; use Utopia\Storage\Device; From cfb678418818df5abbac1d13416001d8452ce047 Mon Sep 17 00:00:00 2001 From: shimon Date: Sat, 9 Mar 2024 13:00:12 +0100 Subject: [PATCH 006/185] first commit --- src/Migration/Sources/Appwrite.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 82f0333..c01dbdf 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -197,6 +197,7 @@ public function report(array $resources = []): array while (true) { $currentBuckets = $storageClient->listBuckets($lastBucket ? [Query::cursorAfter($lastBucket)] : [Query::limit(20)])['buckets']; + var_dump($currentBuckets); $buckets = array_merge($buckets, $currentBuckets); $lastBucket = $buckets[count($buckets) - 1]['$id']; From 73a4e22689a50ec276697ed6136d472dd40c50ff Mon Sep 17 00:00:00 2001 From: shimon Date: Sat, 9 Mar 2024 13:14:49 +0100 Subject: [PATCH 007/185] first commit --- src/Migration/Sources/Appwrite.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index c01dbdf..074ff57 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -197,9 +197,9 @@ public function report(array $resources = []): array while (true) { $currentBuckets = $storageClient->listBuckets($lastBucket ? [Query::cursorAfter($lastBucket)] : [Query::limit(20)])['buckets']; - var_dump($currentBuckets); + $buckets = array_merge($buckets, $currentBuckets); - $lastBucket = $buckets[count($buckets) - 1]['$id']; + $lastBucket = $buckets[count($buckets) - 1]['$id'] ?? null; if (count($currentBuckets) < 20) { break; From 01a9898b9b7fc43fe4d991634a679e4269b44431 Mon Sep 17 00:00:00 2001 From: shimon Date: Sat, 9 Mar 2024 15:41:08 +0100 Subject: [PATCH 008/185] first commit --- src/Migration/Destinations/Backup.php | 16 +++++++++++++++- src/Migration/Sources/Appwrite.php | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Migration/Destinations/Backup.php b/src/Migration/Destinations/Backup.php index 506aeab..2eb2f24 100644 --- a/src/Migration/Destinations/Backup.php +++ b/src/Migration/Destinations/Backup.php @@ -3,8 +3,12 @@ namespace Utopia\Migration\Destinations; use Utopia\Database\Database; +use Utopia\Database\DateTime; +use Utopia\Database\Document; use Utopia\Migration\Destination; use Utopia\Migration\Resource; +use Utopia\Migration\Resources\Functions\Deployment; +use Utopia\Migration\Resources\Storage\File; use Utopia\Migration\Transfer; use Utopia\Storage\Device; @@ -24,11 +28,14 @@ class Backup extends Destination protected Device $storage; - public function __construct(string $path, Database $database, Device $storage) + protected Document $backup; + + public function __construct(Document $backup, string $path, Database $database, Device $storage) { $this->path = $path; $this->database = $database; $this->storage = $storage; + $this->backup = $backup; if (! \file_exists($this->path)) { mkdir($this->path, 0777, true); @@ -92,6 +99,13 @@ private function sync(): void protected function import(array $resources, callable $callback): void { + $this->backup + ->setAttribute('startedAt', DateTime::now()) + ->setAttribute('status', 'started') + ; + + $this->database->updateDocument('backups', $this->backup->getId() ,$this->backup); + foreach ($resources as $resource) { /** @var resource $resource */ switch ($resource->getName()) { diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 074ff57..f2dc29e 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -43,7 +43,7 @@ class Appwrite extends Source /** * @var Client|null */ - protected $client = null; + protected ?Client $client = null; protected string $project = ''; From 55ba508e182a02b3a285f5eee20950d229706b4d Mon Sep 17 00:00:00 2001 From: shimon Date: Sat, 9 Mar 2024 16:04:53 +0100 Subject: [PATCH 009/185] first commit --- src/Migration/Destinations/Backup.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Migration/Destinations/Backup.php b/src/Migration/Destinations/Backup.php index 2eb2f24..71a35b7 100644 --- a/src/Migration/Destinations/Backup.php +++ b/src/Migration/Destinations/Backup.php @@ -42,6 +42,9 @@ public function __construct(Document $backup, string $path, Database $database, mkdir($this->path.'/files', 0777, true); mkdir($this->path.'/deployments', 0777, true); } + + var_dump('__construct'); + var_dump($this->backup); } public static function getName(): string @@ -86,6 +89,9 @@ public function report(array $resources = []): array return $report; } + /** + * @throws \Exception + */ private function sync(): void { $jsonEncodedData = \json_encode($this->data, JSON_PRETTY_PRINT); @@ -99,6 +105,9 @@ private function sync(): void protected function import(array $resources, callable $callback): void { + + var_dump('import'); + var_dump($this->backup); $this->backup ->setAttribute('startedAt', DateTime::now()) ->setAttribute('status', 'started') From 25eeaba9c9e2d1e809989292c493938e3d95c534 Mon Sep 17 00:00:00 2001 From: shimon Date: Sat, 9 Mar 2024 16:10:36 +0100 Subject: [PATCH 010/185] first commit --- src/Migration/Destinations/Backup.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Migration/Destinations/Backup.php b/src/Migration/Destinations/Backup.php index 71a35b7..5ea5ea3 100644 --- a/src/Migration/Destinations/Backup.php +++ b/src/Migration/Destinations/Backup.php @@ -101,6 +101,14 @@ private function sync(): void } \file_put_contents($this->path.'/backup.json', \json_encode($this->data, JSON_PRETTY_PRINT)); + + var_dump('completed'); + var_dump($this->backup); + $this->backup + ->setAttribute('finishedAt', DateTime::now()) + ->setAttribute('status', 'completed') + ; + } protected function import(array $resources, callable $callback): void From 1102e056560fed16561c09340bb1be6db678f023 Mon Sep 17 00:00:00 2001 From: shimon Date: Sat, 9 Mar 2024 16:14:55 +0100 Subject: [PATCH 011/185] first commit --- src/Migration/Destinations/Backup.php | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Migration/Destinations/Backup.php b/src/Migration/Destinations/Backup.php index 5ea5ea3..6d248e5 100644 --- a/src/Migration/Destinations/Backup.php +++ b/src/Migration/Destinations/Backup.php @@ -5,6 +5,10 @@ use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; +use Utopia\Database\Exception; +use Utopia\Database\Exception\Authorization; +use Utopia\Database\Exception\Conflict; +use Utopia\Database\Exception\Structure; use Utopia\Migration\Destination; use Utopia\Migration\Resource; use Utopia\Migration\Resources\Functions\Deployment; @@ -104,22 +108,28 @@ private function sync(): void var_dump('completed'); var_dump($this->backup); - $this->backup - ->setAttribute('finishedAt', DateTime::now()) - ->setAttribute('status', 'completed') - ; +// $this->backup +// ->setAttribute('finishedAt', DateTime::now()) +// ->setAttribute('status', 'completed') +// ; } + /** + * @throws Authorization + * @throws Structure + * @throws Conflict + * @throws Exception + */ protected function import(array $resources, callable $callback): void { var_dump('import'); var_dump($this->backup); - $this->backup - ->setAttribute('startedAt', DateTime::now()) - ->setAttribute('status', 'started') - ; +// $this->backup +// ->setAttribute('startedAt', DateTime::now()) +// ->setAttribute('status', 'started') +// ; $this->database->updateDocument('backups', $this->backup->getId() ,$this->backup); From dacd2083b048acf6376c3946a3df925071a348b9 Mon Sep 17 00:00:00 2001 From: shimon Date: Sat, 9 Mar 2024 16:20:02 +0100 Subject: [PATCH 012/185] first commit --- src/Migration/Destinations/Backup.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Migration/Destinations/Backup.php b/src/Migration/Destinations/Backup.php index 6d248e5..c3b437b 100644 --- a/src/Migration/Destinations/Backup.php +++ b/src/Migration/Destinations/Backup.php @@ -47,8 +47,6 @@ public function __construct(Document $backup, string $path, Database $database, mkdir($this->path.'/deployments', 0777, true); } - var_dump('__construct'); - var_dump($this->backup); } public static function getName(): string From c4f1bcacf11867704f076c4c025f60acc90b17dc Mon Sep 17 00:00:00 2001 From: shimon Date: Sat, 9 Mar 2024 16:49:22 +0100 Subject: [PATCH 013/185] first commit --- src/Migration/Sources/Appwrite.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index f2dc29e..c1b549b 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -760,7 +760,7 @@ private function exportCollections(int $batchSize) $collections[] = $newCollection; } - $lastCollection = $collections[count($collections) - 1]->getId(); + $lastCollection = $collections[count($collections) - 1]->getId() ?? null; $this->callback($collections); From bed9202508c1484ce9e3d487ea86c21e66852c60 Mon Sep 17 00:00:00 2001 From: shimon Date: Sat, 9 Mar 2024 16:58:54 +0100 Subject: [PATCH 014/185] first commit --- src/Migration/Sources/Appwrite.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index c1b549b..5209296 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -731,6 +731,9 @@ private function exportCollections(int $batchSize) // Transfer Collections $databases = $this->cache->get(Database::getName()); + var_dump('databases'); + var_dump($databases); + foreach ($databases as $database) { $lastCollection = null; From 58fae28faa88a6d3c0b399a5e26374424f5a698a Mon Sep 17 00:00:00 2001 From: shimon Date: Sat, 9 Mar 2024 18:43:52 +0100 Subject: [PATCH 015/185] first commit --- src/Migration/Destinations/Backup.php | 37 ++++++++++++--------------- src/Migration/Sources/Appwrite.php | 2 -- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/Migration/Destinations/Backup.php b/src/Migration/Destinations/Backup.php index c3b437b..0e008d5 100644 --- a/src/Migration/Destinations/Backup.php +++ b/src/Migration/Destinations/Backup.php @@ -26,8 +26,6 @@ class Backup extends Destination { private array $data = []; - protected string $path; - protected Database $database; protected Device $storage; @@ -40,12 +38,13 @@ public function __construct(Document $backup, string $path, Database $database, $this->database = $database; $this->storage = $storage; $this->backup = $backup; - - if (! \file_exists($this->path)) { - mkdir($this->path, 0777, true); - mkdir($this->path.'/files', 0777, true); - mkdir($this->path.'/deployments', 0777, true); - } + ; +// +// if (! \file_exists($this->path)) { +// mkdir($this->path, 0777, true); +// mkdir($this->path.'/files', 0777, true); +// mkdir($this->path.'/deployments', 0777, true); +// } } @@ -104,12 +103,12 @@ private function sync(): void \file_put_contents($this->path.'/backup.json', \json_encode($this->data, JSON_PRETTY_PRINT)); - var_dump('completed'); - var_dump($this->backup); -// $this->backup -// ->setAttribute('finishedAt', DateTime::now()) -// ->setAttribute('status', 'completed') -// ; + $this->backup + ->setAttribute('finishedAt', DateTime::now()) + ->setAttribute('status', 'completed') + ; + + $this->database->updateDocument('backups', $this->backup->getId() ,$this->backup); } @@ -122,12 +121,10 @@ private function sync(): void protected function import(array $resources, callable $callback): void { - var_dump('import'); - var_dump($this->backup); -// $this->backup -// ->setAttribute('startedAt', DateTime::now()) -// ->setAttribute('status', 'started') -// ; + $this->backup + ->setAttribute('startedAt', DateTime::now()) + ->setAttribute('status', 'started') + ; $this->database->updateDocument('backups', $this->backup->getId() ,$this->backup); diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 5209296..9c0b477 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -731,8 +731,6 @@ private function exportCollections(int $batchSize) // Transfer Collections $databases = $this->cache->get(Database::getName()); - var_dump('databases'); - var_dump($databases); foreach ($databases as $database) { $lastCollection = null; From 9c0df43a1c4e08d167657acd6e1bd73ca7867821 Mon Sep 17 00:00:00 2001 From: shimon Date: Sat, 9 Mar 2024 18:59:10 +0100 Subject: [PATCH 016/185] first commit --- src/Migration/Sources/Appwrite.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 9c0b477..4ae521d 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -761,7 +761,9 @@ private function exportCollections(int $batchSize) $collections[] = $newCollection; } - $lastCollection = $collections[count($collections) - 1]->getId() ?? null; + $lastCollection = !empty($collection) + ? $collections[count($collections) - 1]->getId() + : null; $this->callback($collections); From 24a3d6fc038e91d0a01054cf353814f6ba0c4d4e Mon Sep 17 00:00:00 2001 From: shimon Date: Sat, 9 Mar 2024 19:10:12 +0100 Subject: [PATCH 017/185] first commit --- src/Migration/Destinations/Backup.php | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/Migration/Destinations/Backup.php b/src/Migration/Destinations/Backup.php index 0e008d5..b7e73e4 100644 --- a/src/Migration/Destinations/Backup.php +++ b/src/Migration/Destinations/Backup.php @@ -32,20 +32,11 @@ class Backup extends Destination protected Document $backup; - public function __construct(Document $backup, string $path, Database $database, Device $storage) + public function __construct(Document $backup, Database $database, Device $storage) { - $this->path = $path; $this->database = $database; $this->storage = $storage; $this->backup = $backup; - ; -// -// if (! \file_exists($this->path)) { -// mkdir($this->path, 0777, true); -// mkdir($this->path.'/files', 0777, true); -// mkdir($this->path.'/deployments', 0777, true); -// } - } public static function getName(): string @@ -101,7 +92,7 @@ private function sync(): void throw new \Exception('Unable to encode data to JSON, Are you accidentally encoding binary data?'); } - \file_put_contents($this->path.'/backup.json', \json_encode($this->data, JSON_PRETTY_PRINT)); + \file_put_contents(APP_STORAGE_BACKUPS. '/backup.json', \json_encode($this->data, JSON_PRETTY_PRINT)); $this->backup ->setAttribute('finishedAt', DateTime::now()) From ae6736040adf42f2581f1e7ce79379106d3e89dd Mon Sep 17 00:00:00 2001 From: fogelito Date: Sat, 9 Mar 2024 19:11:33 +0100 Subject: [PATCH 018/185] tar --- src/Migration/Destinations/Backup.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Migration/Destinations/Backup.php b/src/Migration/Destinations/Backup.php index 0e008d5..51c336c 100644 --- a/src/Migration/Destinations/Backup.php +++ b/src/Migration/Destinations/Backup.php @@ -2,6 +2,7 @@ namespace Utopia\Migration\Destinations; +use Utopia\CLI\Console; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; @@ -110,6 +111,19 @@ private function sync(): void $this->database->updateDocument('backups', $this->backup->getId() ,$this->backup); + if (file_exists($this->path)){ + // $file = $local->getPath('shmuel.tar.gz'); + $file = realpath($this->path .'/..') . '/shmuel.tar.gz'; + $cmd = 'cd '. $this->path .' && tar -zcf ' . $file . ' * && cd ' . getcwd(); + Console::success($cmd); + + $stdout = ''; + $stderr = ''; + Console::execute($cmd, '', $stdout, $stderr); + if (!empty($stderr)) { + throw new Exception($stderr); + } + } } /** From dc7cb1284392e49ee51e1bc332b251f4eaaef078 Mon Sep 17 00:00:00 2001 From: shimon Date: Sat, 9 Mar 2024 19:14:33 +0100 Subject: [PATCH 019/185] first commit --- src/Migration/Destinations/Backup.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Migration/Destinations/Backup.php b/src/Migration/Destinations/Backup.php index b7e73e4..2f9b53d 100644 --- a/src/Migration/Destinations/Backup.php +++ b/src/Migration/Destinations/Backup.php @@ -73,9 +73,9 @@ public function report(array $resources = []): array } // Check we can write to the file - if (! \is_writable($this->path.'/backup.json')) { - $report[Transfer::GROUP_DATABASES][] = 'Unable to write to file: '.$this->path; - throw new \Exception('Unable to write to file: '.$this->path); + if (! \is_writable(APP_STORAGE_BACKUPS. '/backup.json')) { + $report[Transfer::GROUP_DATABASES][] = 'Unable to write to file: '.APP_STORAGE_BACKUPS; + throw new \Exception('Unable to write to file: '. APP_STORAGE_BACKUPS); } return $report; @@ -128,7 +128,7 @@ protected function import(array $resources, callable $callback): void $this->data[$resource->getGroup()][$resource->getName()][] = $resource->asArray(); } - file_put_contents($this->path.'deployments/'.$resource->getId().'.tar.gz', $resource->getData(), FILE_APPEND); + file_put_contents(APP_STORAGE_BACKUPS. '/deployments/'.$resource->getId().'.tar.gz', $resource->getData(), FILE_APPEND); $resource->setData(''); break; case Resource::TYPE_FILE: @@ -137,7 +137,7 @@ protected function import(array $resources, callable $callback): void // Handle folders if (str_contains($resource->getFileName(), '/')) { $folders = explode('/', $resource->getFileName()); - $folderPath = $this->path.'/files'; + $folderPath = APP_STORAGE_BACKUPS. '/files'; foreach ($folders as $folder) { $folderPath .= '/'.$folder; @@ -148,11 +148,11 @@ protected function import(array $resources, callable $callback): void } } - if ($resource->getStart() === 0 && \file_exists($this->path.'/files/'.$resource->getFileName())) { - unlink($this->path.'/files/'.$resource->getFileName()); + if ($resource->getStart() === 0 && \file_exists(APP_STORAGE_BACKUPS. '/files/'.$resource->getFileName())) { + unlink(APP_STORAGE_BACKUPS. '/files/'.$resource->getFileName()); } - file_put_contents($this->path.'/files/'.$resource->getFileName(), $resource->getData(), FILE_APPEND); + file_put_contents(APP_STORAGE_BACKUPS. '/files/'.$resource->getFileName(), $resource->getData(), FILE_APPEND); $resource->setData(''); break; default: From 53f94c47ab609e07d4e1c88830e97be659bd7767 Mon Sep 17 00:00:00 2001 From: shimon Date: Sun, 10 Mar 2024 11:02:16 +0100 Subject: [PATCH 020/185] local path --- src/Migration/Destinations/Backup.php | 43 ++++++++++----------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/src/Migration/Destinations/Backup.php b/src/Migration/Destinations/Backup.php index 60fbc53..43bef21 100644 --- a/src/Migration/Destinations/Backup.php +++ b/src/Migration/Destinations/Backup.php @@ -2,7 +2,6 @@ namespace Utopia\Migration\Destinations; -use Utopia\CLI\Console; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; @@ -27,17 +26,20 @@ class Backup extends Destination { private array $data = []; + protected string $path; + protected Database $database; protected Device $storage; protected Document $backup; - public function __construct(Document $backup, Database $database, Device $storage) + public function __construct(string $path, Document $backup, Database $database, Device $storage) { - $this->database = $database; - $this->storage = $storage; - $this->backup = $backup; + $this->path = $path; + $this->database = $database; + $this->storage = $storage; + $this->backup = $backup; } public static function getName(): string @@ -74,9 +76,9 @@ public function report(array $resources = []): array } // Check we can write to the file - if (! \is_writable(APP_STORAGE_BACKUPS. '/backup.json')) { - $report[Transfer::GROUP_DATABASES][] = 'Unable to write to file: '.APP_STORAGE_BACKUPS; - throw new \Exception('Unable to write to file: '. APP_STORAGE_BACKUPS); + if (! \is_writable($this->path. '/backup.json')) { + $report[Transfer::GROUP_DATABASES][] = 'Unable to write to file: '.$this->path; + throw new \Exception('Unable to write to file: '. $this->path); } return $report; @@ -93,7 +95,7 @@ private function sync(): void throw new \Exception('Unable to encode data to JSON, Are you accidentally encoding binary data?'); } - \file_put_contents(APP_STORAGE_BACKUPS. '/backup.json', \json_encode($this->data, JSON_PRETTY_PRINT)); + \file_put_contents($this->path. '/backup.json', \json_encode($this->data, JSON_PRETTY_PRINT)); $this->backup ->setAttribute('finishedAt', DateTime::now()) @@ -102,19 +104,6 @@ private function sync(): void $this->database->updateDocument('backups', $this->backup->getId() ,$this->backup); - if (file_exists($this->path)){ - // $file = $local->getPath('shmuel.tar.gz'); - $file = realpath($this->path .'/..') . '/shmuel.tar.gz'; - $cmd = 'cd '. $this->path .' && tar -zcf ' . $file . ' * && cd ' . getcwd(); - Console::success($cmd); - - $stdout = ''; - $stderr = ''; - Console::execute($cmd, '', $stdout, $stderr); - if (!empty($stderr)) { - throw new Exception($stderr); - } - } } /** @@ -142,7 +131,7 @@ protected function import(array $resources, callable $callback): void $this->data[$resource->getGroup()][$resource->getName()][] = $resource->asArray(); } - file_put_contents(APP_STORAGE_BACKUPS. '/deployments/'.$resource->getId().'.tar.gz', $resource->getData(), FILE_APPEND); + file_put_contents($this->path. '/deployments/'.$resource->getId().'.tar.gz', $resource->getData(), FILE_APPEND); $resource->setData(''); break; case Resource::TYPE_FILE: @@ -151,7 +140,7 @@ protected function import(array $resources, callable $callback): void // Handle folders if (str_contains($resource->getFileName(), '/')) { $folders = explode('/', $resource->getFileName()); - $folderPath = APP_STORAGE_BACKUPS. '/files'; + $folderPath = $this->path. '/files'; foreach ($folders as $folder) { $folderPath .= '/'.$folder; @@ -162,11 +151,11 @@ protected function import(array $resources, callable $callback): void } } - if ($resource->getStart() === 0 && \file_exists(APP_STORAGE_BACKUPS. '/files/'.$resource->getFileName())) { - unlink(APP_STORAGE_BACKUPS. '/files/'.$resource->getFileName()); + if ($resource->getStart() === 0 && \file_exists($this->path. '/files/'.$resource->getFileName())) { + unlink($this->path. '/files/'.$resource->getFileName()); } - file_put_contents(APP_STORAGE_BACKUPS. '/files/'.$resource->getFileName(), $resource->getData(), FILE_APPEND); + file_put_contents($this->path. '/files/'.$resource->getFileName(), $resource->getData(), FILE_APPEND); $resource->setData(''); break; default: From 5c6d5081d3c77d2f896888ba89af9d39ff251d8e Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 10 Mar 2024 12:38:08 +0100 Subject: [PATCH 021/185] upload --- composer.json | 4 +- src/Migration/Destinations/Backup.php | 58 +++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 3c41742..257da38 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,9 @@ "utopia-php/cli": "0.*", "appwrite/appwrite": "10.1.0", "utopia-php/database": "0.48.*", - "utopia-php/storage": "0.18.*" + "utopia-php/storage": "0.18.*", + "utopia-php/dsn": "0.1.*", + "utopia-php/framework": "0.33.*" }, "require-dev": { "phpunit/phpunit": "9.*", diff --git a/src/Migration/Destinations/Backup.php b/src/Migration/Destinations/Backup.php index 43bef21..fd5a69a 100644 --- a/src/Migration/Destinations/Backup.php +++ b/src/Migration/Destinations/Backup.php @@ -2,6 +2,8 @@ namespace Utopia\Migration\Destinations; +use Utopia\App; +use Utopia\CLI\Console; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; @@ -9,12 +11,15 @@ use Utopia\Database\Exception\Authorization; use Utopia\Database\Exception\Conflict; use Utopia\Database\Exception\Structure; +use Utopia\DSN\DSN; use Utopia\Migration\Destination; use Utopia\Migration\Resource; use Utopia\Migration\Resources\Functions\Deployment; use Utopia\Migration\Resources\Storage\File; use Utopia\Migration\Transfer; use Utopia\Storage\Device; +use Utopia\Storage\Device\DOSpaces; +use Utopia\Storage\Device\Local; /** * Local @@ -37,9 +42,9 @@ class Backup extends Destination public function __construct(string $path, Document $backup, Database $database, Device $storage) { $this->path = $path; - $this->database = $database; - $this->storage = $storage; - $this->backup = $backup; + $this->database = $database; + $this->storage = $storage; + $this->backup = $backup; } public static function getName(): string @@ -104,6 +109,7 @@ private function sync(): void $this->database->updateDocument('backups', $this->backup->getId() ,$this->backup); + $this->upload(); } /** @@ -170,4 +176,50 @@ protected function import(array $resources, callable $callback): void $callback($resources); } + + /** + * @throws Exception + */ + public function upload(): void + { + if (!file_exists($this->path)){ + Console::error('Nothing to upload'); + return; + } + + $time = date('Y_m_d_H_i_s'); + $filename = $time . '.tar.gz'; + + $tarFolder = realpath($this->path . '/..'); + $tarFile = $tarFolder . '/' . $filename; + + $local = new Local($this->path); + $local->setTransferChunkSize(5 * 1024 * 1024); // >= 5MB; + + $cmd = 'cd '. $this->path .' && tar -zcf ' . $tarFile . ' * && cd ' . getcwd(); + Console::success($cmd); + + $stdout = ''; + $stderr = ''; + Console::execute($cmd, '', $stdout, $stderr); + if (!empty($stderr)) { + throw new Exception($stderr); + } + + $destination = $this->storage->getRoot() . '/' . $filename; + + if (!$local->transfer($tarFile, $destination, $this->storage)) { + throw new Exception('Error uploading to ' . $destination); + } + + if (!$this->storage->exists($destination)) { + throw new Exception('File not found on cloud: ' . $destination); + } + +// if (!unlink($tarFile)) { +// Console::error('Error deleting: ' . $tarFile); +// } + + } + } From 7a2cec71c5d9c51cd863d0e0aac0e94b8ea95b34 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 10 Mar 2024 12:40:08 +0100 Subject: [PATCH 022/185] dsn --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 257da38..edb809b 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "appwrite/appwrite": "10.1.0", "utopia-php/database": "0.48.*", "utopia-php/storage": "0.18.*", - "utopia-php/dsn": "0.1.*", + "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*" }, "require-dev": { From badd6d47f32d601d3d0199f3dfa1639f0f2b9a17 Mon Sep 17 00:00:00 2001 From: shimon Date: Sun, 10 Mar 2024 15:08:59 +0100 Subject: [PATCH 023/185] update --- src/Migration/Destinations/Backup.php | 66 +++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/src/Migration/Destinations/Backup.php b/src/Migration/Destinations/Backup.php index 43bef21..6cf921b 100644 --- a/src/Migration/Destinations/Backup.php +++ b/src/Migration/Destinations/Backup.php @@ -2,6 +2,8 @@ namespace Utopia\Migration\Destinations; +use Utopia\App; +use Utopia\CLI\Console; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; @@ -9,12 +11,15 @@ use Utopia\Database\Exception\Authorization; use Utopia\Database\Exception\Conflict; use Utopia\Database\Exception\Structure; +use Utopia\DSN\DSN; use Utopia\Migration\Destination; use Utopia\Migration\Resource; use Utopia\Migration\Resources\Functions\Deployment; use Utopia\Migration\Resources\Storage\File; use Utopia\Migration\Transfer; use Utopia\Storage\Device; +use Utopia\Storage\Device\DOSpaces; +use Utopia\Storage\Device\Local; /** * Local @@ -37,9 +42,17 @@ class Backup extends Destination public function __construct(string $path, Document $backup, Database $database, Device $storage) { $this->path = $path; - $this->database = $database; - $this->storage = $storage; - $this->backup = $backup; + $this->database = $database; + $this->storage = $storage; + $this->backup = $backup; + $this->path .= '/'. $backup->getId(); + + if (! \file_exists($this->path)) { + mkdir($this->path, 0777, true); + mkdir($this->path.'/files', 0777, true); + mkdir($this->path.'/deployments', 0777, true); + } + } public static function getName(): string @@ -104,6 +117,7 @@ private function sync(): void $this->database->updateDocument('backups', $this->backup->getId() ,$this->backup); + $this->upload(); } /** @@ -170,4 +184,50 @@ protected function import(array $resources, callable $callback): void $callback($resources); } + + /** + * @throws Exception + */ + public function upload(): void + { + if (!file_exists($this->path)){ + Console::error('Nothing to upload'); + return; + } + + $time = date('Y_m_d_H_i_s'); + $filename = $time . '.tar.gz'; + + $tarFolder = realpath($this->path . '/..'); + $tarFile = $tarFolder . '/' . $filename; + + $local = new Local($this->path); + $local->setTransferChunkSize(5 * 1024 * 1024); // >= 5MB; + + $cmd = 'cd '. $this->path .' && tar -zcf ' . $tarFile . ' * && cd ' . getcwd(); + Console::success($cmd); + + $stdout = ''; + $stderr = ''; + Console::execute($cmd, '', $stdout, $stderr); + if (!empty($stderr)) { + throw new Exception($stderr); + } + + $destination = $this->storage->getRoot() . '/' . $filename; + + if (!$local->transfer($tarFile, $destination, $this->storage)) { + throw new Exception('Error uploading to ' . $destination); + } + + if (!$this->storage->exists($destination)) { + throw new Exception('File not found on cloud: ' . $destination); + } + +// if (!unlink($tarFile)) { +// Console::error('Error deleting: ' . $tarFile); +// } + + } + } From f87e483d25397f8daebe34dec5f614bbe809202c Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 10 Mar 2024 17:26:01 +0100 Subject: [PATCH 024/185] filesize --- src/Migration/Destinations/Backup.php | 75 +++++++++++++++++++-------- 1 file changed, 54 insertions(+), 21 deletions(-) diff --git a/src/Migration/Destinations/Backup.php b/src/Migration/Destinations/Backup.php index fd5a69a..a764572 100644 --- a/src/Migration/Destinations/Backup.php +++ b/src/Migration/Destinations/Backup.php @@ -94,22 +94,44 @@ public function report(array $resources = []): array */ private function sync(): void { - $jsonEncodedData = \json_encode($this->data, JSON_PRETTY_PRINT); - - if ($jsonEncodedData === false) { - throw new \Exception('Unable to encode data to JSON, Are you accidentally encoding binary data?'); + $files = []; + + foreach ($this->data as $group => $v1){ + foreach ($v1 as $k2 => $v2){ + $name = $group . '::' . $k2; + $files[][] = $name; + + $data = \json_encode($v2); + if ($data === false) { + throw new \Exception('Unable to encode data to JSON, Are you accidentally encoding binary data?'); + } + + \file_put_contents( + $this->path . '/'. $name .'.json', + \json_encode($data) + ); + } } - \file_put_contents($this->path. '/backup.json', \json_encode($this->data, JSON_PRETTY_PRINT)); + \file_put_contents( + $this->path . '/index.json', + \json_encode($files, JSON_PRETTY_PRINT) + ); $this->backup + ->setAttribute('status', 'uploading') ->setAttribute('finishedAt', DateTime::now()) - ->setAttribute('status', 'completed') ; $this->database->updateDocument('backups', $this->backup->getId() ,$this->backup); - $this->upload(); + $filesize = $this->upload(); + + $this->backup + ->setAttribute('status', 'completed') + ->setAttribute('finishedAt', DateTime::now()) + ->setAttribute('size', $filesize) + ; } /** @@ -178,48 +200,59 @@ protected function import(array $resources, callable $callback): void } /** - * @throws Exception + * @throws \Exception */ - public function upload(): void + public function upload(): int { if (!file_exists($this->path)){ - Console::error('Nothing to upload'); - return; + throw new \Exception('Nothing to upload'); } - $time = date('Y_m_d_H_i_s'); - $filename = $time . '.tar.gz'; + $id = $this->backup->getInternalId(); + $filename = $id . '.tar.gz'; $tarFolder = realpath($this->path . '/..'); $tarFile = $tarFolder . '/' . $filename; $local = new Local($this->path); + + Console::error($local->getRoot()); + $local->setTransferChunkSize(5 * 1024 * 1024); // >= 5MB; $cmd = 'cd '. $this->path .' && tar -zcf ' . $tarFile . ' * && cd ' . getcwd(); - Console::success($cmd); + Console::error($cmd); $stdout = ''; $stderr = ''; Console::execute($cmd, '', $stdout, $stderr); if (!empty($stderr)) { - throw new Exception($stderr); + throw new \Exception($stderr); } $destination = $this->storage->getRoot() . '/' . $filename; + var_dump($this->storage); + Console::error($destination); - if (!$local->transfer($tarFile, $destination, $this->storage)) { - throw new Exception('Error uploading to ' . $destination); + $result = $local->transfer($tarFile, $destination, $this->storage); + Console::error($result); + + if ($result === false) { + throw new \Exception('Error uploading to ' . $destination); } if (!$this->storage->exists($destination)) { - throw new Exception('File not found on cloud: ' . $destination); + throw new \Exception('File not found on cloud: ' . $destination); } -// if (!unlink($tarFile)) { -// Console::error('Error deleting: ' . $tarFile); -// } + $filesize = filesize($tarFile); + + if (!unlink($tarFile)) { + Console::error('Error deleting: ' . $tarFile); + throw new \Exception('Error deleting: ' . $tarFile); + } + return $filesize; } } From 9861173ecda77b025a2cf08bf8e47a972f5b7b10 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 10 Mar 2024 17:56:10 +0100 Subject: [PATCH 025/185] cli version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index edb809b..09b4d50 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ }, "require": { "php": "8.*", - "utopia-php/cli": "0.*", + "utopia-php/cli": "0.15.*", "appwrite/appwrite": "10.1.0", "utopia-php/database": "0.48.*", "utopia-php/storage": "0.18.*", From dc3fe12bd4e3b642433274d1b977528045c977ee Mon Sep 17 00:00:00 2001 From: shimon Date: Mon, 11 Mar 2024 10:26:57 +0100 Subject: [PATCH 026/185] update --- src/Migration/Destinations/Backup.php | 266 ----- src/Migration/Sources/AppwriteInternal.php | 1218 -------------------- src/Migration/Sources/Backup.php | 80 -- 3 files changed, 1564 deletions(-) delete mode 100644 src/Migration/Destinations/Backup.php delete mode 100644 src/Migration/Sources/AppwriteInternal.php delete mode 100644 src/Migration/Sources/Backup.php diff --git a/src/Migration/Destinations/Backup.php b/src/Migration/Destinations/Backup.php deleted file mode 100644 index a15f4f1..0000000 --- a/src/Migration/Destinations/Backup.php +++ /dev/null @@ -1,266 +0,0 @@ -path = $path; - $this->database = $database; - $this->storage = $storage; - $this->backup = $backup; - $this->path .= '/'. $backup->getId(); - - if (! \file_exists($this->path)) { - mkdir($this->path, 0777, true); - mkdir($this->path.'/files', 0777, true); - mkdir($this->path.'/deployments', 0777, true); - } - - } - - public static function getName(): string - { - return 'Backup'; - } - - public static function getSupportedResources(): array - { - return [ - Resource::TYPE_ATTRIBUTE, - Resource::TYPE_BUCKET, - Resource::TYPE_COLLECTION, - Resource::TYPE_DATABASE, - Resource::TYPE_DEPLOYMENT, - Resource::TYPE_DOCUMENT, - Resource::TYPE_ENVIRONMENT_VARIABLE, - Resource::TYPE_FILE, - Resource::TYPE_FUNCTION, - Resource::TYPE_HASH, - Resource::TYPE_INDEX, - Resource::TYPE_TEAM, - Resource::TYPE_MEMBERSHIP, - Resource::TYPE_USER, - ]; - } - - public function report(array $resources = []): array - { - $report = []; - - if (empty($resources)) { - $resources = $this->getSupportedResources(); - } - - // Check we can write to the file - if (! \is_writable($this->path. '/backup.json')) { - $report[Transfer::GROUP_DATABASES][] = 'Unable to write to file: '.$this->path; - throw new \Exception('Unable to write to file: '. $this->path); - } - - return $report; - } - - /** - * @throws \Exception - */ - private function sync(): void - { - $files = []; - - foreach ($this->data as $group => $v1){ - foreach ($v1 as $k2 => $v2){ - $name = $group . '::' . $k2; - $files[][] = $name; - - $data = \json_encode($v2); - if ($data === false) { - throw new \Exception('Unable to encode data to JSON, Are you accidentally encoding binary data?'); - } - - \file_put_contents( - $this->path . '/'. $name .'.json', - \json_encode($data) - ); - } - } - - \file_put_contents( - $this->path . '/index.json', - \json_encode($files, JSON_PRETTY_PRINT) - ); - - $this->backup - ->setAttribute('status', 'uploading') - ->setAttribute('finishedAt', DateTime::now()) - ; - - $this->database->updateDocument('backups', $this->backup->getId() ,$this->backup); - - $filesize = $this->upload(); - - $this->backup - ->setAttribute('status', 'completed') - ->setAttribute('finishedAt', DateTime::now()) - ->setAttribute('size', $filesize) - ; - } - - /** - * @throws Authorization - * @throws Structure - * @throws Conflict - * @throws Exception - */ - protected function import(array $resources, callable $callback): void - { - - $this->backup - ->setAttribute('startedAt', DateTime::now()) - ->setAttribute('status', 'started') - ; - - $this->database->updateDocument('backups', $this->backup->getId() ,$this->backup); - - foreach ($resources as $resource) { - /** @var resource $resource */ - switch ($resource->getName()) { - case Resource::TYPE_DEPLOYMENT: - /** @var Deployment $resource */ - if ($resource->getStart() === 0) { - $this->data[$resource->getGroup()][$resource->getName()][] = $resource->asArray(); - } - - file_put_contents($this->path. '/deployments/'.$resource->getId().'.tar.gz', $resource->getData(), FILE_APPEND); - $resource->setData(''); - break; - case Resource::TYPE_FILE: - /** @var File $resource */ - - // Handle folders - if (str_contains($resource->getFileName(), '/')) { - $folders = explode('/', $resource->getFileName()); - $folderPath = $this->path. '/files'; - - foreach ($folders as $folder) { - $folderPath .= '/'.$folder; - - if (! \file_exists($folderPath) && str_contains($folder, '.') === false) { - mkdir($folderPath, 0777, true); - } - } - } - - if ($resource->getStart() === 0 && \file_exists($this->path. '/files/'.$resource->getFileName())) { - unlink($this->path. '/files/'.$resource->getFileName()); - } - - file_put_contents($this->path. '/files/'.$resource->getFileName(), $resource->getData(), FILE_APPEND); - $resource->setData(''); - break; - default: - $this->data[$resource->getGroup()][$resource->getName()][] = $resource->asArray(); - break; - } - - $resource->setStatus(Resource::STATUS_SUCCESS); - $this->cache->update($resource); - $this->sync(); - } - - $callback($resources); - } - - /** - * @throws \Exception - */ - public function upload(): int - { - if (!file_exists($this->path)){ - throw new \Exception('Nothing to upload'); - } - - $id = $this->backup->getInternalId(); - $filename = $id . '.tar.gz'; - - $tarFolder = realpath($this->path . '/..'); - $tarFile = $tarFolder . '/' . $filename; - - $local = new Local($this->path); - - Console::error($local->getRoot()); - - $local->setTransferChunkSize(5 * 1024 * 1024); // >= 5MB; - - $cmd = 'cd '. $this->path .' && tar -zcf ' . $tarFile . ' * && cd ' . getcwd(); - Console::error($cmd); - - $stdout = ''; - $stderr = ''; - Console::execute($cmd, '', $stdout, $stderr); - if (!empty($stderr)) { - throw new \Exception($stderr); - } - - $destination = $this->storage->getRoot() . '/' . $filename; - var_dump($this->storage); - Console::error($destination); - - $result = $local->transfer($tarFile, $destination, $this->storage); - Console::error($result); - - if ($result === false) { - throw new \Exception('Error uploading to ' . $destination); - } - - if (!$this->storage->exists($destination)) { - throw new \Exception('File not found on cloud: ' . $destination); - } - - $filesize = filesize($tarFile); - - if (!unlink($tarFile)) { - Console::error('Error deleting: ' . $tarFile); - throw new \Exception('Error deleting: ' . $tarFile); - } - - return $filesize; - } - -} diff --git a/src/Migration/Sources/AppwriteInternal.php b/src/Migration/Sources/AppwriteInternal.php deleted file mode 100644 index 13dd443..0000000 --- a/src/Migration/Sources/AppwriteInternal.php +++ /dev/null @@ -1,1218 +0,0 @@ -project = $project; - } - - public static function getName(): string - { - return 'Appwrite'; - } - - public static function getSupportedResources(): array - { - return [ - // Auth - Resource::TYPE_USER, - Resource::TYPE_TEAM, - Resource::TYPE_MEMBERSHIP, - - // Database - Resource::TYPE_DATABASE, - Resource::TYPE_COLLECTION, - Resource::TYPE_ATTRIBUTE, - Resource::TYPE_INDEX, - Resource::TYPE_DOCUMENT, - - // Storage - Resource::TYPE_BUCKET, - Resource::TYPE_FILE, - - // Functions - Resource::TYPE_FUNCTION, - Resource::TYPE_DEPLOYMENT, - Resource::TYPE_ENVIRONMENT_VARIABLE, - - // Settings - ]; - } - - public function report(array $resources = []): array - { - $report = []; - $currentPermission = ''; - - if (empty($resources)) { - $resources = $this->getSupportedResources(); - } - - $usersClient = new Users($this->client); - $teamsClient = new Teams($this->client); - $databaseClient = new Databases($this->client); - $storageClient = new Storage($this->client); - $functionsClient = new Functions($this->client); - - // Auth - try { - $currentPermission = 'users.read'; - if (in_array(Resource::TYPE_USER, $resources)) { - $report[Resource::TYPE_USER] = $usersClient->list()['total']; - } - - $currentPermission = 'teams.read'; - if (in_array(Resource::TYPE_TEAM, $resources)) { - $report[Resource::TYPE_TEAM] = $teamsClient->list()['total']; - } - - if (in_array(Resource::TYPE_MEMBERSHIP, $resources)) { - $report[Resource::TYPE_MEMBERSHIP] = 0; - $teams = $teamsClient->list()['teams']; - foreach ($teams as $team) { - $report[Resource::TYPE_MEMBERSHIP] += $teamsClient->listMemberships($team['$id'], [Query::limit(1)])['total']; - } - } - - // Databases - $currentPermission = 'databases.read'; - if (in_array(Resource::TYPE_DATABASE, $resources)) { - $report[Resource::TYPE_DATABASE] = $databaseClient->list()['total']; - } - - $currentPermission = 'collections.read'; - if (in_array(Resource::TYPE_COLLECTION, $resources)) { - $report[Resource::TYPE_COLLECTION] = 0; - $databases = $databaseClient->list()['databases']; - foreach ($databases as $database) { - $report[Resource::TYPE_COLLECTION] += $databaseClient->listCollections($database['$id'], [Query::limit(1)])['total']; - } - } - - $currentPermission = 'documents.read'; - if (in_array(Resource::TYPE_DOCUMENT, $resources)) { - $report[Resource::TYPE_DOCUMENT] = 0; - $databases = $databaseClient->list()['databases']; - foreach ($databases as $database) { - $collections = $databaseClient->listCollections($database['$id'])['collections']; - foreach ($collections as $collection) { - $report[Resource::TYPE_DOCUMENT] += $databaseClient->listDocuments($database['$id'], $collection['$id'], [Query::limit(1)])['total']; - } - } - } - - $currentPermission = 'attributes.read'; - if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) { - $report[Resource::TYPE_ATTRIBUTE] = 0; - $databases = $databaseClient->list()['databases']; - foreach ($databases as $database) { - $collections = $databaseClient->listCollections($database['$id'])['collections']; - foreach ($collections as $collection) { - $report[Resource::TYPE_ATTRIBUTE] += $databaseClient->listAttributes($database['$id'], $collection['$id'])['total']; - } - } - } - - $currentPermission = 'indexes.read'; - if (in_array(Resource::TYPE_INDEX, $resources)) { - $report[Resource::TYPE_INDEX] = 0; - $databases = $databaseClient->list()['databases']; - foreach ($databases as $database) { - $collections = $databaseClient->listCollections($database['$id'])['collections']; - foreach ($collections as $collection) { - $report[Resource::TYPE_INDEX] += $databaseClient->listIndexes($database['$id'], $collection['$id'])['total']; - } - } - } - - // Storage - $currentPermission = 'buckets.read'; - if (in_array(Resource::TYPE_BUCKET, $resources)) { - $report[Resource::TYPE_BUCKET] = $storageClient->listBuckets()['total']; - } - - $currentPermission = 'files.read'; - if (in_array(Resource::TYPE_FILE, $resources)) { - $report[Resource::TYPE_FILE] = 0; - $report['size'] = 0; - $buckets = []; - $lastBucket = null; - - while (true) { - $currentBuckets = $storageClient->listBuckets($lastBucket ? [Query::cursorAfter($lastBucket)] : [Query::limit(20)])['buckets']; - $buckets = array_merge($buckets, $currentBuckets); - $lastBucket = $buckets[count($buckets) - 1]['$id']; - - if (count($currentBuckets) < 20) { - break; - } - } - - foreach ($buckets as $bucket) { - $files = []; - $lastFile = null; - - while (true) { - $currentFiles = $storageClient->listFiles($bucket['$id'], $lastFile ? [Query::cursorAfter($lastFile)] : [Query::limit(20)])['files']; - $files = array_merge($files, $currentFiles); - $lastFile = $files[count($files) - 1]['$id']; - - if (count($currentFiles) < 20) { - break; - } - } - - $report[Resource::TYPE_FILE] += count($files); - foreach ($files as $file) { - $report['size'] += $storageClient->getFile($bucket['$id'], $file['$id'])['sizeOriginal']; - } - } - $report['size'] = $report['size'] / 1000 / 1000; // MB - } - - // Functions - $currentPermission = 'functions.read'; - if (in_array(Resource::TYPE_FUNCTION, $resources)) { - $report[Resource::TYPE_FUNCTION] = $functionsClient->list()['total']; - } - - if (in_array(Resource::TYPE_DEPLOYMENT, $resources)) { - $report[Resource::TYPE_DEPLOYMENT] = 0; - $functions = $functionsClient->list()['functions']; - foreach ($functions as $function) { - $report[Resource::TYPE_DEPLOYMENT] += $functionsClient->listDeployments($function['$id'], [Query::limit(1)])['total']; - } - } - - if (in_array(Resource::TYPE_ENVIRONMENT_VARIABLE, $resources)) { - $report[Resource::TYPE_ENVIRONMENT_VARIABLE] = 0; - $functions = $functionsClient->list()['functions']; - foreach ($functions as $function) { - $report[Resource::TYPE_ENVIRONMENT_VARIABLE] += $functionsClient->listVariables($function['$id'])['total']; - } - } - - $report['version'] = $this->call('GET', '/health/version', ['X-Appwrite-Key' => '', 'X-Appwrite-Project' => ''])['version']; - - $this->previousReport = $report; - - return $report; - } catch (\Throwable $e) { - if ($e->getCode() === 403) { - throw new \Exception("Missing Permission: {$currentPermission}."); - } else { - throw new \Exception($e->getMessage()); - } - } - } - - /** - * Export Auth Resources - * - * @param int $batchSize Max 100 - * @param string[] $resources - * @return void - */ - protected function exportGroupAuth(int $batchSize, array $resources) - { - - if (in_array(Resource::TYPE_USER, $resources)) { - - $this->exportUsers($batchSize); - - } - - if (in_array(Resource::TYPE_TEAM, $resources)) { - - $this->exportTeams($batchSize); - } - - if (in_array(Resource::TYPE_MEMBERSHIP, $resources)) { - $this->exportMemberships($batchSize); - } - } - - private function exportUsers(int $batchSize) - { - $usersClient = new Users($this->client); - $lastDocument = null; - - // Export Users - while (true) { - $users = []; - - $queries = [Query::limit($batchSize)]; - - if ($lastDocument) { - $queries[] = Query::cursorAfter($lastDocument); - } - - $response = $usersClient->list($queries); - - if ($response['total'] == 0) { - break; - } - - foreach ($response['users'] as $user) { - $users[] = new User( - $user['$id'], - $user['email'], - $user['name'], - $user['password'] ? new Hash($user['password'], algorithm: $user['hash']) : null, - $user['phone'], - $this->calculateTypes($user), - $user['labels'] ?? [], - '', - $user['emailVerification'] ?? false, - $user['phoneVerification'] ?? false, - ! $user['status'], - $user['prefs'] ?? [], - ); - - $lastDocument = $user['$id']; - } - - $this->callback($users); - - if (count($users) < $batchSize) { - break; - } - } - } - - private function exportTeams(int $batchSize) - { - $teamsClient = new Teams($this->client); - $lastDocument = null; - - // Export Teams - while (true) { - $teams = []; - - $queries = [Query::limit($batchSize)]; - - if ($lastDocument) { - $queries[] = Query::cursorAfter($lastDocument); - } - - $response = $teamsClient->list($queries); - - if ($response['total'] == 0) { - break; - } - - foreach ($response['teams'] as $team) { - $teams[] = new Team( - $team['$id'], - $team['name'], - $team['prefs'], - ); - - $lastDocument = $team['$id']; - } - - $this->callback($teams); - - if (count($teams) < $batchSize) { - break; - } - } - } - - private function exportMemberships(int $batchSize) - { - $teamsClient = new Teams($this->client); - - // Export Memberships - $cacheTeams = $this->cache->get(Team::getName()); - /** @var array - array where key is user ID */ - $cacheUsers = []; - foreach ($this->cache->get(User::getName()) as $cacheUser) { - /** @var User $cacheUser */ - $cacheUsers[$cacheUser->getId()] = $cacheUser; - } - - foreach ($cacheTeams as $team) { - /** @var Team $team */ - $lastDocument = null; - - while (true) { - $memberships = []; - - $queries = [Query::limit($batchSize)]; - - if ($lastDocument) { - $queries[] = Query::cursorAfter($lastDocument); - } - - $response = $teamsClient->listMemberships($team->getId(), $queries); - - if ($response['total'] == 0) { - break; - } - - foreach ($response['memberships'] as $membership) { - $user = $cacheUsers[$membership['userId']] ?? null; - if ($user === null) { - throw new \Exception('User not found'); - } - - $memberships[] = new Membership( - $team, - $user, - $membership['roles'], - $membership['confirm'] - ); - - $lastDocument = $membership['$id']; - } - - $this->callback($memberships); - - if (count($memberships) < $batchSize) { - break; - } - } - } - } - - protected function exportGroupDatabases(int $batchSize, array $resources) - { - if (in_array(Resource::TYPE_DATABASE, $resources)) { - $this->exportDatabases($batchSize); - } - - if (in_array(Resource::TYPE_COLLECTION, $resources)) { - $this->exportCollections($batchSize); - } - - if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) { - $this->exportAttributes($batchSize); - } - - if (in_array(Resource::TYPE_INDEX, $resources)) { - $this->exportIndexes($batchSize); - } - - if (in_array(Resource::TYPE_DOCUMENT, $resources)) { - $this->exportDocuments($batchSize); - } - } - - public function stripMetadata(array $document, bool $root = true) - { - if ($root) { - unset($document['$id']); - } - - unset($document['$permissions']); - unset($document['$collectionId']); - unset($document['$updatedAt']); - unset($document['$createdAt']); - unset($document['$databaseId']); - - foreach ($document as $key => $value) { - if (is_array($value)) { - $document[$key] = $this->stripMetadata($value, false); - } - } - - return $document; - } - - private function exportDocuments(int $batchSize) - { - $databaseClient = new Databases($this->client); - $collections = $this->cache->get(Collection::getName()); - - foreach ($collections as $collection) { - /** @var Collection $collection */ - $lastDocument = null; - - while (true) { - $queries = [Query::limit($batchSize)]; - - $documents = []; - - if ($lastDocument) { - $queries[] = Query::cursorAfter($lastDocument); - } - - $response = $databaseClient->listDocuments( - $collection->getDatabase()->getId(), - $collection->getId(), - $queries - ); - - foreach ($response['documents'] as $document) { - $id = $document['$id']; - $permissions = $document['$permissions']; - - $document = $this->stripMetadata($document); - - // Certain Appwrite versions allowed for data to be required but null - // This isn't allowed in modern versions so we need to remove it by comparing their attributes and replacing it with default value. - $attributes = $this->cache->get(Attribute::getName()); - foreach ($attributes as $attribute) { - /** @var Attribute $attribute */ - if ($attribute->getCollection()->getId() !== $collection->getId()) { - continue; - } - - if ($attribute->getRequired() && ! isset($document[$attribute->getKey()])) { - switch ($attribute->getTypeName()) { - case Attribute::TYPE_BOOLEAN: - $document[$attribute->getKey()] = false; - break; - case Attribute::TYPE_STRING: - $document[$attribute->getKey()] = ''; - break; - case Attribute::TYPE_INTEGER: - $document[$attribute->getKey()] = 0; - break; - case Attribute::TYPE_FLOAT: - $document[$attribute->getKey()] = 0.0; - break; - case Attribute::TYPE_DATETIME: - $document[$attribute->getKey()] = 0; - break; - case Attribute::TYPE_URL: - $document[$attribute->getKey()] = 'http://null'; - break; - } - } - } - - $cleanData = $this->stripMetadata($document); - - $documents[] = new Document( - $id, - $collection->getDatabase(), - $collection, - $cleanData, - $permissions - ); - $lastDocument = $id; - } - - $this->callback($documents); - - if (count($response['documents']) < $batchSize) { - break; - } - } - } - } - - private function convertAttribute(array $value, Collection $collection): Attribute - { - switch ($value['type']) { - case 'string': - if (! isset($value['format'])) { - return new Text( - $value['key'], - $collection, - $value['required'], - $value['array'], - $value['default'], - $value['size'] ?? 0 - ); - } - - switch ($value['format']) { - case 'email': - return new Email( - $value['key'], - $collection, - $value['required'], - $value['array'], - $value['default'] - ); - case 'enum': - return new Enum( - $value['key'], - $collection, - $value['elements'], - $value['required'], - $value['array'], - $value['default'] - ); - case 'url': - return new URL( - $value['key'], - $collection, - $value['required'], - $value['array'], - $value['default'] - ); - case 'ip': - return new IP( - $value['key'], - $collection, - $value['required'], - $value['array'], - $value['default'] - ); - case 'datetime': - return new DateTime( - $value['key'], - $collection, - $value['required'], - $value['array'], - $value['default'] - ); - default: - return new Text( - $value['key'], - $collection, - $value['required'], - $value['array'], - $value['default'], - $value['size'] ?? 0 - ); - } - case 'boolean': - return new Boolean( - $value['key'], - $collection, - $value['required'], - $value['array'], - $value['default'] - ); - case 'integer': - return new Integer( - $value['key'], - $collection, - $value['required'], - $value['array'], - $value['default'], - $value['min'] ?? 0, - $value['max'] ?? 0 - ); - case 'double': - return new Decimal( - $value['key'], - $collection, - $value['required'], - $value['array'], - $value['default'], - $value['min'] ?? 0, - $value['max'] ?? 0 - ); - case 'relationship': - return new Relationship( - $value['key'], - $collection, - $value['required'], - $value['array'], - $value['relatedCollection'], - $value['relationType'], - $value['twoWay'], - $value['twoWayKey'], - $value['onDelete'], - $value['side'] - ); - case 'datetime': - return new DateTime( - $value['key'], - $collection, - $value['required'], - $value['array'], - $value['default'] - ); - } - - throw new \Exception('Unknown attribute type: '.$value['type']); - } - - private function exportDatabases(int $batchSize) - { - - $databaseClient = new Databases($this->client); - - $lastDatabase = null; - - // Transfer Databases - while (true) { - $queries = [Query::limit($batchSize)]; - $databases = []; - - if ($lastDatabase) { - $queries[] = Query::cursorAfter($lastDatabase); - } - - $response = $databaseClient->list($queries); - - foreach ($response['databases'] as $database) { - $newDatabase = new Database( - $database['name'], - $database['$id'] - ); - - $databases[] = $newDatabase; - } - - if (empty($databases)) { - break; - } - - $lastDatabase = $databases[count($databases) - 1]->getId(); - - $this->callback($databases); - - if (count($databases) < $batchSize) { - break; - } - } - } - - private function exportCollections(int $batchSize) - { - $databaseClient = new Databases($this->client); - - // Transfer Collections - - $databases = $this->cache->get(Database::getName()); - foreach ($databases as $database) { - $lastCollection = null; - - /** @var Database $database */ - while (true) { - $queries = [Query::limit($batchSize)]; - $collections = []; - - if ($lastCollection) { - $queries[] = Query::cursorAfter($lastCollection); - } - - $response = $databaseClient->listCollections( - $database->getId(), - $queries - ); - - foreach ($response['collections'] as $collection) { - $newCollection = new Collection( - $database, - $collection['name'], - $collection['$id'], - $collection['documentSecurity'], - $collection['$permissions'] - ); - - $collections[] = $newCollection; - } - - $lastCollection = $collections[count($collections) - 1]->getId(); - - $this->callback($collections); - - if (count($collections) < $batchSize) { - break; - } - } - } - } - - private function exportAttributes(int $batchSize) - { - $databaseClient = new Databases($this->client); - - // Transfer Attributes - $collections = $this->cache->get(Collection::getName()); - /** @var Collection[] $collections */ - foreach ($collections as $collection) { - $lastAttribute = null; - - while (true) { - $queries = [Query::limit($batchSize)]; - $attributes = []; - - if ($lastAttribute) { - $queries[] = Query::cursorAfter($lastAttribute); - } - - $response = $databaseClient->listAttributes( - $collection->getDatabase()->getId(), - $collection->getId(), - $queries - ); - - // Remove two way relationship attributes - $this->cache->get(Resource::TYPE_ATTRIBUTE); - - $knownTwoways = []; - - foreach ($this->cache->get(Resource::TYPE_ATTRIBUTE) as $attribute) { - /** @var Attribute|Relationship $attribute */ - if ($attribute->getTypeName() == Attribute::TYPE_RELATIONSHIP && $attribute->getTwoWay()) { - $knownTwoways[] = $attribute->getTwoWayKey(); - } - } - - foreach ($response['attributes'] as $attribute) { - if (in_array($attribute['key'], $knownTwoways)) { - continue; - } - - if ($attribute['type'] === 'relationship') { - $knownTwoways[] = $attribute['twoWayKey']; - } - - $attributes[] = $this->convertAttribute($attribute, $collection); - } - - if (empty($attributes)) { - break; - } - - $this->callback($attributes); - - $lastAttribute = $attributes[count($attributes) - 1]->getId(); - if (count($attributes) < $batchSize) { - break; - } - } - } - } - - private function exportIndexes(int $batchSize) - { - $databaseClient = new Databases($this->client); - - $collections = $this->cache->get(Resource::TYPE_COLLECTION); - - // Transfer Indexes - foreach ($collections as $collection) { - /** @var Collection $collection */ - $lastIndex = null; - - while (true) { - $queries = [Query::limit($batchSize)]; - $indexes = []; - - if ($lastIndex) { - $queries[] = Query::cursorAfter($lastIndex); - } - - $response = $databaseClient->listIndexes( - $collection->getDatabase()->getId(), - $collection->getId(), - $queries - ); - - foreach ($response['indexes'] as $index) { - $indexes[] = new Index( - 'unique()', - $index['key'], - $collection, - $index['type'], - $index['attributes'], - $index['orders'] - ); - } - - if (empty($indexes)) { - break; - } - - $this->callback($indexes); - $lastIndex = $indexes[count($indexes) - 1]->getId(); - - if (count($indexes) < $batchSize) { - break; - } - } - } - } - - private function calculateTypes(array $user): array - { - if (empty($user['email']) && empty($user['phone'])) { - return [User::TYPE_ANONYMOUS]; - } - - $types = []; - - if (! empty($user['email']) && ! empty($user['password'])) { - $types[] = User::TYPE_PASSWORD; - } - - if (! empty($user['phone'])) { - $types[] = User::TYPE_PHONE; - } - - return $types; - } - - protected function exportGroupStorage(int $batchSize, array $resources) - { - if (in_array(Resource::TYPE_BUCKET, $resources)) { - $this->exportBuckets($batchSize, false); - } - - if (in_array(Resource::TYPE_FILE, $resources)) { - $this->exportFiles($batchSize); - } - - if (in_array(Resource::TYPE_BUCKET, $resources)) { - $this->exportBuckets($batchSize, true); - } - } - - private function exportBuckets(int $batchSize, bool $updateLimits) - { - $storageClient = new Storage($this->client); - - $buckets = $storageClient->listBuckets(); - - $convertedBuckets = []; - - foreach ($buckets['buckets'] as $bucket) { - $bucket = new Bucket( - $bucket['$id'], - $bucket['name'], - $bucket['$permissions'], - $bucket['fileSecurity'], - $bucket['enabled'], - $bucket['maximumFileSize'], - $bucket['allowedFileExtensions'], - $bucket['compression'], - $bucket['encryption'], - $bucket['antivirus'], - $updateLimits - ); - - $bucket->setUpdateLimits($updateLimits); - $convertedBuckets[] = $bucket; - } - - if (empty($convertedBuckets)) { - return; - } - - $this->callback($convertedBuckets); - } - - private function exportFiles(int $batchSize) - { - $storageClient = new Storage($this->client); - - $buckets = $this->cache->get(Bucket::getName()); - foreach ($buckets as $bucket) { - /** @var Bucket $bucket */ - $lastDocument = null; - - while (true) { - $queries = [Query::limit($batchSize)]; - - if ($lastDocument) { - $queries[] = Query::cursorAfter($lastDocument); - } - - $response = $storageClient->listFiles( - $bucket->getId(), - $queries - ); - - foreach ($response['files'] as $file) { - try { - $this->exportFileData(new File( - $file['$id'], - $bucket, - $file['name'], - $file['signature'], - $file['mimeType'], - $file['$permissions'], - $file['sizeOriginal'], - )); - } catch (\Throwable $e) { - $this->addError(new Exception( - resourceType: Resource::TYPE_FILE, - message: $e->getMessage(), - code: $e->getCode(), - resourceId: $file['$id'] - )); - } - - $lastDocument = $file['$id']; - } - - if (count($response['files']) < $batchSize) { - break; - } - } - } - } - - private function exportFileData(File $file) - { - // Set the chunk size (5MB) - $start = 0; - $end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1; - - // Get the file size - $fileSize = $file->getSize(); - - if ($end > $fileSize) { - $end = $fileSize - 1; - } - - // Loop until the entire file is downloaded - while ($start < $fileSize) { - $chunkData = $this->call( - 'GET', - "/storage/buckets/{$file->getBucket()->getId()}/files/{$file->getId()}/download", - ['range' => "bytes=$start-$end"] - ); - - // Send the chunk to the callback function - $file->setData($chunkData) - ->setStart($start) - ->setEnd($end); - - $this->callback([$file]); - - // Update the range - $start += Transfer::STORAGE_MAX_CHUNK_SIZE; - $end += Transfer::STORAGE_MAX_CHUNK_SIZE; - - if ($end > $fileSize) { - $end = $fileSize - 1; - } - } - } - - protected function exportGroupFunctions(int $batchSize, array $resources) - { - if (in_array(Resource::TYPE_FUNCTION, $resources)) { - $this->exportFunctions($batchSize); - } - - if (in_array(Resource::TYPE_DEPLOYMENT, $resources)) { - $this->exportDeployments($batchSize, true); - } - } - - private function exportFunctions(int $batchSize) - { - $functionsClient = new Functions($this->client); - - $functions = $functionsClient->list(); - - if ($functions['total'] === 0) { - return; - } - - $convertedResources = []; - - foreach ($functions['functions'] as $function) { - $convertedFunc = new Func( - $function['name'], - $function['$id'], - $function['runtime'], - $function['execute'], - $function['enabled'], - $function['events'], - $function['schedule'], - $function['timeout'], - $function['deployment'] - ); - - $convertedResources[] = $convertedFunc; - - foreach ($function['vars'] as $var) { - $convertedResources[] = new EnvVar( - $convertedFunc, - $var['key'], - $var['value'], - ); - } - } - - $this->callback($convertedResources); - } - - private function exportDeployments(int $batchSize, bool $exportOnlyActive = false) - { - $functionsClient = new Functions($this->client); - $functions = $this->cache->get(Func::getName()); - - // exportDeploymentData doesn't exist on Appwrite versions prior to 1.4 - $appwriteVersion = $this->call('GET', '/health/version', ['X-Appwrite-Key' => '', 'X-Appwrite-Project' => ''])['version']; - - if (version_compare($appwriteVersion, '1.4.0', '<')) { - return; - } - - foreach ($functions as $func) { - /** @var Func $func */ - $lastDocument = null; - - if ($exportOnlyActive && $func->getActiveDeployment()) { - $deployment = $functionsClient->getDeployment($func->getId(), $func->getActiveDeployment()); - - try { - $this->exportDeploymentData($func, $deployment); - } catch (\Throwable $e) { - $func->setStatus(Resource::STATUS_ERROR, $e->getMessage()); - } - - continue; - } - - while (true) { - $queries = [Query::limit($batchSize)]; - - if ($lastDocument) { - $queries[] = Query::cursorAfter($lastDocument); - } - - $response = $functionsClient->listDeployments( - $func->getId(), - $queries - ); - - foreach ($response['deployments'] as $deployment) { - try { - $this->exportDeploymentData($func, $deployment); - } catch (\Throwable $e) { - $func->setStatus(Resource::STATUS_ERROR, $e->getMessage()); - } - - $lastDocument = $deployment['$id']; - } - - if (count($response['deployments']) < $batchSize) { - break; - } - } - } - } - - private function exportDeploymentData(Func $func, array $deployment) - { - // Set the chunk size (5MB) - $start = 0; - $end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1; - - // Get the file size - $responseHeaders = []; - - $this->call( - 'HEAD', - "/functions/{$func->getId()}/deployments/{$deployment['$id']}/download", - [], - [], - $responseHeaders - ); - - // Content-Length header was missing, file is less than max buffer size. - if (! array_key_exists('Content-Length', $responseHeaders)) { - $file = $this->call( - 'GET', - "/functions/{$func->getId()}/deployments/{$deployment['$id']}/download", - [], - [], - $responseHeaders - ); - - $deployment = new Deployment( - $deployment['$id'], - $func, - strlen($file), - $deployment['entrypoint'], - $start, - $end, - $file, - $deployment['activate'] - ); - $deployment->setInternalId($deployment->getId()); - - return $this->callback([$deployment]); - } - - $fileSize = $responseHeaders['Content-Length']; - - $deployment = new Deployment( - $deployment['$id'], - $func, - $fileSize, - $deployment['entrypoint'], - $start, - $end, - '', - $deployment['activate'] - ); - - $deployment->setInternalId($deployment->getId()); - - // Loop until the entire file is downloaded - while ($start < $fileSize) { - $chunkData = $this->call( - 'GET', - "/functions/{$func->getId()}/deployments/{$deployment->getInternalId()}/download", - ['range' => "bytes=$start-$end"] - ); - - // Send the chunk to the callback function - $deployment->setData($chunkData); - $deployment->setStart($start); - $deployment->setEnd($end); - $this->callback([$deployment]); - - // Update the range - $start += Transfer::STORAGE_MAX_CHUNK_SIZE; - $end += Transfer::STORAGE_MAX_CHUNK_SIZE; - - if ($end > $fileSize) { - $end = $fileSize - 1; - } - } - } -} diff --git a/src/Migration/Sources/Backup.php b/src/Migration/Sources/Backup.php deleted file mode 100644 index 60510b0..0000000 --- a/src/Migration/Sources/Backup.php +++ /dev/null @@ -1,80 +0,0 @@ -path = $path; - $this->database = $database; - $this->storage = $storage; - } - - public static function getName(): string - { - return 'Backup'; - } - - /** - * Export Auth Group - * - * @param int $batchSize Max 100 - * @param string[] $resources Resources to export - */ - protected function exportGroupAuth(int $batchSize, array $resources) - { - - } - - /** - * Export Databases Group - * - * @param int $batchSize Max 100 - * @param string[] $resources Resources to export - */ - protected function exportGroupDatabases(int $batchSize, array $resources) - { - } - - /** - * Export Storage Group - * - * @param int $batchSize Max 5 - * @param string[] $resources Resources to export - */ - protected function exportGroupStorage(int $batchSize, array $resources) - { - } - - /** - * Export Functions Group - * - * @param int $batchSize Max 100 - * @param string[] $resources Resources to export - */ - protected function exportGroupFunctions(int $batchSize, array $resources) - { - } - - public static function getSupportedResources(): array - { - } - - - public function report(array $resources = []): array - { - } -} From aabe9a11ea79af786ca6ea90bb1a95bdadced6a1 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 20 Mar 2024 11:38:05 +0200 Subject: [PATCH 027/185] init & shutDown --- src/Migration/Destination.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Migration/Destination.php b/src/Migration/Destination.php index 2b291c5..8cd1ed7 100644 --- a/src/Migration/Destination.php +++ b/src/Migration/Destination.php @@ -49,4 +49,14 @@ public function run(array $resources, callable $callback): void * @param callable $callback Callback to run after import */ abstract protected function import(array $resources, callable $callback): void; + + /** + * Init function + */ + public function init(): void {} + + /** + * shutDown function + */ + public function shutDown(): void {} } From d7d4dc9593d638ca3f859d716151b6f2cf9d5997 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 21 Mar 2024 13:03:31 +0200 Subject: [PATCH 028/185] Pull main --- playground.php | 4 +--- src/Migration/Destination.php | 8 ++++++-- src/Migration/Source.php | 1 - src/Migration/Sources/Appwrite.php | 5 +---- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/playground.php b/playground.php index 6613f30..41e36da 100644 --- a/playground.php +++ b/playground.php @@ -10,7 +10,6 @@ use Dotenv\Dotenv; use Utopia\CLI\Console; use Utopia\Migration\Destinations\Appwrite as AppwriteDestination; -use Utopia\Migration\Destinations\Local; use Utopia\Migration\Destinations\Backup; use Utopia\Migration\Sources\Appwrite; use Utopia\Migration\Sources\Firebase; @@ -64,13 +63,12 @@ $_ENV['DESTINATION_APPWRITE_TEST_KEY'] ); - /**xx * Initialise Transfer Class */ $transfer = new Transfer( $sourceAppwrite, - new Backup(__DIR__ . '/localBackup/') + new Backup(__DIR__.'/localBackup/') ); /** diff --git a/src/Migration/Destination.php b/src/Migration/Destination.php index 8cd1ed7..8f3504a 100644 --- a/src/Migration/Destination.php +++ b/src/Migration/Destination.php @@ -53,10 +53,14 @@ abstract protected function import(array $resources, callable $callback): void; /** * Init function */ - public function init(): void {} + public function init(): void + { + } /** * shutDown function */ - public function shutDown(): void {} + public function shutDown(): void + { + } } diff --git a/src/Migration/Source.php b/src/Migration/Source.php index e187bea..88fb1a8 100644 --- a/src/Migration/Source.php +++ b/src/Migration/Source.php @@ -25,7 +25,6 @@ public function callback(array $resources): void public function run(array $resources, callable $callback): void { - $this->transferCallback = function (array $returnedResources) use ($callback, $resources) { $prunedResurces = []; diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 4ae521d..c81f09b 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -40,9 +40,6 @@ class Appwrite extends Source { - /** - * @var Client|null - */ protected ?Client $client = null; protected string $project = ''; @@ -761,7 +758,7 @@ private function exportCollections(int $batchSize) $collections[] = $newCollection; } - $lastCollection = !empty($collection) + $lastCollection = ! empty($collection) ? $collections[count($collections) - 1]->getId() : null; From 317d8f127810995d11705ebe251e5d1be08b68f4 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 21 Mar 2024 13:11:47 +0200 Subject: [PATCH 029/185] Client --- src/Migration/Destination.php | 2 -- src/Migration/Source.php | 3 --- src/Migration/Sources/Appwrite.php | 12 ++++-------- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/Migration/Destination.php b/src/Migration/Destination.php index 8f3504a..69809fb 100644 --- a/src/Migration/Destination.php +++ b/src/Migration/Destination.php @@ -35,9 +35,7 @@ public function setSource(Source $source): self */ public function run(array $resources, callable $callback): void { - $this->source->run($resources, function (array $resources) use ($callback) { - $this->import($resources, $callback); }); } diff --git a/src/Migration/Source.php b/src/Migration/Source.php index 88fb1a8..45ba29a 100644 --- a/src/Migration/Source.php +++ b/src/Migration/Source.php @@ -24,10 +24,8 @@ public function callback(array $resources): void */ public function run(array $resources, callable $callback): void { - $this->transferCallback = function (array $returnedResources) use ($callback, $resources) { $prunedResurces = []; - foreach ($returnedResources as $resource) { /** @var resource $resource */ if (! in_array($resource->getName(), $resources)) { @@ -73,7 +71,6 @@ public function exportResources(array $resources, int $batchSize) // Send each group to the relevant export function foreach ($groups as $group => $resources) { - switch ($group) { case Transfer::GROUP_AUTH: $this->exportGroupAuth($batchSize, $resources); diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index c81f09b..496cd0a 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -40,7 +40,10 @@ class Appwrite extends Source { - protected ?Client $client = null; + /** + * @var Client|null + */ + protected $client = null; protected string $project = ''; @@ -194,7 +197,6 @@ public function report(array $resources = []): array while (true) { $currentBuckets = $storageClient->listBuckets($lastBucket ? [Query::cursorAfter($lastBucket)] : [Query::limit(20)])['buckets']; - $buckets = array_merge($buckets, $currentBuckets); $lastBucket = $buckets[count($buckets) - 1]['$id'] ?? null; @@ -270,15 +272,11 @@ public function report(array $resources = []): array */ protected function exportGroupAuth(int $batchSize, array $resources) { - if (in_array(Resource::TYPE_USER, $resources)) { - $this->exportUsers($batchSize); - } if (in_array(Resource::TYPE_TEAM, $resources)) { - $this->exportTeams($batchSize); } @@ -682,7 +680,6 @@ private function convertAttribute(array $value, Collection $collection): Attribu private function exportDatabases(int $batchSize) { - $databaseClient = new Databases($this->client); $lastDatabase = null; @@ -728,7 +725,6 @@ private function exportCollections(int $batchSize) // Transfer Collections $databases = $this->cache->get(Database::getName()); - foreach ($databases as $database) { $lastCollection = null; From df296cfe8d3d2034e83f7a0140c47b6f74fc2a90 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 21 Mar 2024 13:29:12 +0200 Subject: [PATCH 030/185] Client --- src/Migration/Sources/Appwrite.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 496cd0a..8d14f02 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -40,9 +40,9 @@ class Appwrite extends Source { - /** - * @var Client|null - */ +// /** +// * @var Client|null +// */ protected $client = null; protected string $project = ''; From 99c73987cc41530e56d16f3ef72078e6925d929d Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 21 May 2024 16:21:19 +0300 Subject: [PATCH 031/185] Bumb utopia-php/database --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 09b4d50..b286e25 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "php": "8.*", "utopia-php/cli": "0.15.*", "appwrite/appwrite": "10.1.0", - "utopia-php/database": "0.48.*", + "utopia-php/database": "0.49.*", "utopia-php/storage": "0.18.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*" From 0b02c1910ba332ba356cbac86ead5f3f4f854554 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 25 May 2024 03:28:51 +1200 Subject: [PATCH 032/185] Update resource serialization --- src/Migration/Resources/Auth/Hash.php | 195 ++++---------- src/Migration/Resources/Auth/Membership.php | 92 ++++--- src/Migration/Resources/Auth/Team.php | 86 +++--- src/Migration/Resources/Auth/User.php | 249 +++++------------- .../Resources/Database/Attribute.php | 86 ++---- .../Resources/Database/Attributes/Boolean.php | 50 ++-- .../Database/Attributes/DateTime.php | 24 +- .../Resources/Database/Attributes/Decimal.php | 76 +++--- .../Resources/Database/Attributes/Email.php | 24 +- .../Resources/Database/Attributes/Enum.php | 64 +++-- .../Resources/Database/Attributes/IP.php | 21 +- .../Resources/Database/Attributes/Integer.php | 78 +++--- .../Database/Attributes/Relationship.php | 79 +++--- .../Resources/Database/Attributes/Text.php | 57 ++-- .../Resources/Database/Attributes/URL.php | 24 +- .../Resources/Database/Collection.php | 83 +++--- src/Migration/Resources/Database/Database.php | 59 ++--- src/Migration/Resources/Database/Document.php | 91 ++++--- src/Migration/Resources/Database/Index.php | 117 ++++---- .../Resources/Functions/Deployment.php | 104 +++----- src/Migration/Resources/Functions/EnvVar.php | 68 +++-- src/Migration/Resources/Functions/Func.php | 145 ++++------ src/Migration/Resources/Storage/Bucket.php | 174 +++++------- src/Migration/Resources/Storage/File.php | 129 +++++---- 24 files changed, 863 insertions(+), 1312 deletions(-) diff --git a/src/Migration/Resources/Auth/Hash.php b/src/Migration/Resources/Auth/Hash.php index e1fef05..401cd66 100644 --- a/src/Migration/Resources/Auth/Hash.php +++ b/src/Migration/Resources/Auth/Hash.php @@ -10,51 +10,70 @@ */ class Hash extends Resource { - public const ALGORITHM_SCRYPT_MODIFIED = 'scryptModified'; + public const string ALGORITHM_SCRYPT_MODIFIED = 'scryptModified'; - public const ALGORITHM_BCRYPT = 'bcrypt'; + public const string ALGORITHM_BCRYPT = 'bcrypt'; - public const ALGORITHM_MD5 = 'md5'; + public const string ALGORITHM_MD5 = 'md5'; - public const ALGORITHM_ARGON2 = 'argon2'; + public const string ALGORITHM_ARGON2 = 'argon2'; - public const ALGORITHM_SHA256 = 'sha256'; + public const string ALGORITHM_SHA256 = 'sha256'; - public const ALGORITHM_PHPASS = 'phpass'; + public const string ALGORITHM_PHPASS = 'phpass'; - public const ALGORITHM_SCRYPT = 'scrypt'; + public const string ALGORITHM_SCRYPT = 'scrypt'; - public const ALGORITHM_PLAINTEXT = 'plainText'; + public const string ALGORITHM_PLAINTEXT = 'plainText'; - private string $hash; - - private string $salt = ''; - - private string $algorithm = self::ALGORITHM_SHA256; - - private string $separator = ''; - - private string $signingKey = ''; - - private int $passwordCpu = 0; - - private int $passwordMemory = 0; - - private int $passwordParallel = 0; + public function __construct( + private readonly string $hash, + private readonly string $salt = '', + private readonly string $algorithm = self::ALGORITHM_SHA256, + private readonly string $separator = '', + private readonly string $signingKey = '', + private readonly int $passwordCpu = 0, + private readonly int $passwordMemory = 0, + private readonly int $passwordParallel = 0, + private readonly int $passwordLength = 0 + ) { + } - private int $passwordLength = 0; + /** + * @param array $array + * @return self + */ + public static function fromArray(array $array): self + { + return new self( + $array['hash'] ?? '', + $array['salt'] ?? '', + $array['algorithm'] ?? self::ALGORITHM_SHA256, + $array['separator'] ?? '', + $array['signingKey'] ?? '', + $array['passwordCpu'] ?? 0, + $array['passwordMemory'] ?? 0, + $array['passwordParallel'] ?? 0, + $array['passwordLength'] ?? 0 + ); + } - public function __construct(string $hash, string $salt = '', string $algorithm = self::ALGORITHM_SHA256, string $separator = '', string $signingKey = '', int $passwordCpu = 0, int $passwordMemory = 0, int $passwordParallel = 0, int $passwordLength = 0) + /** + * @return array + */ + public function jsonSerialize(): array { - $this->hash = $hash; - $this->salt = $salt; - $this->algorithm = $algorithm; - $this->separator = $separator; - $this->signingKey = $signingKey; - $this->passwordCpu = $passwordCpu; - $this->passwordMemory = $passwordMemory; - $this->passwordParallel = $passwordParallel; - $this->passwordLength = $passwordLength; + return [ + 'hash' => $this->hash, + 'salt' => $this->salt, + 'algorithm' => $this->algorithm, + 'separator' => $this->separator, + 'signingKey' => $this->signingKey, + 'passwordCpu' => $this->passwordCpu, + 'passwordMemory' => $this->passwordMemory, + 'passwordParallel' => $this->passwordParallel, + 'passwordLength' => $this->passwordLength, + ]; } public static function getName(): string @@ -75,16 +94,6 @@ public function getHash(): string return $this->hash; } - /** - * Set Hash - */ - public function setHash(string $hash): self - { - $this->hash = $hash; - - return $this; - } - /** * Get Salt */ @@ -93,16 +102,6 @@ public function getSalt(): string return $this->salt; } - /** - * Set Salt - */ - public function setSalt(string $salt): self - { - $this->salt = $salt; - - return $this; - } - /** * Get Algorithm */ @@ -111,16 +110,6 @@ public function getAlgorithm(): string return $this->algorithm; } - /** - * Set Algorithm - */ - public function setAlgorithm(string $algorithm): self - { - $this->algorithm = $algorithm; - - return $this; - } - /** * Get Separator */ @@ -129,16 +118,6 @@ public function getSeparator(): string return $this->separator; } - /** - * Set Separator - */ - public function setSeparator(string $separator): self - { - $this->separator = $separator; - - return $this; - } - /** * Get Signing Key */ @@ -147,16 +126,6 @@ public function getSigningKey(): string return $this->signingKey; } - /** - * Set Signing Key - */ - public function setSigningKey(string $signingKey): self - { - $this->signingKey = $signingKey; - - return $this; - } - /** * Get Password CPU */ @@ -165,16 +134,6 @@ public function getPasswordCpu(): int return $this->passwordCpu; } - /** - * Set Password CPU - */ - public function setPasswordCpu(int $passwordCpu): self - { - $this->passwordCpu = $passwordCpu; - - return $this; - } - /** * Get Password Memory */ @@ -183,16 +142,6 @@ public function getPasswordMemory(): int return $this->passwordMemory; } - /** - * Set Password Memory - */ - public function setPasswordMemory(int $passwordMemory): self - { - $this->passwordMemory = $passwordMemory; - - return $this; - } - /** * Get Password Parallel */ @@ -201,16 +150,6 @@ public function getPasswordParallel(): int return $this->passwordParallel; } - /** - * Set Password Parallel - */ - public function setPasswordParallel(int $passwordParallel): self - { - $this->passwordParallel = $passwordParallel; - - return $this; - } - /** * Get Password Length */ @@ -218,32 +157,4 @@ public function getPasswordLength(): int { return $this->passwordLength; } - - /** - * Set Password Length - */ - public function setPasswordLength(int $passwordLength): self - { - $this->passwordLength = $passwordLength; - - return $this; - } - - /** - * As Array - */ - public function asArray(): array - { - return [ - 'hash' => $this->hash, - 'salt' => $this->salt, - 'algorithm' => $this->algorithm, - 'separator' => $this->separator, - 'signingKey' => $this->signingKey, - 'passwordCpu' => $this->passwordCpu, - 'passwordMemory' => $this->passwordMemory, - 'passwordParallel' => $this->passwordParallel, - 'passwordLength' => $this->passwordLength, - ]; - } } diff --git a/src/Migration/Resources/Auth/Membership.php b/src/Migration/Resources/Auth/Membership.php index efe5816..a97915a 100644 --- a/src/Migration/Resources/Auth/Membership.php +++ b/src/Migration/Resources/Auth/Membership.php @@ -10,20 +10,50 @@ */ class Membership extends Resource { - protected Team $team; - - protected User $user; - - protected array $roles; + /** + * @param string $id + * @param Team $team + * @param User $user + * @param array $roles + * @param bool $active + */ + public function __construct( + string $id, + private readonly Team $team, + private readonly User $user, + private readonly array $roles = [], + private readonly bool $active = true + ) { + $this->id = $id; + } - protected bool $active = true; + /** + * @param array $array + * @return self + */ + public static function fromArray(array $array): self + { + return new self( + $array['id'], + Team::fromArray($array['team'] ?? []), + User::fromArray($array['user'] ?? []), + $array['roles'] ?? [], + $array['active'] ?? true + ); + } - public function __construct(Team $team, User $user, array $roles = [], bool $active = true) + /** + * @return array + */ + public function jsonSerialize(): array { - $this->team = $team; - $this->user = $user; - $this->roles = $roles; - $this->active = $active; + return [ + 'id' => $this->id, + 'team' => $this->team, + 'user' => $this->user, + 'roles' => $this->roles, + 'active' => $this->active, + ]; } public static function getName(): string @@ -41,55 +71,21 @@ public function getTeam(): Team return $this->team; } - public function setTeam(Team $team): self - { - $this->team = $team; - - return $this; - } - public function getUser(): User { return $this->user; } - public function setUser(User $user): self - { - $this->user = $user; - - return $this; - } - + /** + * @return array + */ public function getRoles(): array { return $this->roles; } - public function setRoles(array $roles): self - { - $this->roles = $roles; - - return $this; - } - public function getActive(): bool { return $this->active; } - - public function setActive(bool $active): self - { - $this->active = $active; - - return $this; - } - - public function asArray(): array - { - return [ - 'userId' => $this->user->getId(), - 'roles' => $this->roles, - 'active' => $this->active, - ]; - } } diff --git a/src/Migration/Resources/Auth/Team.php b/src/Migration/Resources/Auth/Team.php index b379129..c9d8440 100644 --- a/src/Migration/Resources/Auth/Team.php +++ b/src/Migration/Resources/Auth/Team.php @@ -3,23 +3,50 @@ namespace Utopia\Migration\Resources\Auth; use Utopia\Migration\Resource; -use Utopia\Migration\Resources\User; use Utopia\Migration\Transfer; class Team extends Resource { - protected string $name; - - protected array $preferences = []; + /** + * @param string $id + * @param string $name + * @param array $preferences + * @param array $members + */ + public function __construct( + string $id, + private readonly string $name, + private readonly array $preferences = [], + private readonly array $members = [] + ) { + $this->id = $id; + } - protected array $members = []; + /** + * @param array $array + * @return self + */ + public static function fromArray(array $array): self + { + return new self( + $array['id'], + $array['name'], + $array['preferences'] ?? [], + $array['members'] ?? [] + ); + } - public function __construct(string $id, string $name, array $preferences = [], array $members = []) + /** + * @return array + */ + public function jsonSerialize(): array { - $this->id = $id; - $this->name = $name; - $this->preferences = $preferences; - $this->members = $members; + return [ + 'id' => $this->id, + 'name' => $this->name, + 'preferences' => $this->preferences, + 'members' => $this->members, + ]; } public static function getName(): string @@ -37,46 +64,19 @@ public function getTeamName(): string return $this->name; } - public function setTeamName(string $name): self - { - $this->name = $name; - - return $this; - } - + /** + * @return array + */ public function getPreferences(): array { return $this->preferences; } - public function setPreferences(array $preferences): self - { - $this->preferences = $preferences; - - return $this; - } - - public function getMembers(): array - { - return $this->members; - } - /** - * @param User[] $members + * @return array */ - public function setMembers(array $members): self - { - $this->members = $members; - - return $this; - } - - public function asArray(): array + public function getMembers(): array { - return [ - 'id' => $this->id, - 'name' => $this->name, - 'preferences' => $this->preferences, - ]; + return $this->members; } } diff --git a/src/Migration/Resources/Auth/User.php b/src/Migration/Resources/Auth/User.php index af87b64..ea7d11a 100644 --- a/src/Migration/Resources/Auth/User.php +++ b/src/Migration/Resources/Auth/User.php @@ -7,108 +7,111 @@ class User extends Resource { - public const TYPE_PASSWORD = 'password'; + public const string TYPE_PASSWORD = 'password'; - public const TYPE_PHONE = 'phone'; + public const string TYPE_PHONE = 'phone'; - public const TYPE_ANONYMOUS = 'anonymous'; + public const string TYPE_ANONYMOUS = 'anonymous'; - public const TYPE_MAGIC = 'magic'; + public const string TYPE_MAGIC = 'magic'; - public const TYPE_OAUTH = 'oauth'; - - protected string $email = ''; - - protected string $username = ''; - - protected ?Hash $passwordHash = null; - - protected string $phone = ''; - - protected array $types = [self::TYPE_ANONYMOUS]; - - protected array $labels = []; - - protected string $oauthProvider = ''; - - protected bool $emailVerified = false; - - protected bool $phoneVerified = false; - - protected bool $disabled = false; - - protected array $preferences = []; + public const string TYPE_OAUTH = 'oauth'; + /** + * @param string $id + * @param string $email + * @param string $username + * @param Hash|null $passwordHash + * @param string $phone + * @param array $types + * @param array $labels + * @param string $oauthProvider + * @param bool $emailVerified + * @param bool $phoneVerified + * @param bool $disabled + * @param array $preferences + */ public function __construct( string $id, - string $email = '', - string $username = '', - ?Hash $passwordHash = null, - string $phone = '', - array $types = [self::TYPE_ANONYMOUS], - array $labels = [], - string $oauthProvider = '', - bool $emailVerified = false, - bool $phoneVerified = false, - bool $disabled = false, - array $preferences = [] + private readonly string $email = '', + private readonly string $username = '', + private readonly ?Hash $passwordHash = null, + private readonly string $phone = '', + private readonly array $types = [self::TYPE_ANONYMOUS], + private readonly array $labels = [], + private readonly string $oauthProvider = '', + private readonly bool $emailVerified = false, + private readonly bool $phoneVerified = false, + private readonly bool $disabled = false, + private readonly array $preferences = [] ) { $this->id = $id; - $this->email = $email; - $this->username = $username; - $this->passwordHash = $passwordHash; - $this->phone = $phone; - $this->types = $types; - $this->labels = $labels; - $this->oauthProvider = $oauthProvider; - $this->emailVerified = $emailVerified; - $this->phoneVerified = $phoneVerified; - $this->disabled = $disabled; - $this->preferences = $preferences; } /** - * Get Name + * @param array $array + * @return self */ - public static function getName(): string + public static function fromArray(array $array): self { - return Resource::TYPE_USER; + return new self( + $array['id'], + $array['email'] ?? '', + $array['username'] ?? '', + $array['passwordHash'] ?? null, + $array['phone'] ?? '', + $array['types'] ?? [self::TYPE_ANONYMOUS], + $array['labels'] ?? [], + $array['oauthProvider'] ?? '', + $array['emailVerified'] ?? false, + $array['phoneVerified'] ?? false, + $array['disabled'] ?? false, + $array['preferences'] ?? [] + ); } /** - * Get Email + * @return array */ - public function getEmail(): string + public function jsonSerialize(): array { - return $this->email; + return [ + 'id' => $this->id, + 'email' => $this->email, + 'username' => $this->username, + 'passwordHash' => $this->passwordHash, + 'phone' => $this->phone, + 'types' => $this->types, + 'labels' => $this->labels, + 'oauthProvider' => $this->oauthProvider, + 'emailVerified' => $this->emailVerified, + 'phoneVerified' => $this->phoneVerified, + 'disabled' => $this->disabled, + 'preferences' => $this->preferences, + ]; } /** - * Set Email + * Get Name */ - public function setEmail(string $email): self + public static function getName(): string { - $this->email = $email; - - return $this; + return Resource::TYPE_USER; } /** - * Get Username + * Get Email */ - public function getUsername(): string + public function getEmail(): string { - return $this->username; + return $this->email; } - /** - * Set Username + * Get Username */ - public function setUsername(string $username): self + public function getUsername(): string { - $this->username = $username; - - return $this; + return $this->username; } /** @@ -119,16 +122,6 @@ public function getPasswordHash(): ?Hash return $this->passwordHash; } - /** - * Set Password Hash - */ - public function setPasswordHash(Hash $passwordHash): self - { - $this->passwordHash = $passwordHash; - - return $this; - } - /** * Get Phone */ @@ -137,16 +130,6 @@ public function getPhone(): string return $this->phone; } - /** - * Set Phone - */ - public function setPhone(string $phone): self - { - $this->phone = $phone; - - return $this; - } - /** * Get Type */ @@ -155,16 +138,6 @@ public function getTypes(): array return $this->types; } - /** - * Set Types - */ - public function setTypes(string $types): self - { - $this->types = $types; - - return $this; - } - /** * Get Labels */ @@ -173,16 +146,6 @@ public function getLabels(): array return $this->labels; } - /** - * Set Labels - */ - public function setLabels(array $labels): self - { - $this->labels = $labels; - - return $this; - } - /** * Get OAuth Provider */ @@ -191,16 +154,6 @@ public function getOAuthProvider(): string return $this->oauthProvider; } - /** - * Set OAuth Provider - */ - public function setOAuthProvider(string $oauthProvider): self - { - $this->oauthProvider = $oauthProvider; - - return $this; - } - /** * Get Email Verified */ @@ -209,16 +162,6 @@ public function getEmailVerified(): bool return $this->emailVerified; } - /** - * Set Email Verified - */ - public function setEmailVerified(bool $verified): self - { - $this->emailVerified = $verified; - - return $this; - } - /** * Get Email Verified */ @@ -227,16 +170,6 @@ public function getPhoneVerified(): bool return $this->phoneVerified; } - /** - * Set Phone Verified - */ - public function setPhoneVerified(bool $verified): self - { - $this->phoneVerified = $verified; - - return $this; - } - public function getGroup(): string { return Transfer::GROUP_AUTH; @@ -250,16 +183,6 @@ public function getDisabled(): bool return $this->disabled; } - /** - * Set Disabled - */ - public function setDisabled(bool $disabled): self - { - $this->disabled = $disabled; - - return $this; - } - /** * Get Preferences */ @@ -267,32 +190,4 @@ public function getPreferences(): array { return $this->preferences; } - - /** - * Set Preferences - */ - public function setPreferences(array $preferences): self - { - $this->preferences = $preferences; - - return $this; - } - - /** - * As Array - */ - public function asArray(): array - { - return [ - 'id' => $this->id, - 'email' => $this->email, - 'username' => $this->username, - 'passwordHash' => $this->passwordHash ? $this->passwordHash->asArray() : null, - 'phone' => $this->phone, - 'types' => $this->types, - 'oauthProvider' => $this->oauthProvider, - 'emailVerified' => $this->emailVerified, - 'phoneVerified' => $this->phoneVerified, - ]; - } } diff --git a/src/Migration/Resources/Database/Attribute.php b/src/Migration/Resources/Database/Attribute.php index a81909b..6c5982b 100644 --- a/src/Migration/Resources/Database/Attribute.php +++ b/src/Migration/Resources/Database/Attribute.php @@ -7,43 +7,47 @@ abstract class Attribute extends Resource { - public const TYPE_STRING = 'string'; + public const string TYPE_STRING = 'string'; - public const TYPE_INTEGER = 'int'; + public const string TYPE_INTEGER = 'int'; - public const TYPE_FLOAT = 'float'; + public const string TYPE_FLOAT = 'float'; - public const TYPE_BOOLEAN = 'bool'; + public const string TYPE_BOOLEAN = 'bool'; - public const TYPE_DATETIME = 'dateTime'; + public const string TYPE_DATETIME = 'dateTime'; - public const TYPE_EMAIL = 'email'; + public const string TYPE_EMAIL = 'email'; - public const TYPE_ENUM = 'enum'; + public const string TYPE_ENUM = 'enum'; - public const TYPE_IP = 'IP'; + public const string TYPE_IP = 'IP'; - public const TYPE_URL = 'URL'; + public const string TYPE_URL = 'URL'; - public const TYPE_RELATIONSHIP = 'relationship'; + public const string TYPE_RELATIONSHIP = 'relationship'; - protected string $key; - protected bool $required; - - protected bool $array; - - protected Collection $collection; + public function __construct( + protected readonly string $key, + protected readonly Collection $collection, + protected readonly bool $required = false, + protected readonly bool $array = false + ) { + } /** - * @param int $size + * @return array */ - public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false) + public function jsonSerialize(): array { - $this->key = $key; - $this->required = $required; - $this->array = $array; - $this->collection = $collection; + return [ + 'key' => $this->key, + 'type' => $this->getTypeName(), + 'collection' => $this->collection, + 'required' => $this->required, + 'array' => $this->array, + ]; } public static function getName(): string @@ -63,56 +67,18 @@ public function getKey(): string return $this->key; } - public function setKey(string $key): self - { - $this->key = $key; - - return $this; - } - public function getCollection(): Collection { return $this->collection; } - public function setCollection(Collection $collection) - { - $this->collection = $collection; - - return $this; - } - public function getRequired(): bool { return $this->required; } - public function setRequired(bool $required): self - { - $this->required = $required; - - return $this; - } - public function getArray(): bool { return $this->array; } - - public function setArray(bool $array): self - { - $this->array = $array; - - return $this; - } - - public function asArray(): array - { - return [ - 'key' => $this->key, - 'required' => $this->required, - 'array' => $this->array, - 'type' => $this->getName(), - ]; - } } diff --git a/src/Migration/Resources/Database/Attributes/Boolean.php b/src/Migration/Resources/Database/Attributes/Boolean.php index 1d43f67..5ac539d 100644 --- a/src/Migration/Resources/Database/Attributes/Boolean.php +++ b/src/Migration/Resources/Database/Attributes/Boolean.php @@ -7,21 +7,39 @@ class Boolean extends Attribute { - protected string $key; - - protected bool $required; - - protected bool $array; + public function __construct( + string $key, + Collection $collection, + bool $required = false, + bool $array = false, + private readonly ?bool $default = null + ) { + parent::__construct($key, $collection, $required, $array); + } - protected ?bool $default; + /** + * @param array $array + * @return self + */ + public static function fromArray(array $array): self + { + return new self( + $array['key'], + Collection::fromArray($array['collection']), + $array['required'] ?? false, + $array['array'] ?? false, + $array['default'] ?? null + ); + } /** - * @param ?bool $default + * @return array */ - public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?bool $default = null) + public function jsonSerialize(): array { - parent::__construct($key, $collection, $required, $array); - $this->default = $default; + return array_merge(parent::jsonSerialize(), [ + 'default' => $this->default, + ]); } public function getTypeName(): string @@ -33,16 +51,4 @@ public function getDefault(): ?bool { return $this->default; } - - public function setDefault(bool $default): void - { - $this->default = $default; - } - - public function asArray(): array - { - return array_merge(parent::asArray(), [ - 'default' => $this->default, - ]); - } } diff --git a/src/Migration/Resources/Database/Attributes/DateTime.php b/src/Migration/Resources/Database/Attributes/DateTime.php index 363f0f5..09d1906 100644 --- a/src/Migration/Resources/Database/Attributes/DateTime.php +++ b/src/Migration/Resources/Database/Attributes/DateTime.php @@ -3,31 +3,9 @@ namespace Utopia\Migration\Resources\Database\Attributes; use Utopia\Migration\Resources\Database\Attribute; -use Utopia\Migration\Resources\Database\Collection; -class DateTime extends Attribute +class DateTime extends Text { - protected ?string $default; - - /** - * @param ?string $default - */ - public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?string $default = null) - { - parent::__construct($key, $collection, $required, $array); - $this->default = $default; - } - - public function getDefault(): ?string - { - return $this->default; - } - - public function setDefault(string $default): void - { - $this->default = $default; - } - public function getTypeName(): string { return Attribute::TYPE_DATETIME; diff --git a/src/Migration/Resources/Database/Attributes/Decimal.php b/src/Migration/Resources/Database/Attributes/Decimal.php index 111b326..ab89000 100644 --- a/src/Migration/Resources/Database/Attributes/Decimal.php +++ b/src/Migration/Resources/Database/Attributes/Decimal.php @@ -7,23 +7,45 @@ class Decimal extends Attribute { - protected ?float $default; - - protected ?float $min; + public function __construct( + string $key, + Collection $collection, + bool $required = false, + bool $array = false, + private readonly ?float $default = null, + private readonly ?float $min = null, + private readonly ?float $max = null + ) { + parent::__construct($key, $collection, $required, $array); + } - protected ?float $max; + /** + * @param array $array + * @return self + */ + public static function fromArray(array $array): self + { + return new self( + $array['key'], + Collection::fromArray($array['collection']), + $array['required'] ?? false, + $array['array'] ?? false, + $array['default'] ?? null, + $array['min'] ?? null, + $array['max'] ?? null + ); + } /** - * @param ?float $default - * @param ?float $min - * @param ?float $max + * @return array */ - public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?float $default = null, ?float $min = null, ?float $max = null) + public function jsonSerialize(): array { - parent::__construct($key, $collection, $required, $array); - $this->default = $default; - $this->min = $min; - $this->max = $max; + return array_merge(parent::jsonSerialize(), [ + 'default' => $this->default, + 'min' => $this->min, + 'max' => $this->max, + ]); } public function getTypeName(): string @@ -41,38 +63,8 @@ public function getMax(): ?float return $this->max; } - public function setMin(float $min): self - { - $this->min = $min; - - return $this; - } - - public function setMax(float $max): self - { - $this->max = $max; - - return $this; - } - public function getDefault(): ?float { return $this->default; } - - public function setDefault(float $default): self - { - $this->default = $default; - - return $this; - } - - public function asArray(): array - { - return array_merge(parent::asArray(), [ - 'min' => $this->getMin(), - 'max' => $this->getMax(), - 'default' => $this->getDefault(), - ]); - } } diff --git a/src/Migration/Resources/Database/Attributes/Email.php b/src/Migration/Resources/Database/Attributes/Email.php index 4fa025a..d815236 100644 --- a/src/Migration/Resources/Database/Attributes/Email.php +++ b/src/Migration/Resources/Database/Attributes/Email.php @@ -3,31 +3,9 @@ namespace Utopia\Migration\Resources\Database\Attributes; use Utopia\Migration\Resources\Database\Attribute; -use Utopia\Migration\Resources\Database\Collection; -class Email extends Attribute +class Email extends Text { - protected ?string $default; - - /** - * @param ?string $default - */ - public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?string $default = null) - { - parent::__construct($key, $collection, $required, $array); - $this->default = $default; - } - - public function getDefault(): ?string - { - return $this->default; - } - - public function setDefault(string $default): void - { - $this->default = $default; - } - public function getTypeName(): string { return Attribute::TYPE_EMAIL; diff --git a/src/Migration/Resources/Database/Attributes/Enum.php b/src/Migration/Resources/Database/Attributes/Enum.php index e80d36d..b7f58f3 100644 --- a/src/Migration/Resources/Database/Attributes/Enum.php +++ b/src/Migration/Resources/Database/Attributes/Enum.php @@ -7,19 +7,45 @@ class Enum extends Attribute { - protected ?string $default; + /** + * @param array $elements + */ + public function __construct( + string $key, + Collection $collection, + private readonly array $elements, + bool $required = false, + bool $array = false, + private readonly ?string $default = null + ) { + parent::__construct($key, $collection, $required, $array); + } - protected array $elements; + /** + * @param array $array + * @return self + */ + public static function fromArray(array $array): self + { + return new self( + $array['key'], + Collection::fromArray($array['collection']), + $array['elements'], + $array['required'] ?? false, + $array['array'] ?? false, + $array['default'] ?? null + ); + } /** - * @param string[] $elements - * @param ?string $default + * @return array */ - public function __construct(string $key, Collection $collection, array $elements, bool $required, bool $array, ?string $default) + public function jsonSerialize(): array { - parent::__construct($key, $collection, $required, $array); - $this->default = $default; - $this->elements = $elements; + return array_merge(parent::jsonSerialize(), [ + 'elements' => $this->elements, + 'default' => $this->default, + ]); } public function getTypeName(): string @@ -27,32 +53,16 @@ public function getTypeName(): string return Attribute::TYPE_ENUM; } + /** + * @return array + */ public function getElements(): array { return $this->elements; } - public function setElements(array $elements): self - { - $this->elements = $elements; - - return $this; - } - public function getDefault(): ?string { return $this->default; } - - public function setDefault(string $default): void - { - $this->default = $default; - } - - public function asArray(): array - { - return array_merge(parent::asArray(), [ - 'elements' => $this->elements, - ]); - } } diff --git a/src/Migration/Resources/Database/Attributes/IP.php b/src/Migration/Resources/Database/Attributes/IP.php index 35f3c1d..b930c4a 100644 --- a/src/Migration/Resources/Database/Attributes/IP.php +++ b/src/Migration/Resources/Database/Attributes/IP.php @@ -3,28 +3,9 @@ namespace Utopia\Migration\Resources\Database\Attributes; use Utopia\Migration\Resources\Database\Attribute; -use Utopia\Migration\Resources\Database\Collection; -class IP extends Attribute +class IP extends Text { - protected ?string $default; - - public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?string $default = null) - { - parent::__construct($key, $collection, $required, $array); - $this->default = $default; - } - - public function getDefault(): ?string - { - return $this->default; - } - - public function setDefault(string $default): void - { - $this->default = $default; - } - public function getTypeName(): string { return Attribute::TYPE_IP; diff --git a/src/Migration/Resources/Database/Attributes/Integer.php b/src/Migration/Resources/Database/Attributes/Integer.php index 75ecee1..f2c5b2a 100644 --- a/src/Migration/Resources/Database/Attributes/Integer.php +++ b/src/Migration/Resources/Database/Attributes/Integer.php @@ -7,28 +7,50 @@ class Integer extends Attribute { - protected ?int $default; - - protected ?int $min; + public function __construct( + string $key, + Collection $collection, + bool $required = false, + bool $array = false, + private readonly ?int $default = null, + private readonly ?int $min = null, + private readonly ?int $max = null + ) { + parent::__construct($key, $collection, $required, $array); + } - protected ?int $max; + /** + * @param array $array + * @return self + */ + public static function fromArray(array $array): self + { + return new self( + $array['key'], + Collection::fromArray($array['collection']), + $array['required'] ?? false, + $array['array'] ?? false, + $array['default'] ?? null, + $array['min'] ?? null, + $array['max'] ?? null + ); + } /** - * @param ?int $default - * @param ?int $min - * @param ?int $max + * @return array */ - public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?int $default = null, ?int $min = null, ?int $max = null) + public function jsonSerialize(): array { - parent::__construct($key, $collection, $required, $array); - $this->default = $default; - $this->min = $min; - $this->max = $max; + return array_merge(parent::jsonSerialize(), [ + 'default' => $this->default, + 'min' => $this->min, + 'max' => $this->max, + ]); } public function getTypeName(): string { - return Attribute::TYPE_INTEGER; + return Attribute::TYPE_FLOAT; } public function getMin(): ?int @@ -41,38 +63,8 @@ public function getMax(): ?int return $this->max; } - public function setMin(?int $min): self - { - $this->min = $min; - - return $this; - } - - public function setMax(?int $max): self - { - $this->max = $max; - - return $this; - } - public function getDefault(): ?int { return $this->default; } - - public function setDefault(int $default): self - { - $this->default = $default; - - return $this; - } - - public function asArray(): array - { - return array_merge(parent::asArray(), [ - 'min' => $this->getMin(), - 'max' => $this->getMax(), - 'default' => $this->getDefault(), - ]); - } } diff --git a/src/Migration/Resources/Database/Attributes/Relationship.php b/src/Migration/Resources/Database/Attributes/Relationship.php index 0aa3b48..ed41c35 100644 --- a/src/Migration/Resources/Database/Attributes/Relationship.php +++ b/src/Migration/Resources/Database/Attributes/Relationship.php @@ -19,8 +19,18 @@ class Relationship extends Attribute protected string $side; - public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, string $relatedCollection = '', string $relationType = '', bool $twoWay = false, string $twoWayKey = '', string $onDelete = '', string $side = '') - { + public function __construct( + string $key, + Collection $collection, + bool $required = false, + bool $array = false, + string $relatedCollection = '', + string $relationType = '', + bool $twoWay = false, + string $twoWayKey = '', + string $onDelete = '', + string $side = '' + ) { parent::__construct($key, $collection, $required, $array); $this->relatedCollection = $relatedCollection; $this->relationType = $relationType; @@ -30,29 +40,54 @@ public function __construct(string $key, Collection $collection, bool $required $this->side = $side; } - public function getTypeName(): string + /** + * @param array $array + * @return self + */ + public static function fromArray(array $array): self { - return Attribute::TYPE_RELATIONSHIP; + return new self( + $array['key'], + Collection::fromArray($array['collection']), + $array['required'] ?? false, + $array['array'] ?? false, + $array['relatedCollection'] ?? '', + $array['relationType'] ?? '', + $array['twoWay'] ?? false, + $array['twoWayKey'] ?? '', + $array['onDelete'] ?? '', + $array['side'] ?? '' + ); } - public function getRelatedCollection(): string + /** + * @return array + */ + public function jsonSerialize(): array { - return $this->relatedCollection; + return array_merge(parent::jsonSerialize(), [ + 'relatedCollection' => $this->relatedCollection, + 'relationType' => $this->relationType, + 'twoWay' => $this->twoWay, + 'twoWayKey' => $this->twoWayKey, + 'onDelete' => $this->onDelete, + 'side' => $this->side, + ]); } - public function setRelatedCollection(string $relatedCollection): void + public function getTypeName(): string { - $this->relatedCollection = $relatedCollection; + return Attribute::TYPE_RELATIONSHIP; } - public function getRelationType(): string + public function getRelatedCollection(): string { - return $this->relationType; + return $this->relatedCollection; } - public function setRelationType(string $relationType): void + public function getRelationType(): string { - $this->relationType = $relationType; + return $this->relationType; } public function getTwoWay(): bool @@ -60,38 +95,18 @@ public function getTwoWay(): bool return $this->twoWay; } - public function setTwoWay(bool $twoWay): void - { - $this->twoWay = $twoWay; - } - public function getTwoWayKey(): string { return $this->twoWayKey; } - public function setTwoWayKey(string $twoWayKey): void - { - $this->twoWayKey = $twoWayKey; - } - public function getOnDelete(): string { return $this->onDelete; } - public function setOnDelete(string $onDelete): void - { - $this->onDelete = $onDelete; - } - public function getSide(): string { return $this->side; } - - public function setSide(string $side): void - { - $this->side = $side; - } } diff --git a/src/Migration/Resources/Database/Attributes/Text.php b/src/Migration/Resources/Database/Attributes/Text.php index 8b4b6ba..2bc0ed3 100644 --- a/src/Migration/Resources/Database/Attributes/Text.php +++ b/src/Migration/Resources/Database/Attributes/Text.php @@ -7,18 +7,42 @@ class Text extends Attribute { - protected ?string $default; + public function __construct( + string $key, + Collection $collection, + bool $required = false, + bool $array = false, + private readonly ?string $default = null, + private readonly int $size = 256 + ) { + parent::__construct($key, $collection, $required, $array); + } - protected int $size = 256; + /** + * @param array $array + * @return self + */ + public static function fromArray(array $array): self + { + return new self( + $array['key'], + Collection::fromArray($array['collection']), + $array['required'] ?? false, + $array['array'] ?? false, + $array['default'] ?? null, + $array['size'] ?? 256 + ); + } /** - * @param ?string $default + * @return array */ - public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?string $default = null, int $size = 256) + public function jsonSerialize(): array { - parent::__construct($key, $collection, $required, $array); - $this->default = $default; - $this->size = $size; + return array_merge(parent::jsonSerialize(), [ + 'default' => $this->default, + 'size' => $this->size, + ]); } public function getTypeName(): string @@ -31,27 +55,8 @@ public function getSize(): int return $this->size; } - public function setSize(int $size): self - { - $this->size = $size; - - return $this; - } - public function getDefault(): ?string { return $this->default; } - - public function setDefault(string $default): void - { - $this->default = $default; - } - - public function asArray(): array - { - return array_merge(parent::asArray(), [ - 'size' => $this->size, - ]); - } } diff --git a/src/Migration/Resources/Database/Attributes/URL.php b/src/Migration/Resources/Database/Attributes/URL.php index 3b76afe..8410800 100644 --- a/src/Migration/Resources/Database/Attributes/URL.php +++ b/src/Migration/Resources/Database/Attributes/URL.php @@ -3,31 +3,9 @@ namespace Utopia\Migration\Resources\Database\Attributes; use Utopia\Migration\Resources\Database\Attribute; -use Utopia\Migration\Resources\Database\Collection; -class URL extends Attribute +class URL extends Text { - protected ?string $default; - - /** - * @param ?string $default - */ - public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?string $default = null) - { - parent::__construct($key, $collection, $required, $array); - $this->default = $default; - } - - public function getDefault(): ?string - { - return $this->default; - } - - public function setDefault(string $default): void - { - $this->default = $default; - } - public function getTypeName(): string { return Attribute::TYPE_URL; diff --git a/src/Migration/Resources/Database/Collection.php b/src/Migration/Resources/Database/Collection.php index 5d03619..b32d0ee 100644 --- a/src/Migration/Resources/Database/Collection.php +++ b/src/Migration/Resources/Database/Collection.php @@ -8,28 +8,48 @@ class Collection extends Resource { /** - * @var list + * @param Database $database + * @param string $name + * @param string $id + * @param bool $documentSecurity + * @param array $permissions */ - private array $columns = []; + public function __construct( + private readonly Database $database, + private readonly string $name, + string $id, + private readonly bool $documentSecurity = false, + array $permissions = [] + ) { + $this->id = $id; + $this->permissions = $permissions; + } /** - * @var list + * @param array $array */ - private array $indexes = []; - - private Database $database; - - protected bool $documentSecurity = false; - - protected string $name; + public static function fromArray(array $array): self + { + return new self( + Database::fromArray($array['database']), + $array['name'], + $array['id'], + $array['documentSecurity'] ?? false, + $array['permissions'] ?? [] + ); + } - public function __construct(Database $database, string $name, string $id, bool $documentSecurity = false, array $permissions = []) + /** + * @return array + */ + public function jsonSerialize(): array { - $this->database = $database; - $this->name = $name; - $this->id = $id; - $this->documentSecurity = $documentSecurity; - $this->permissions = $permissions; + return array_merge([ + 'database' => $this->database, + 'name' => $this->name, + 'documentSecurity' => $this->documentSecurity, + 'permissions' => $this->permissions, + ]); } public static function getName(): string @@ -47,44 +67,13 @@ public function getDatabase(): Database return $this->database; } - public function setDatabase(Database $database): self - { - $this->database = $database; - - return $this; - } - public function getCollectionName(): string { return $this->name; } - public function setCollectionName(string $name): self - { - $this->name = $name; - - return $this; - } - public function getDocumentSecurity(): bool { return $this->documentSecurity; } - - public function setDocumentSecurity(bool $documentSecurity): self - { - $this->documentSecurity = $documentSecurity; - - return $this; - } - - public function asArray(): array - { - return [ - 'name' => $this->name, - 'id' => $this->id, - 'permissions' => $this->permissions, - 'documentSecurity' => $this->documentSecurity, - ]; - } } diff --git a/src/Migration/Resources/Database/Database.php b/src/Migration/Resources/Database/Database.php index 804543d..ab9d435 100644 --- a/src/Migration/Resources/Database/Database.php +++ b/src/Migration/Resources/Database/Database.php @@ -15,17 +15,33 @@ class Database extends Resource { + public function __construct( + string $id = '', + private readonly string $name = '', + ) { + $this->id = $id; + } + /** - * @var list + * @param array $array */ - private array $collections = []; - - protected string $name; + public static function fromArray(array $array): self + { + return new self( + $array['id'], + $array['name'], + ); + } - public function __construct(string $name = '', string $id = '') + /** + * @return array + */ + public function jsonSerialize(): array { - $this->name = $name; - $this->id = $id; + return [ + 'id' => $this->id, + 'name' => $this->name, + ]; } public static function getName(): string @@ -42,33 +58,4 @@ public function getDBName(): string { return $this->name; } - - /** - * @return list - */ - public function getCollections(): array - { - return $this->collections; - } - - /** - * @param list $collections - */ - public function setCollections(array $collections): self - { - $this->collections = $collections; - - return $this; - } - - public function asArray(): array - { - return [ - 'name' => $this->name, - 'id' => $this->id, - 'collections' => array_map(function ($collection) { - return $collection->asArray(); - }, $this->collections), - ]; - } } diff --git a/src/Migration/Resources/Database/Document.php b/src/Migration/Resources/Database/Document.php index 4124bcf..6af9fda 100644 --- a/src/Migration/Resources/Database/Document.php +++ b/src/Migration/Resources/Database/Document.php @@ -7,19 +7,50 @@ class Document extends Resource { - protected Database $database; - - protected Collection $collection; + /** + * @param string $id + * @param Database $database + * @param Collection $collection + * @param array $data + * @param array $permissions + */ + public function __construct( + string $id, + private readonly Database $database, + private readonly Collection $collection, + private readonly array $data = [], + array $permissions = [] + ) { + $this->id = $id; + $this->permissions = $permissions; + } - protected array $data; + /** + * @param array $array + */ + public static function fromArray(array $array): self + { + return new self( + $array['id'], + Database::fromArray($array['database']), + Collection::fromArray($array['collection']), + $array['attributes'], + $array['permissions'] ?? [] + ); + } - public function __construct(string $id, Database $database, Collection $collection, array $data = [], array $permissions = []) + /** + * @return array + */ + public function jsonSerialize(): array { - $this->id = $id; - $this->database = $database; - $this->collection = $collection; - $this->data = $data; - $this->permissions = $permissions; + return [ + 'id' => $this->id, + 'database' => $this->database, + 'collection' => $this->collection, + 'attributes' => $this->data, + 'permissions' => $this->permissions, + ]; } public static function getName(): string @@ -37,50 +68,16 @@ public function getDatabase(): Database return $this->database; } - public function setDatabase(Database $database): self - { - $this->database = $database; - - return $this; - } - public function getCollection(): Collection { return $this->collection; } - public function setCollection(Collection $collection): self - { - $this->collection = $collection; - - return $this; - } - - public function getData(): array - { - return $this->data; - } - /** - * Set Data - * - * @param array $data + * @return array */ - public function setData(array $data): self - { - $this->data = $data; - - return $this; - } - - public function asArray(): array + public function getData(): array { - return [ - 'id' => $this->id, - 'database' => $this->database, - 'collection' => $this->collection, - 'attributes' => $this->data, - 'permissions' => $this->permissions, - ]; + return $this->data; } } diff --git a/src/Migration/Resources/Database/Index.php b/src/Migration/Resources/Database/Index.php index bf0381f..a7a64f8 100644 --- a/src/Migration/Resources/Database/Index.php +++ b/src/Migration/Resources/Database/Index.php @@ -7,33 +7,62 @@ class Index extends Resource { - protected string $key; + public const string TYPE_UNIQUE = 'unique'; - protected string $type; + public const string TYPE_FULLTEXT = 'fulltext'; - protected array $attributes; + public const string TYPE_KEY = 'key'; - protected array $orders; - - protected Collection $collection; - - public const TYPE_UNIQUE = 'unique'; - - public const TYPE_FULLTEXT = 'fulltext'; + /** + * @param string $id + * @param string $key + * @param Collection $collection + * @param string $type + * @param array $attributes + * @param array $lengths + * @param array $orders + */ + public function __construct( + string $id, + private readonly string $key, + private readonly Collection $collection, + private readonly string $type = '', + private readonly array $attributes = [], + private readonly array $lengths = [], + private readonly array $orders = [] + ) { + $this->id = $id; + } - public const TYPE_KEY = 'key'; + /** + * @param array $array + */ + public static function fromArray(array $array): self + { + return new self( + $array['id'], + $array['key'], + Collection::fromArray($array['collection']), + $array['type'] ?? '', + $array['attributes'], + $array['lengths'] ?? [], + $array['orders'] ?? [] + ); + } /** - * @param list $attributes + * @return array */ - public function __construct(string $id, string $key, Collection $collection, string $type = '', array $attributes = [], array $orders = []) + public function jsonSerialize(): array { - $this->id = $id; - $this->key = $key; - $this->type = $type; - $this->attributes = $attributes; - $this->orders = $orders; - $this->collection = $collection; + return [ + 'key' => $this->key, + 'collection' => $this->collection, + 'type' => $this->type, + 'attributes' => $this->attributes, + 'lengths' => $this->lengths, + 'orders' => $this->orders, + ]; } public static function getName(): string @@ -51,71 +80,29 @@ public function getKey(): string return $this->key; } - public function setKey(string $key): self - { - $this->key = $key; - - return $this; - } - public function getCollection(): Collection { return $this->collection; } - public function setCollection(Collection $collection): self - { - $this->collection = $collection; - - return $this; - } - public function getType(): string { return $this->type; } - public function setType(string $type): self - { - $this->type = $type; - - return $this; - } - + /** + * @return array + */ public function getAttributes(): array { return $this->attributes; } /** - * @param list $attributes + * @return array */ - public function setAttributes(array $attributes): self - { - $this->attributes = $attributes; - - return $this; - } - public function getOrders(): array { return $this->orders; } - - public function setOrders(array $orders): self - { - $this->orders = $orders; - - return $this; - } - - public function asArray(): array - { - return [ - 'key' => $this->key, - 'type' => $this->type, - 'attributes' => $this->attributes, - 'orders' => $this->orders, - ]; - } } diff --git a/src/Migration/Resources/Functions/Deployment.php b/src/Migration/Resources/Functions/Deployment.php index 39927c5..3f5b95b 100644 --- a/src/Migration/Resources/Functions/Deployment.php +++ b/src/Migration/Resources/Functions/Deployment.php @@ -7,30 +7,51 @@ class Deployment extends Resource { - protected Func $func; - - protected string $entrypoint; - - protected int $size; - - protected int $start; - - protected int $end; - - protected string $data; + public function __construct( + string $id, + private readonly Func $func, + private readonly int $size, + private readonly string $entrypoint, + private int $start = 0, + private int $end = 0, + private string $data = '', + private readonly bool $activated = false + ) { + $this->id = $id; + } - protected bool $activated; + /** + * @param array $array + * @return self + */ + public static function fromArray(array $array): self + { + return new self( + $array['id'], + Func::fromArray($array['func']), + $array['size'], + $array['entrypoint'], + $array['start'] ?? 0, + $array['end'] ?? 0, + $array['data'] ?? '', + $array['activated'] ?? false + ); + } - public function __construct(string $id, Func $func, int $size, string $entrypoint, int $start = 0, int $end = 0, string $data = '', bool $activated = false) + /** + * @return array + */ + public function jsonSerialize(): array { - $this->id = $id; - $this->func = $func; - $this->size = $size; - $this->entrypoint = $entrypoint; - $this->start = $start; - $this->end = $end; - $this->data = $data; - $this->activated = $activated; + return [ + 'func' => $this->func, + 'size' => $this->size, + 'entrypoint' => $this->entrypoint, + 'start' => $this->start, + 'end' => $this->end, + 'data' => $this->data, + 'activated' => $this->activated, + ]; } public static function getName(): string @@ -48,32 +69,11 @@ public function getFunction(): Func return $this->func; } - public function setFunction(Func $func): self - { - $this->func = $func; - - return $this; - } - - public function setSize(int $size): self - { - $this->size = $size; - - return $this; - } - public function getSize(): int { return $this->size; } - public function setEntrypoint(string $entrypoint): self - { - $this->entrypoint = $entrypoint; - - return $this; - } - public function getEntrypoint(): string { return $this->entrypoint; @@ -115,28 +115,8 @@ public function getData(): string return $this->data; } - public function setActivated(bool $activated): self - { - $this->activated = $activated; - - return $this; - } - public function getActivated(): bool { return $this->activated; } - - public function asArray(): array - { - return [ - 'id' => $this->id, - 'func' => $this->func->asArray(), - 'size' => $this->size, - 'entrypoint' => $this->entrypoint, - 'start' => $this->start, - 'end' => $this->end, - 'activated' => $this->activated, - ]; - } } diff --git a/src/Migration/Resources/Functions/EnvVar.php b/src/Migration/Resources/Functions/EnvVar.php index c2f7b75..0555d16 100644 --- a/src/Migration/Resources/Functions/EnvVar.php +++ b/src/Migration/Resources/Functions/EnvVar.php @@ -7,17 +7,39 @@ class EnvVar extends Resource { - protected Func $func; - - protected string $key; + public function __construct( + string $id, + private readonly Func $func, + private readonly string $key, + private readonly string $value + ) { + $this->id = $id; + } - protected string $value; + /** + * @param array $array + * @return self + */ + public static function fromArray(array $array): self + { + return new self( + $array['id'], + Func::fromArray($array['func']), + $array['key'], + $array['value'] + ); + } - public function __construct(Func $func, string $key, string $value) + /** + * @return array + */ + public function jsonSerialize(): array { - $this->func = $func; - $this->key = $key; - $this->value = $value; + return [ + 'func' => $this->func, + 'key' => $this->key, + 'value' => $this->value, + ]; } public static function getName(): string @@ -35,43 +57,13 @@ public function getFunc(): Func return $this->func; } - public function setFunc(Func $func): self - { - $this->func = $func; - - return $this; - } - public function getKey(): string { return $this->key; } - public function setKey(string $key): self - { - $this->key = $key; - - return $this; - } - public function getValue(): string { return $this->value; } - - public function setValue(string $value): self - { - $this->value = $value; - - return $this; - } - - public function asArray(): array - { - return [ - 'func' => $this->func->getId(), - 'key' => $this->key, - 'value' => $this->value, - ]; - } } diff --git a/src/Migration/Resources/Functions/Func.php b/src/Migration/Resources/Functions/Func.php index 93c62a3..14756d6 100644 --- a/src/Migration/Resources/Functions/Func.php +++ b/src/Migration/Resources/Functions/Func.php @@ -7,33 +7,66 @@ class Func extends Resource { - protected string $name; - - protected array $execute; - - protected bool $enabled; - - protected string $runtime; - - protected array $events; - - protected string $schedule; - - protected int $timeout; + /** + * @param string $name + * @param string $id + * @param string $runtime + * @param array $execute + * @param bool $enabled + * @param array $events + * @param string $schedule + * @param int $timeout + * @param string $activeDeployment + */ + public function __construct( + string $id, + private readonly string $name, + private readonly string $runtime, + private readonly array $execute = [], + private readonly bool $enabled = true, + private readonly array $events = [], + private readonly string $schedule = '', + private readonly int $timeout = 0, + private readonly string $activeDeployment = '' + ) { + $this->id = $id; + } - protected string $activeDeployment; + /** + * @param array $array + * @return self + */ + public static function fromArray(array $array): self + { + return new self( + $array['id'], + $array['name'], + $array['runtime'], + $array['execute'] ?? [], + $array['enabled'] ?? true, + $array['events'] ?? [], + $array['schedule'] ?? '', + $array['timeout'] ?? 0, + $array['activeDeployment'] ?? '' + ); + } - public function __construct(string $name, string $id, string $runtime, array $execute = [], bool $enabled = true, array $events = [], string $schedule = '', int $timeout = 0, string $activeDeployment = '') + /** + * @return array + */ + public function jsonSerialize(): array { - $this->name = $name; - $this->id = $id; - $this->execute = $execute; - $this->enabled = $enabled; - $this->runtime = $runtime; - $this->events = $events; - $this->schedule = $schedule; - $this->timeout = $timeout; - $this->activeDeployment = $activeDeployment; + return [ + 'name' => $this->name, + 'id' => $this->id, + 'execute' => $this->execute, + 'enabled' => $this->enabled, + 'runtime' => $this->runtime, + 'events' => $this->events, + 'schedule' => $this->schedule, + 'timeout' => $this->timeout, + 'activeDeployment' => $this->activeDeployment, + ]; } public static function getName(): string @@ -56,97 +89,33 @@ public function getExecute(): array return $this->execute; } - public function setExecute(array $execute): self - { - $this->execute = $execute; - - return $this; - } - public function getEnabled(): bool { return $this->enabled; } - public function setEnabled(bool $enabled): self - { - $this->enabled = $enabled; - - return $this; - } - public function getRuntime(): string { return $this->runtime; } - public function setRuntime(string $runtime): self - { - $this->runtime = $runtime; - - return $this; - } - public function getEvents(): array { return $this->events; } - public function setEvents(array $events): self - { - $this->events = $events; - - return $this; - } - public function getSchedule(): string { return $this->schedule; } - public function setSchedule(string $schedule): self - { - $this->schedule = $schedule; - - return $this; - } - public function getTimeout(): int { return $this->timeout; } - public function setTimeout(int $timeout): self - { - $this->timeout = $timeout; - - return $this; - } - public function getActiveDeployment(): string { return $this->activeDeployment; } - - public function setActiveDeployment(string $activeDeployment): self - { - $this->activeDeployment = $activeDeployment; - - return $this; - } - - public function asArray(): array - { - return [ - 'name' => $this->name, - 'id' => $this->id, - 'execute' => $this->execute, - 'enabled' => $this->enabled, - 'runtime' => $this->runtime, - 'events' => $this->events, - 'schedule' => $this->schedule, - 'timeout' => $this->timeout, - 'activeDeployment' => $this->activeDeployment, - ]; - } } diff --git a/src/Migration/Resources/Storage/Bucket.php b/src/Migration/Resources/Storage/Bucket.php index 6d26f00..72916ef 100644 --- a/src/Migration/Resources/Storage/Bucket.php +++ b/src/Migration/Resources/Storage/Bucket.php @@ -7,36 +7,72 @@ class Bucket extends Resource { - protected ?bool $fileSecurity; - - protected string $name; - - protected ?bool $enabled; - - protected ?int $maxFileSize; - - protected ?array $allowedFileExtensions; - - protected ?string $compression; - - protected ?bool $encryption; - - protected ?bool $antiVirus; - - protected bool $updateLimits = false; - - public function __construct(string $id = '', string $name = '', array $permissions = [], bool $fileSecurity = false, bool $enabled = false, ?int $maxFileSize = null, array $allowedFileExtensions = [], string $compression = 'none', bool $encryption = false, bool $antiVirus = false, bool $updateLimits = false) - { + /** + * @param string $id + * @param string $name + * @param array $permissions + * @param bool $fileSecurity + * @param bool $enabled + * @param int|null $maxFileSize + * @param array $allowedFileExtensions + * @param string $compression + * @param bool $encryption + * @param bool $antiVirus + */ + public function __construct( + string $id = '', + private readonly string $name = '', + array $permissions = [], + private readonly bool $fileSecurity = false, + private readonly bool $enabled = false, + private readonly ?int $maxFileSize = null, + private readonly array $allowedFileExtensions = [], + private readonly string $compression = 'none', + private readonly bool $encryption = false, + private readonly bool $antiVirus = false, + ) { $this->id = $id; - $this->name = $name; $this->permissions = $permissions; - $this->fileSecurity = $fileSecurity; - $this->enabled = $enabled; - $this->maxFileSize = $maxFileSize; - $this->allowedFileExtensions = $allowedFileExtensions; - $this->compression = $compression; - $this->encryption = $encryption; - $this->antiVirus = $antiVirus; + } + + /** + * @param array $array + * @return self + */ + public static function fromArray(array $array): self + { + return new self( + $array['id'], + $array['name'] ?? '', + $array['permissions'] ?? [], + $array['fileSecurity'] ?? false, + $array['enabled'] ?? false, + $array['maxFileSize'] ?? null, + $array['allowedFileExtensions'] ?? [], + $array['compression'] ?? 'none', + $array['encryption'] ?? false, + $array['antiVirus'] ?? false, + $array['updateLimits'] ?? false + ); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'id' => $this->id, + 'name' => $this->name, + 'fileSecurity' => $this->fileSecurity, + 'enabled' => $this->enabled, + 'maxFileSize' => $this->maxFileSize, + 'allowedFileExtensions' => $this->allowedFileExtensions, + 'compression' => $this->compression, + 'encryption' => $this->encryption, + 'antiVirus' => $this->antiVirus, + 'updateLimits' => $this->updateLimits, + ]; } public static function getName(): string @@ -54,122 +90,40 @@ public function getFileSecurity(): bool return $this->fileSecurity; } - public function setFileSecurity(bool $fileSecurity): self - { - $this->fileSecurity = $fileSecurity; - - return $this; - } - public function getBucketName(): string { return $this->name; } - public function setBucketName(string $name): self - { - $this->name = $name; - - return $this; - } public function getEnabled(): bool { return $this->enabled; } - public function setEnabled(bool $enabled): self - { - $this->enabled = $enabled; - - return $this; - } - public function getMaxFileSize(): ?int { return $this->maxFileSize; } - public function setMaxFileSize(?int $maxFileSize): self - { - $this->maxFileSize = $maxFileSize; - - return $this; - } public function getAllowedFileExtensions(): array { return $this->allowedFileExtensions; } - public function setAllowedFileExtensions(array $allowedFileExtensions): self - { - $this->allowedFileExtensions = $allowedFileExtensions; - - return $this; - } - public function getCompression(): string { return $this->compression; } - public function setCompression(string $compression): self - { - $this->compression = $compression; - - return $this; - } - public function getEncryption(): bool { return $this->encryption; } - public function setEncryption(bool $encryption): self - { - $this->encryption = $encryption; - - return $this; - } - public function getAntiVirus(): bool { return $this->antiVirus; } - - public function setAntiVirus(bool $antiVirus): self - { - $this->antiVirus = $antiVirus; - - return $this; - } - - public function getUpdateLimits(): bool - { - return $this->updateLimits; - } - - public function setUpdateLimits(bool $updateLimits): self - { - $this->updateLimits = $updateLimits; - - return $this; - } - - public function asArray(): array - { - return [ - 'id' => $this->id, - 'permissions' => $this->permissions, - 'fileSecurity' => $this->fileSecurity, - 'name' => $this->name, - 'enabled' => $this->enabled, - 'maxFileSize' => $this->maxFileSize, - 'allowedFileExtensions' => $this->allowedFileExtensions, - 'compression' => $this->compression, - 'encryption' => $this->encryption, - 'antiVirus' => $this->antiVirus, - ]; - } } diff --git a/src/Migration/Resources/Storage/File.php b/src/Migration/Resources/Storage/File.php index 99914e7..d9bd64b 100644 --- a/src/Migration/Resources/Storage/File.php +++ b/src/Migration/Resources/Storage/File.php @@ -7,34 +7,70 @@ class File extends Resource { - protected Bucket $bucket; - - protected string $name; - - protected string $signature; - - protected string $mimeType; - - protected int $size; - - protected string $data; - - protected int $start; + /** + * @param string $id + * @param Bucket|null $bucket + * @param string $name + * @param string $signature + * @param string $mimeType + * @param array $permissions + * @param int $size + * @param string $data + * @param int $start + * @param int $end + */ + public function __construct( + string $id, + private readonly ?Bucket $bucket = null, + private readonly string $name = '', + private readonly string $signature = '', + private readonly string $mimeType = '', + array $permissions = [], + private readonly int $size = 0, + private string $data = '', + private int $start = 0, + private int $end = 0 + ) { + $this->id = $id; + $this->permissions = $permissions; + } - protected int $end; + /** + * @param array $array + * @return self + */ + public static function fromArray(array $array): self + { + return new self( + $array['id'], + Bucket::fromArray($array['bucket']), + $array['name'] ?? '', + $array['signature'] ?? '', + $array['mimeType'] ?? '', + $array['permissions'] ?? [], + $array['size'] ?? 0, + $array['data'] ?? '', + $array['start'] ?? 0, + $array['end'] ?? 0 + ); + } - public function __construct(string $id = '', ?Bucket $bucket = null, string $name = '', string $signature = '', string $mimeType = '', array $permissions = [], int $size = 0, string $data = '', int $start = 0, int $end = 0) + /** + * @return array + */ + public function jsonSerialize(): array { - $this->id = $id; - $this->bucket = $bucket; - $this->name = $name; - $this->signature = $signature; - $this->mimeType = $mimeType; - $this->permissions = $permissions; - $this->size = $size; - $this->data = $data; - $this->start = $start; - $this->end = $end; + return [ + 'id' => $this->id, + 'bucket' => $this->bucket, + 'name' => $this->name, + 'signature' => $this->signature, + 'mimeType' => $this->mimeType, + 'permissions' => $this->permissions, + 'size' => $this->size, + 'start' => $this->start, + 'end' => $this->end, + ]; } public static function getName(): string @@ -52,25 +88,11 @@ public function getBucket(): Bucket return $this->bucket; } - public function setBucket(Bucket $bucket): self - { - $this->bucket = $bucket; - - return $this; - } - public function getFileName(): string { return $this->name; } - public function setName(string $name): self - { - $this->name = $name; - - return $this; - } - public function getSize(): int { return $this->size; @@ -81,25 +103,11 @@ public function getSignature(): string return $this->signature; } - public function setSignature(string $signature): self - { - $this->signature = $signature; - - return $this; - } - public function getMimeType(): string { return $this->mimeType; } - public function setMimeType(string $mimeType): self - { - $this->mimeType = $mimeType; - - return $this; - } - public function getData(): string { return $this->data; @@ -145,19 +153,4 @@ public function getChunkSize(): int { return $this->end - $this->start; } - - public function asArray(): array - { - return [ - 'id' => $this->id, - 'bucket' => $this->bucket->getId(), - 'name' => $this->name, - 'signature' => $this->signature, - 'mimeType' => $this->mimeType, - 'permissions' => $this->permissions, - 'size' => $this->size, - 'start' => $this->start, - 'end' => $this->end, - ]; - } } From d6be56c7408fa5a427763e1dec8dd0996edb1c57 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 25 May 2024 03:29:18 +1200 Subject: [PATCH 033/185] Add pint rules --- pint.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 pint.json diff --git a/pint.json b/pint.json new file mode 100644 index 0000000..41457b3 --- /dev/null +++ b/pint.json @@ -0,0 +1,19 @@ +{ + "preset": "psr12", + "exclude": [ + "./tests/resources" + ], + "rules": { + "array_indentation": true, + "single_import_per_statement": true, + "simplified_null_return": true, + "ordered_imports": { + "sort_algorithm": "alpha", + "imports_order": [ + "const", + "class", + "function" + ] + } + } +} From 52f48b9094a7a0312748925e330baf0c78f48217 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 25 May 2024 03:29:46 +1200 Subject: [PATCH 034/185] Update to PHP 8.3 --- composer.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 2d8f63b..9fb4546 100644 --- a/composer.json +++ b/composer.json @@ -18,8 +18,8 @@ "format": "./vendor/bin/pint" }, "require": { - "php": "8.*", - "appwrite/appwrite": "10.1.0" + "php": "8.3", + "appwrite/appwrite": "10.1.*", "utopia-php/cli": "0.15.*", "utopia-php/database": "0.49.*", "utopia-php/storage": "0.18.*", @@ -30,5 +30,8 @@ "phpunit/phpunit": "9.*", "vlucas/phpdotenv": "5.*", "laravel/pint": "1.*" + }, + "platform": { + "php": "8.3" } } From af18587cdeae010dd4e45ba5648ed3c2a2a9fdb5 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 25 May 2024 03:30:01 +1200 Subject: [PATCH 035/185] Add extension refs --- composer.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/composer.json b/composer.json index 9fb4546..f06615a 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,8 @@ }, "require": { "php": "8.3", + "ext-curl": "*", + "ext-openssl": "*", "appwrite/appwrite": "10.1.*", "utopia-php/cli": "0.15.*", "utopia-php/database": "0.49.*", From fe480c8a55143e87af2d293492975e7c06547667 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 25 May 2024 03:30:13 +1200 Subject: [PATCH 036/185] Add PHPStan --- composer.json | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index f06615a..d479ccc 100644 --- a/composer.json +++ b/composer.json @@ -14,8 +14,10 @@ "psr-4": {"Utopia\\Tests\\": "tests/Migration"} }, "scripts": { + "test": "./vendor/bin/phpunit", "lint": "./vendor/bin/pint --test", - "format": "./vendor/bin/pint" + "format": "./vendor/bin/pint", + "check": "./vendor/bin/phpstan analyse --level=8 src/Migration" }, "require": { "php": "8.3", @@ -29,9 +31,10 @@ "utopia-php/framework": "0.33.*" }, "require-dev": { - "phpunit/phpunit": "9.*", - "vlucas/phpdotenv": "5.*", - "laravel/pint": "1.*" + "phpunit/phpunit": "10.5.*", + "vlucas/phpdotenv": "5.6.*", + "laravel/pint": "1.12.*", + "phpstan/phpstan": "^1.11" }, "platform": { "php": "8.3" From 9017a1e36f40403afe6ff40e755174f6306dc4ad Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 25 May 2024 03:30:27 +1200 Subject: [PATCH 037/185] Update docker --- Dockerfile | 19 +++++++++++++++---- docker-compose.yml | 4 +--- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index f62c026..a5a5269 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,24 +1,35 @@ FROM supabase/postgres:15.1.0.96 as supabase-db + COPY tests/Migration/resources/supabase/1_globals.sql /docker-entrypoint-initdb.d/1_globals.sql COPY tests/Migration/resources/supabase/2_main.sql /docker-entrypoint-initdb.d/2_main.sql + RUN rm -rf /docker-entrypoint-initdb.d/migrate.sh FROM postgres:alpine3.18 as nhost-db + COPY tests/Migration/resources/nhost/1_globals.sql /docker-entrypoint-initdb.d/1_globals.sql COPY tests/Migration/resources/nhost/2_main.sql /docker-entrypoint-initdb.d/2_main.sql FROM composer:2.0 as composer -WORKDIR /usr/local/src/ -COPY composer.json /usr/local/src/ + +COPY composer.json /app +COPY composer.lock /app + RUN composer install --ignore-platform-reqs -FROM php:8.1.21-fpm-alpine3.18 as tests +FROM php:8.3.3-cli-alpine3.19 as tests + # Postgres RUN set -ex \ && apk --no-cache add postgresql-libs postgresql-dev \ && docker-php-ext-install pdo pdo_pgsql \ && apk del postgresql-dev + COPY ./src /app/src COPY ./tests /app/src/tests -COPY --from=composer /usr/local/src/vendor /app/vendor + +COPY --from=composer /app/vendor /app/vendor + +WORKDIR /app + CMD tail -f /dev/null diff --git a/docker-compose.yml b/docker-compose.yml index a151232..ddb4b2e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,14 +46,12 @@ services: tests: build: context: . - target: tests networks: - tests volumes: - - ./tests:/app/tests - ./src:/app/src + - ./tests:/app/tests - ./phpunit.xml:/app/phpunit.xml - working_dir: /app depends_on: - supabase-db - nhost-db From 56b9f447a111d289c7cc377bf8befc76852999ca Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 25 May 2024 03:30:37 +1200 Subject: [PATCH 038/185] Migrate phpunit --- phpunit.xml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index 9a35000..acf2d6f 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,13 +1,11 @@ - + + ./tests/Migration/E2E From 1b0feb4769d4fdc99c9b2d785587e91119ad510d Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 25 May 2024 03:34:26 +1200 Subject: [PATCH 039/185] Stan fixes --- bin/MigrationCLI.php | 4 +- src/Migration/Cache.php | 41 +- src/Migration/Destination.php | 10 +- src/Migration/Destinations/Appwrite.php | 484 +++++++++++------- src/Migration/Destinations/Local.php | 75 +-- src/Migration/Exception.php | 9 +- src/Migration/Resource.php | 52 +- src/Migration/Source.php | 26 +- src/Migration/Sources/Appwrite.php | 493 ++++++++++--------- src/Migration/Sources/Firebase.php | 57 ++- src/Migration/Sources/NHost.php | 54 +- src/Migration/Sources/Supabase.php | 34 +- src/Migration/Target.php | 112 +++-- src/Migration/Transfer.php | 99 ++-- tests/Migration/E2E/Sources/SupabaseTest.php | 3 +- 15 files changed, 887 insertions(+), 666 deletions(-) diff --git a/bin/MigrationCLI.php b/bin/MigrationCLI.php index 891b92b..1db5bdf 100644 --- a/bin/MigrationCLI.php +++ b/bin/MigrationCLI.php @@ -17,7 +17,7 @@ class MigrationCLI /** * Prints the current status of migrations as a table after wiping the screen */ - public function drawFrame() + public function drawFrame(): void { echo chr(27).chr(91).'H'.chr(27).chr(91).'J'; @@ -31,7 +31,7 @@ public function drawFrame() } } - public function start() + public function start(): void { $dotenv = Dotenv::createImmutable(__DIR__); $dotenv->load(); diff --git a/src/Migration/Cache.php b/src/Migration/Cache.php index cc0ce77..86ab231 100644 --- a/src/Migration/Cache.php +++ b/src/Migration/Cache.php @@ -2,6 +2,7 @@ namespace Utopia\Migration; +use Utopia\Migration\Resources\Functions\Deployment; use Utopia\Migration\Resources\Storage\File; /** @@ -11,7 +12,10 @@ */ class Cache { - protected $cache = []; + /** + * @var array> $cache + */ + protected array $cache = []; public function __construct() { @@ -23,10 +27,8 @@ public function __construct() * * Places the resource in the cache, in the cache backend this also gets assigned a unique ID. * - * @param resource $resource - * @return void */ - public function add($resource) + public function add(Resource $resource): void { if (! $resource->getInternalId()) { $resourceId = uniqid(); @@ -47,10 +49,10 @@ public function add($resource) /** * Add All Resources * - * @param resource[] $resources + * @param array $resources * @return void */ - public function addAll(array $resources) + public function addAll(array $resources): void { foreach ($resources as $resource) { $this->add($resource); @@ -63,10 +65,10 @@ public function addAll(array $resources) * Updates the resource in the cache, if the resource does not exist in the cache an exception is thrown. * Use Add to add a new resource to the cache. * - * @param resource $resource + * @param Resource $resource * @return void */ - public function update($resource) + public function update(Resource $resource): void { if (! in_array($resource->getName(), $this->cache)) { $this->add($resource); @@ -75,7 +77,11 @@ public function update($resource) $this->cache[$resource->getName()][$resource->getInternalId()] = $resource; } - public function updateAll($resources) + /** + * @param array $resources + * @return void + */ + public function updateAll(array $resources): void { foreach ($resources as $resource) { $this->update($resource); @@ -87,10 +93,11 @@ public function updateAll($resources) * * Removes the resource from the cache, if the resource does not exist in the cache an exception is thrown. * - * @param resource $resource + * @param Resource $resource * @return void + * @throws \Exception */ - public function remove($resource) + public function remove(Resource $resource): void { if (! in_array($resource, $this->cache[$resource->getName()])) { throw new \Exception('Resource does not exist in cache'); @@ -102,10 +109,10 @@ public function remove($resource) /** * Get Resources * - * @param string|resource $resourceType - * @return resource[] + * @param string|Resource $resource + * @return array */ - public function get($resource) + public function get(string|Resource $resource): array { if (is_string($resource)) { return $this->cache[$resource] ?? []; @@ -117,9 +124,9 @@ public function get($resource) /** * Get All Resources * - * @return array + * @return array> */ - public function getAll() + public function getAll(): array { return $this->cache; } @@ -131,7 +138,7 @@ public function getAll() * * @return void */ - public function wipe() + public function wipe(): void { $this->cache = []; } diff --git a/src/Migration/Destination.php b/src/Migration/Destination.php index 69809fb..f2205a9 100644 --- a/src/Migration/Destination.php +++ b/src/Migration/Destination.php @@ -9,17 +9,11 @@ abstract class Destination extends Target */ protected Source $source; - /** - * Get Source - */ public function getSource(): Source { return $this->source; } - /** - * Set Soruce - */ public function setSource(Source $source): self { $this->source = $source; @@ -30,7 +24,7 @@ public function setSource(Source $source): self /** * Transfer Resources to Destination from Source callback * - * @param string[] $resources Resources to transfer + * @param array $resources Resources to transfer * @param callable $callback Callback to run after transfer */ public function run(array $resources, callable $callback): void @@ -58,7 +52,7 @@ public function init(): void /** * shutDown function */ - public function shutDown(): void + public function shutdown(): void { } } diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 9ef746b..41733d5 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -2,6 +2,7 @@ namespace Utopia\Migration\Destinations; +use Appwrite\AppwriteException; use Appwrite\Client; use Appwrite\InputFile; use Appwrite\Services\Databases; @@ -9,6 +10,7 @@ use Appwrite\Services\Storage; use Appwrite\Services\Teams; use Appwrite\Services\Users; +use Override; use Utopia\Migration\Destination; use Utopia\Migration\Exception; use Utopia\Migration\Resource; @@ -17,6 +19,7 @@ use Utopia\Migration\Resources\Auth\Team; use Utopia\Migration\Resources\Auth\User; use Utopia\Migration\Resources\Database\Attribute; +use Utopia\Migration\Resources\Database\Attributes\Boolean; use Utopia\Migration\Resources\Database\Attributes\DateTime; use Utopia\Migration\Resources\Database\Attributes\Decimal; use Utopia\Migration\Resources\Database\Attributes\Email; @@ -24,25 +27,32 @@ use Utopia\Migration\Resources\Database\Attributes\IP; use Utopia\Migration\Resources\Database\Attributes\Relationship; use Utopia\Migration\Resources\Database\Attributes\Text; +use Utopia\Migration\Resources\Database\Attributes\URL; use Utopia\Migration\Resources\Database\Collection; use Utopia\Migration\Resources\Database\Database; use Utopia\Migration\Resources\Database\Document; +use Utopia\Migration\Resources\Database\Index; use Utopia\Migration\Resources\Functions\Deployment; use Utopia\Migration\Resources\Functions\EnvVar; use Utopia\Migration\Resources\Functions\Func; use Utopia\Migration\Resources\Storage\Bucket; use Utopia\Migration\Resources\Storage\File; -use Utopia\Migration\Resources\Storage\Index; use Utopia\Migration\Transfer; class Appwrite extends Destination { protected Client $client; - protected string $project; protected string $key; + private Databases $databases; + private Functions $functions; + private Storage $storage; + private Teams $teams; + private Users $users; + + public function __construct(string $project, string $endpoint, string $key) { $this->project = $project; @@ -53,6 +63,12 @@ public function __construct(string $project, string $endpoint, string $key) ->setEndpoint($endpoint) ->setProject($project) ->setKey($key); + + $this->databases = new Databases($this->client); + $this->functions = new Functions($this->client); + $this->storage = new Storage($this->client); + $this->teams = new Teams($this->client); + $this->users = new Users($this->client); } public static function getName(): string @@ -60,6 +76,9 @@ public static function getName(): string return 'Appwrite'; } + /** + * @return array + */ public static function getSupportedResources(): array { return [ @@ -83,130 +102,134 @@ public static function getSupportedResources(): array Resource::TYPE_FUNCTION, Resource::TYPE_DEPLOYMENT, Resource::TYPE_ENVIRONMENT_VARIABLE, - - // Settings ]; } + /** + * @param array $resources + * @throws AppwriteException + * @throws \Exception + */ + #[Override] public function report(array $resources = []): array { if (empty($resources)) { $resources = $this->getSupportedResources(); } - $databases = new Databases($this->client); - $functions = new Functions($this->client); - $storage = new Storage($this->client); - $teams = new Teams($this->client); - $users = new Users($this->client); + $scope = ''; - $currentPermission = ''; // Most of these API calls are purposely wrong. Appwrite will throw a 403 before a 400. // We want to make sure the API key has full read and write access to the project. - try { // Auth - if (in_array(Resource::TYPE_USER, $resources)) { - $currentPermission = 'users.read'; - $users->list(); + if (\in_array(Resource::TYPE_USER, $resources)) { + $scope = 'users.read'; + $this->users->list(); - $currentPermission = 'users.write'; - $users->create('', '', ''); + $scope = 'users.write'; + $this->users->create(''); } - if (in_array(Resource::TYPE_TEAM, $resources)) { - $currentPermission = 'teams.read'; - $teams->list(); + if (\in_array(Resource::TYPE_TEAM, $resources)) { + $scope = 'teams.read'; + $this->teams->list(); - $currentPermission = 'teams.write'; - $teams->create('', ''); + $scope = 'teams.write'; + $this->teams->create('', ''); } - if (in_array(Resource::TYPE_MEMBERSHIP, $resources)) { - $currentPermission = 'memberships.read'; - $teams->listMemberships(''); + if (\in_array(Resource::TYPE_MEMBERSHIP, $resources)) { + $scope = 'memberships.read'; + $this->teams->listMemberships(''); - $currentPermission = 'memberships.write'; - $teams->createMembership('', [], ''); + $scope = 'memberships.write'; + $this->teams->createMembership('', [], ''); } // Database - if (in_array(Resource::TYPE_DATABASE, $resources)) { - $currentPermission = 'database.read'; - $databases->list(); + if (\in_array(Resource::TYPE_DATABASE, $resources)) { + $scope = 'database.read'; + $this->databases->list(); - $currentPermission = 'database.write'; - $databases->create('', ''); + $scope = 'database.write'; + $this->databases->create('', ''); } - if (in_array(Resource::TYPE_COLLECTION, $resources)) { - $currentPermission = 'collections.read'; - $databases->listCollections(''); + if (\in_array(Resource::TYPE_COLLECTION, $resources)) { + $scope = 'collections.read'; + $this->databases->listCollections(''); - $currentPermission = 'collections.write'; - $databases->createCollection('', '', ''); + $scope = 'collections.write'; + $this->databases->createCollection('', '', ''); } - if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) { - $currentPermission = 'attributes.read'; - $databases->listAttributes('', ''); + if (\in_array(Resource::TYPE_ATTRIBUTE, $resources)) { + $scope = 'attributes.read'; + $this->databases->listAttributes('', ''); - $currentPermission = 'attributes.write'; - $databases->createStringAttribute('', '', '', 0, false); + $scope = 'attributes.write'; + $this->databases->createStringAttribute('', '', '', 0, false); } - if (in_array(Resource::TYPE_INDEX, $resources)) { - $currentPermission = 'indexes.read'; - $databases->listIndexes('', ''); + if (\in_array(Resource::TYPE_INDEX, $resources)) { + $scope = 'indexes.read'; + $this->databases->listIndexes('', ''); - $currentPermission = 'indexes.write'; - $databases->createIndex('', '', '', '', []); + $scope = 'indexes.write'; + $this->databases->createIndex('', '', '', '', []); } - if (in_array(Resource::TYPE_DOCUMENT, $resources)) { - $currentPermission = 'documents.read'; - $databases->listDocuments('', ''); + if (\in_array(Resource::TYPE_DOCUMENT, $resources)) { + $scope = 'documents.read'; + $this->databases->listDocuments('', ''); - $currentPermission = 'documents.write'; - $databases->createDocument('', '', '', []); + $scope = 'documents.write'; + $this->databases->createDocument('', '', '', []); } // Storage - if (in_array(Resource::TYPE_BUCKET, $resources)) { - $currentPermission = 'storage.read'; - $storage->listBuckets(); + if (\in_array(Resource::TYPE_BUCKET, $resources)) { + $scope = 'storage.read'; + $this->storage->listBuckets(); - $currentPermission = 'storage.write'; - $storage->createBucket('', ''); + $scope = 'storage.write'; + $this->storage->createBucket('', ''); } - if (in_array(Resource::TYPE_FILE, $resources)) { - $currentPermission = 'files.read'; - $storage->listFiles(''); + if (\in_array(Resource::TYPE_FILE, $resources)) { + $scope = 'files.read'; + $this->storage->listFiles(''); - $currentPermission = 'files.write'; - $storage->createFile('', '', new InputFile()); + $scope = 'files.write'; + $this->storage->createFile('', '', new InputFile()); } // Functions - if (in_array(Resource::TYPE_FUNCTION, $resources)) { - $currentPermission = 'functions.read'; - $functions->list(); + if (\in_array(Resource::TYPE_FUNCTION, $resources)) { + $scope = 'functions.read'; + $this->functions->list(); - $currentPermission = 'functions.write'; - $functions->create('', '', ''); + $scope = 'functions.write'; + $this->functions->create('', '', ''); } - return []; - } catch (\Throwable $exception) { - if ($exception->getCode() === 403) { - throw new \Exception('Missing permission: '.$currentPermission); - } else { - throw $exception; + } catch (AppwriteException $e) { + if ($e->getCode() === 403) { + throw new \Exception('Missing scope: ' . $scope, previous: $e); } + throw $e; } + + return []; } + /** + * @param array $resources + * @param callable $callback + * @return void + */ + #[Override] protected function import(array $resources, callable $callback): void { if (empty($resources)) { @@ -214,34 +237,27 @@ protected function import(array $resources, callable $callback): void } foreach ($resources as $resource) { - /** @var resource $resource */ $resource->setStatus(Resource::STATUS_PROCESSING); try { - switch ($resource->getGroup()) { - case Transfer::GROUP_DATABASES: - $responseResource = $this->importDatabaseResource($resource); - break; - case Transfer::GROUP_STORAGE: - $responseResource = $this->importFileResource($resource); - break; - case Transfer::GROUP_AUTH: - $responseResource = $this->importAuthResource($resource); - break; - case Transfer::GROUP_FUNCTIONS: - $responseResource = $this->importFunctionResource($resource); - break; - } + $responseResource = match ($resource->getGroup()) { + Transfer::GROUP_DATABASES => $this->importDatabaseResource($resource), + Transfer::GROUP_STORAGE => $this->importFileResource($resource), + Transfer::GROUP_AUTH => $this->importAuthResource($resource), + Transfer::GROUP_FUNCTIONS => $this->importFunctionResource($resource), + default => throw new \Exception('Invalid resource group'), + }; } catch (\Throwable $e) { if ($e->getCode() === 409) { $resource->setStatus(Resource::STATUS_SKIPPED, $e->getMessage()); } else { $resource->setStatus(Resource::STATUS_ERROR, $e->getMessage()); + $this->addError(new Exception( resourceType: $resource->getGroup(), - resourceId: $resource->getId(), message: $e->getMessage(), - code: $e->getCode() + code: $e->getCode(), + resourceId: $resource->getId() )); } @@ -254,18 +270,24 @@ protected function import(array $resources, callable $callback): void $callback($resources); } + /** + * @throws AppwriteException + */ public function importDatabaseResource(Resource $resource): Resource { - $databaseService = new Databases($this->client); + $this->databases = new Databases($this->client); switch ($resource->getName()) { case Resource::TYPE_DATABASE: /** @var Database $resource */ - $databaseService->create($resource->getId(), $resource->getDBName()); + $this->databases->create( + $resource->getId(), + $resource->getDBName() + ); break; case Resource::TYPE_COLLECTION: /** @var Collection $resource */ - $newCollection = $databaseService->createCollection( + $newCollection = $this->databases->createCollection( $resource->getDatabase()->getId(), $resource->getId(), $resource->getCollectionName(), @@ -276,7 +298,7 @@ public function importDatabaseResource(Resource $resource): Resource break; case Resource::TYPE_INDEX: /** @var Index $resource */ - $databaseService->createIndex( + $this->databases->createIndex( $resource->getCollection()->getDatabase()->getId(), $resource->getCollection()->getId(), $resource->getKey(), @@ -291,16 +313,22 @@ public function importDatabaseResource(Resource $resource): Resource break; case Resource::TYPE_DOCUMENT: /** @var Document $resource */ - // Check if document has already been created by subcollection - $docExists = array_key_exists($resource->getId(), $this->cache->get(Resource::TYPE_DOCUMENT)); + // Check if document has already been created + $exists = \array_key_exists( + $resource->getId(), + $this->cache->get(Resource::TYPE_DOCUMENT) + ); - if ($docExists) { - $resource->setStatus(Resource::STATUS_SKIPPED, 'Document has been already created by relationship'); + if ($exists) { + $resource->setStatus( + Resource::STATUS_SKIPPED, + 'Document has been already created by relationship' + ); return $resource; } - $databaseService->createDocument( + $this->databases->createDocument( $resource->getDatabase()->getId(), $resource->getCollection()->getId(), $resource->getId(), @@ -315,50 +343,121 @@ public function importDatabaseResource(Resource $resource): Resource return $resource; } + /** + * @throws AppwriteException + * @throws \Exception + */ public function createAttribute(Attribute $attribute): void { - $databaseService = new Databases($this->client); - switch ($attribute->getTypeName()) { case Attribute::TYPE_STRING: /** @var Text $attribute */ - $databaseService->createStringAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getSize(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + $this->databases->createStringAttribute( + $attribute->getCollection()->getDatabase()->getId(), + $attribute->getCollection()->getId(), + $attribute->getKey(), + $attribute->getSize(), + $attribute->getRequired(), + $attribute->getDefault(), + $attribute->getArray() + ); break; case Attribute::TYPE_INTEGER: - /** @var int $attribute */ - $databaseService->createIntegerAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getMin(), $attribute->getMax() ?? null, $attribute->getDefault(), $attribute->getArray()); + /** @var \Utopia\Migration\Resources\Database\Attributes\Integer $attribute */ + $this->databases->createIntegerAttribute( + $attribute->getCollection()->getDatabase()->getId(), + $attribute->getCollection()->getId(), + $attribute->getKey(), + $attribute->getRequired(), + $attribute->getMin(), + $attribute->getMax(), + $attribute->getDefault(), + $attribute->getArray() + ); break; case Attribute::TYPE_FLOAT: /** @var Decimal $attribute */ - $databaseService->createFloatAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), null, null, $attribute->getDefault(), $attribute->getArray()); + $this->databases->createFloatAttribute( + $attribute->getCollection()->getDatabase()->getId(), + $attribute->getCollection()->getId(), + $attribute->getKey(), + $attribute->getRequired(), + $attribute->getMin(), + $attribute->getMax(), + $attribute->getDefault(), + $attribute->getArray() + ); break; case Attribute::TYPE_BOOLEAN: - /** @var bool $attribute */ - $databaseService->createBooleanAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + /** @var Boolean $attribute */ + $this->databases->createBooleanAttribute( + $attribute->getCollection()->getDatabase()->getId(), + $attribute->getCollection()->getId(), + $attribute->getKey(), + $attribute->getRequired(), + $attribute->getDefault(), + $attribute->getArray() + ); break; case Attribute::TYPE_DATETIME: /** @var DateTime $attribute */ - $databaseService->createDatetimeAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + $this->databases->createDatetimeAttribute( + $attribute->getCollection()->getDatabase()->getId(), + $attribute->getCollection()->getId(), + $attribute->getKey(), + $attribute->getRequired(), + $attribute->getDefault(), + $attribute->getArray() + ); break; case Attribute::TYPE_EMAIL: /** @var Email $attribute */ - $databaseService->createEmailAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + $this->databases->createEmailAttribute( + $attribute->getCollection()->getDatabase()->getId(), + $attribute->getCollection()->getId(), + $attribute->getKey(), + $attribute->getRequired(), + $attribute->getDefault(), + $attribute->getArray() + ); break; case Attribute::TYPE_IP: /** @var IP $attribute */ - $databaseService->createIpAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + $this->databases->createIpAttribute( + $attribute->getCollection()->getDatabase()->getId(), + $attribute->getCollection()->getId(), + $attribute->getKey(), + $attribute->getRequired(), + $attribute->getDefault(), + $attribute->getArray() + ); break; case Attribute::TYPE_URL: - /** @var URLAttribute $attribute */ - $databaseService->createUrlAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + /** @var URL $attribute */ + $this->databases->createUrlAttribute( + $attribute->getCollection()->getDatabase()->getId(), + $attribute->getCollection()->getId(), + $attribute->getKey(), + $attribute->getRequired(), + $attribute->getDefault(), + $attribute->getArray() + ); break; case Attribute::TYPE_ENUM: /** @var Enum $attribute */ - $databaseService->createEnumAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getElements(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + $this->databases->createEnumAttribute( + $attribute->getCollection()->getDatabase()->getId(), + $attribute->getCollection()->getId(), + $attribute->getKey(), + $attribute->getElements(), + $attribute->getRequired(), + $attribute->getDefault(), + $attribute->getArray() + ); break; case Attribute::TYPE_RELATIONSHIP: /** @var Relationship $attribute */ - $databaseService->createRelationshipAttribute( + $this->databases->createRelationshipAttribute( $attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getRelatedCollection(), @@ -379,15 +478,14 @@ public function createAttribute(Attribute $attribute): void /** * Await Attribute Creation + * @throws \Exception */ public function awaitAttributeCreation(Attribute $attribute, int $timeout): bool { - $databaseService = new Databases($this->client); - $start = \time(); while (\time() - $start < $timeout) { - $response = $databaseService->getAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey()); + $response = $this->databases->getAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey()); if ($response['status'] === 'available') { return true; @@ -399,46 +497,30 @@ public function awaitAttributeCreation(Attribute $attribute, int $timeout): bool throw new \Exception('Attribute creation timeout'); } - public function importFileResource(File|Bucket $resource): Resource + /** + * @throws AppwriteException + */ + public function importFileResource(Resource $resource): Resource { - $storageService = new Storage($this->client); - - $response = null; - switch ($resource->getName()) { case Resource::TYPE_FILE: /** @var File $resource */ return $this->importFile($resource); - break; case Resource::TYPE_BUCKET: /** @var Bucket $resource */ - if (! $resource->getUpdateLimits()) { - $response = $storageService->createBucket( - $resource->getId() ?? 'unique()', - $resource->getBucketName(), - $resource->getPermissions(), - $resource->getFileSecurity(), - true, // Set to true for now, we'll come back later. - null, - null, - $resource->getCompression() ?? 'none', - $resource->getEncryption() ?? null, - $resource->getAntiVirus() ?? null - ); - } else { - $response = $storageService->updateBucket( - $resource->getId(), - $resource->getBucketName(), - $resource->getPermissions(), - $resource->getFileSecurity(), - $resource->getEnabled(), - $resource->getMaxFileSize() ?? null, - $resource->getAllowedFileExtensions() ?? null, - $resource->getCompression() ?? 'none', - $resource->getEncryption() ?? null, - $resource->getAntiVirus() ?? null - ); - } + $response = $this->storage->createBucket( + $resource->getId(), + $resource->getBucketName(), + $resource->getPermissions(), + $resource->getFileSecurity(), + $resource->getEnabled(), + $resource->getMaxFileSize(), + $resource->getAllowedFileExtensions(), + $resource->getCompression(), + $resource->getEncryption(), + $resource->getAntiVirus() + ); + $resource->setId($response['$id']); } @@ -451,6 +533,7 @@ public function importFileResource(File|Bucket $resource): Resource * Import File Data * * @returns File + * @throws AppwriteException */ public function importFile(File $file): File { @@ -486,9 +569,9 @@ public function importFile(File $file): File "/storage/buckets/{$bucketId}/files", [ 'content-type' => 'multipart/form-data', - 'content-range' => 'bytes '.($file->getStart()).'-'.($file->getEnd() == ($file->getSize() - 1) ? $file->getSize() : $file->getEnd()).'/'.$file->getSize(), - 'X-Appwrite-Project' => $this->project, - 'x-Appwrite-Key' => $this->key, + 'content-range' => 'bytes '.($file->getStart()) . '-' . ($file->getEnd() == ($file->getSize() - 1) ? $file->getSize() : $file->getEnd()) . '/' . $file->getSize(), + 'x-appwrite-project' => $this->project, + 'x-appwrite-key' => $this->key, ], [ 'bucketId' => $bucketId, @@ -501,9 +584,9 @@ public function importFile(File $file): File if ($file->getEnd() == ($file->getSize() - 1)) { $file->setStatus(Resource::STATUS_SUCCESS); - // Signatures for Encrypted files are invalid, so we skip the check - if ($file->getBucket()->getEncryption() == false || $file->getSize() > (20 * 1024 * 1024)) { - if ($response['signature'] !== $file->getSignature()) { + // Signatures for encrypted files are invalid, so we skip the check + if (!$file->getBucket()->getEncryption() || $file->getSize() > (20 * 1024 * 1024)) { + if (\is_array($response) && $response['signature'] !== $file->getSignature()) { $file->setStatus(Resource::STATUS_WARNING, 'File signature mismatch, Possibly corrupted.'); } } @@ -514,22 +597,25 @@ public function importFile(File $file): File return $file; } + /** + * @throws AppwriteException + */ public function importAuthResource(Resource $resource): Resource { - $userService = new Users($this->client); - $teamService = new Teams($this->client); - switch ($resource->getName()) { case Resource::TYPE_USER: /** @var User $resource */ - if (in_array(User::TYPE_PASSWORD, $resource->getTypes())) { + if (\in_array(User::TYPE_PASSWORD, $resource->getTypes())) { $this->importPasswordUser($resource); - } elseif (in_array(User::TYPE_OAUTH, $resource->getTypes())) { - $resource->setStatus(Resource::STATUS_WARNING, 'OAuth users cannot be imported.'); + } elseif (\in_array(User::TYPE_OAUTH, $resource->getTypes())) { + $resource->setStatus( + Resource::STATUS_WARNING, + 'OAuth users cannot be imported.' + ); return $resource; } else { - $userService->create( + $this->users->create( $resource->getId(), $resource->getEmail(), in_array(User::TYPE_PHONE, $resource->getTypes()) ? $resource->getPhone() : null, @@ -538,44 +624,48 @@ public function importAuthResource(Resource $resource): Resource ); } - if ($resource->getUsername()) { - $userService->updateName($resource->getId(), $resource->getUsername()); + if (!empty($resource->getUsername())) { + $this->users->updateName($resource->getId(), $resource->getUsername()); } - if ($resource->getPhone()) { - $userService->updatePhone($resource->getId(), $resource->getPhone()); + if (!empty($resource->getPhone())) { + $this->users->updatePhone($resource->getId(), $resource->getPhone()); } if ($resource->getEmailVerified()) { - $userService->updateEmailVerification($resource->getId(), $resource->getEmailVerified()); + $this->users->updateEmailVerification($resource->getId(), true); } if ($resource->getPhoneVerified()) { - $userService->updatePhoneVerification($resource->getId(), $resource->getPhoneVerified()); + $this->users->updatePhoneVerification($resource->getId(), true); } if ($resource->getDisabled()) { - $userService->updateStatus($resource->getId(), ! $resource->getDisabled()); + $this->users->updateStatus($resource->getId(), false); } - if ($resource->getPreferences()) { - $userService->updatePrefs($resource->getId(), $resource->getPreferences()); + if (!empty($resource->getPreferences())) { + $this->users->updatePrefs($resource->getId(), $resource->getPreferences()); } - if ($resource->getLabels()) { - $userService->updateLabels($resource->getId(), $resource->getLabels()); + if (!empty($resource->getLabels())) { + $this->users->updateLabels($resource->getId(), $resource->getLabels()); } break; case Resource::TYPE_TEAM: /** @var Team $resource */ - $teamService->create($resource->getId(), $resource->getTeamName()); - $teamService->updatePrefs($resource->getId(), $resource->getPreferences()); + $this->teams->create($resource->getId(), $resource->getTeamName()); + + if (!empty($resource->getPreferences())) { + $this->teams->updatePrefs($resource->getId(), $resource->getPreferences()); + } break; case Resource::TYPE_MEMBERSHIP: /** @var Membership $resource */ $user = $resource->getUser(); - $teamService->createMembership( + + $this->teams->createMembership( $resource->getTeam()->getId(), $resource->getRoles(), userId: $user->getId(), @@ -588,19 +678,24 @@ public function importAuthResource(Resource $resource): Resource return $resource; } + /** + * @param User $user + * @return array|null + * @throws AppwriteException + * @throws \Exception + */ public function importPasswordUser(User $user): ?array { - $auth = new Users($this->client); $hash = $user->getPasswordHash(); $result = null; - if (! $hash) { + if (!$hash) { throw new \Exception('Password hash is missing'); } switch ($hash->getAlgorithm()) { case Hash::ALGORITHM_SCRYPT_MODIFIED: - $result = $auth->createScryptModifiedUser( + $result = $this->users->createScryptModifiedUser( $user->getId(), $user->getEmail(), $hash->getHash(), @@ -611,7 +706,7 @@ public function importPasswordUser(User $user): ?array ); break; case Hash::ALGORITHM_BCRYPT: - $result = $auth->createBcryptUser( + $result = $this->users->createBcryptUser( $user->getId(), $user->getEmail(), $hash->getHash(), @@ -619,7 +714,7 @@ public function importPasswordUser(User $user): ?array ); break; case Hash::ALGORITHM_ARGON2: - $result = $auth->createArgon2User( + $result = $this->users->createArgon2User( $user->getId(), $user->getEmail(), $hash->getHash(), @@ -627,7 +722,7 @@ public function importPasswordUser(User $user): ?array ); break; case Hash::ALGORITHM_SHA256: - $result = $auth->createShaUser( + $result = $this->users->createShaUser( $user->getId(), $user->getEmail(), $hash->getHash(), @@ -636,7 +731,7 @@ public function importPasswordUser(User $user): ?array ); break; case Hash::ALGORITHM_PHPASS: - $result = $auth->createPHPassUser( + $result = $this->users->createPHPassUser( $user->getId(), $user->getEmail(), $hash->getHash(), @@ -644,7 +739,7 @@ public function importPasswordUser(User $user): ?array ); break; case Hash::ALGORITHM_SCRYPT: - $result = $auth->createScryptUser( + $result = $this->users->createScryptUser( $user->getId(), $user->getEmail(), $hash->getHash(), @@ -657,7 +752,7 @@ public function importPasswordUser(User $user): ?array ); break; case Hash::ALGORITHM_PLAINTEXT: - $result = $auth->create( + $result = $this->users->create( $user->getId(), $user->getEmail(), $user->getPhone(), @@ -670,14 +765,15 @@ public function importPasswordUser(User $user): ?array return $result; } + /** + * @throws AppwriteException + */ public function importFunctionResource(Resource $resource): Resource { - $functions = new Functions($this->client); - switch ($resource->getName()) { case Resource::TYPE_FUNCTION: /** @var Func $resource */ - $functions->create( + $this->functions->create( $resource->getId(), $resource->getFunctionName(), $resource->getRuntime(), @@ -690,15 +786,15 @@ public function importFunctionResource(Resource $resource): Resource break; case Resource::TYPE_ENVIRONMENT_VARIABLE: /** @var EnvVar $resource */ - $functions->createVariable( + $this->functions->createVariable( $resource->getFunc()->getId(), $resource->getKey(), $resource->getValue() ); break; case Resource::TYPE_DEPLOYMENT: + /** @var Deployment $resource */ return $this->importDeployment($resource); - break; } $resource->setStatus(Resource::STATUS_SUCCESS); @@ -706,6 +802,10 @@ public function importFunctionResource(Resource $resource): Resource return $resource; } + /** + * @throws AppwriteException + * @throws \Exception + */ private function importDeployment(Deployment $deployment): Resource { $functionId = $deployment->getFunction()->getId(); @@ -748,6 +848,10 @@ private function importDeployment(Deployment $deployment): Resource ] ); + if (!\is_array($response) || !isset($response['$id'])) { + throw new \Exception('Deployment creation failed'); + } + if ($deployment->getStart() === 0) { $deployment->setId($response['$id']); } diff --git a/src/Migration/Destinations/Local.php b/src/Migration/Destinations/Local.php index ad3e22a..68ea59d 100644 --- a/src/Migration/Destinations/Local.php +++ b/src/Migration/Destinations/Local.php @@ -6,7 +6,6 @@ use Utopia\Migration\Resource; use Utopia\Migration\Resources\Functions\Deployment; use Utopia\Migration\Resources\Storage\File; -use Utopia\Migration\Transfer; /** * Local @@ -16,6 +15,9 @@ */ class Local extends Destination { + /** + * @var array>> + */ private array $data = []; protected string $path; @@ -26,8 +28,8 @@ public function __construct(string $path) if (! \file_exists($this->path)) { mkdir($this->path, 0777, true); - mkdir($this->path.'/files', 0777, true); - mkdir($this->path.'/deployments', 0777, true); + mkdir($this->path . '/files', 0777, true); + mkdir($this->path . '/deployments', 0777, true); } } @@ -36,72 +38,84 @@ public static function getName(): string return 'Local'; } + /** + * @return array + */ public static function getSupportedResources(): array { return [ - Resource::TYPE_ATTRIBUTE, - Resource::TYPE_BUCKET, - Resource::TYPE_COLLECTION, + // Auth + Resource::TYPE_USER, + Resource::TYPE_TEAM, + Resource::TYPE_MEMBERSHIP, + Resource::TYPE_HASH, + + // Database Resource::TYPE_DATABASE, - Resource::TYPE_DEPLOYMENT, + Resource::TYPE_COLLECTION, + Resource::TYPE_ATTRIBUTE, + Resource::TYPE_INDEX, Resource::TYPE_DOCUMENT, - Resource::TYPE_ENVIRONMENT_VARIABLE, + + // Storage + Resource::TYPE_BUCKET, Resource::TYPE_FILE, + + // Functions Resource::TYPE_FUNCTION, - Resource::TYPE_HASH, - Resource::TYPE_INDEX, - Resource::TYPE_TEAM, - Resource::TYPE_MEMBERSHIP, - Resource::TYPE_USER, + Resource::TYPE_DEPLOYMENT, + Resource::TYPE_ENVIRONMENT_VARIABLE, ]; } + /** + * @throws \Exception + */ public function report(array $resources = []): array { $report = []; - if (empty($resources)) { - $resources = $this->getSupportedResources(); - } - - // Check we can write to the file - if (! \is_writable($this->path.'/backup.json')) { - $report[Transfer::GROUP_DATABASES][] = 'Unable to write to file: '.$this->path; - throw new \Exception('Unable to write to file: '.$this->path); + if (!\is_writable($this->path . '/backup.json')) { + throw new \Exception('Unable to write to file: ' . $this->path); } return $report; } + /** + * @throws \Exception + */ private function sync(): void { - $jsonEncodedData = \json_encode($this->data, JSON_PRETTY_PRINT); + $json = \json_encode($this->data, JSON_PRETTY_PRINT); - if ($jsonEncodedData === false) { + if ($json === false) { throw new \Exception('Unable to encode data to JSON, Are you accidentally encoding binary data?'); } - \file_put_contents($this->path.'/backup.json', \json_encode($this->data, JSON_PRETTY_PRINT)); + \file_put_contents($this->path . '/backup.json', $json); } + /** + * @param array $resources + * @param callable $callback + * @throws \Exception + */ protected function import(array $resources, callable $callback): void { foreach ($resources as $resource) { - /** @var resource $resource */ switch ($resource->getName()) { case Resource::TYPE_DEPLOYMENT: /** @var Deployment $resource */ if ($resource->getStart() === 0) { - $this->data[$resource->getGroup()][$resource->getName()][] = $resource->asArray(); + $this->data[$resource->getGroup()][$resource->getName()][] = (string) \json_encode($resource); } - file_put_contents($this->path.'deployments/'.$resource->getId().'.tar.gz', $resource->getData(), FILE_APPEND); + file_put_contents($this->path . 'deployments/' . $resource->getId() . '.tar.gz', $resource->getData(), FILE_APPEND); $resource->setData(''); break; case Resource::TYPE_FILE: /** @var File $resource */ - - // Handle folders if (str_contains($resource->getFileName(), '/')) { $folders = explode('/', $resource->getFileName()); $folderPath = $this->path.'/files'; @@ -123,12 +137,13 @@ protected function import(array $resources, callable $callback): void $resource->setData(''); break; default: - $this->data[$resource->getGroup()][$resource->getName()][] = $resource->asArray(); + $this->data[$resource->getGroup()][$resource->getName()][] = (string) \json_encode($resource); break; } $resource->setStatus(Resource::STATUS_SUCCESS); $this->cache->update($resource); + $this->sync(); } diff --git a/src/Migration/Exception.php b/src/Migration/Exception.php index d90f50c..7200628 100644 --- a/src/Migration/Exception.php +++ b/src/Migration/Exception.php @@ -8,8 +8,13 @@ class Exception extends \Exception public string $resourceId; - public function __construct(string $resourceType, string $message, int $code = 0, ?\Throwable $previous = null, string $resourceId = '') - { + public function __construct( + string $resourceType, + string $message, + int $code = 0, + ?\Throwable $previous = null, + string $resourceId = '' + ) { $this->resourceId = $resourceId; $this->resourceType = $resourceType; diff --git a/src/Migration/Resource.php b/src/Migration/Resource.php index 0516dd9..d7b14f4 100644 --- a/src/Migration/Resource.php +++ b/src/Migration/Resource.php @@ -2,59 +2,59 @@ namespace Utopia\Migration; -abstract class Resource +abstract class Resource implements \JsonSerializable { - public const STATUS_PENDING = 'pending'; + public const string STATUS_PENDING = 'pending'; - public const STATUS_SUCCESS = 'success'; + public const string STATUS_SUCCESS = 'success'; - public const STATUS_ERROR = 'error'; + public const string STATUS_ERROR = 'error'; - public const STATUS_SKIPPED = 'skip'; + public const string STATUS_SKIPPED = 'skip'; - public const STATUS_PROCESSING = 'processing'; + public const string STATUS_PROCESSING = 'processing'; - public const STATUS_WARNING = 'warning'; + public const string STATUS_WARNING = 'warning'; /** * For some transfers (namely Firebase) we have to keep resources in cache that do not necessarily need to be Transferred * This status is used to mark resources that are not going to be transferred but are still needed for the transfer to work * e.g Documents are required for Database transfers because of schema tracing in firebase */ - public const STATUS_DISREGARDED = 'disregarded'; + public const string STATUS_DISREGARDED = 'disregarded'; // Master Resources - public const TYPE_BUCKET = 'bucket'; + public const string TYPE_BUCKET = 'bucket'; - public const TYPE_COLLECTION = 'collection'; + public const string TYPE_COLLECTION = 'collection'; - public const TYPE_DATABASE = 'database'; + public const string TYPE_DATABASE = 'database'; - public const TYPE_DOCUMENT = 'document'; + public const string TYPE_DOCUMENT = 'document'; - public const TYPE_FILE = 'file'; + public const string TYPE_FILE = 'file'; - public const TYPE_USER = 'user'; + public const string TYPE_USER = 'user'; - public const TYPE_TEAM = 'team'; + public const string TYPE_TEAM = 'team'; - public const TYPE_MEMBERSHIP = 'membership'; + public const string TYPE_MEMBERSHIP = 'membership'; - public const TYPE_FUNCTION = 'function'; + public const string TYPE_FUNCTION = 'function'; - public const TYPE_INDEX = 'index'; + public const string TYPE_INDEX = 'index'; // Children (Resources that are created by other resources) - public const TYPE_ATTRIBUTE = 'attribute'; + public const string TYPE_ATTRIBUTE = 'attribute'; - public const TYPE_DEPLOYMENT = 'deployment'; + public const string TYPE_DEPLOYMENT = 'deployment'; - public const TYPE_HASH = 'hash'; + public const string TYPE_HASH = 'hash'; - public const TYPE_ENVIRONMENT_VARIABLE = 'environment variable'; + public const string TYPE_ENVIRONMENT_VARIABLE = 'environment variable'; - public const ALL_RESOURCES = [ + public const array ALL_RESOURCES = [ self::TYPE_ATTRIBUTE, self::TYPE_BUCKET, self::TYPE_COLLECTION, @@ -149,7 +149,7 @@ public function setMessage(string $message): self } /** - * @returns string[] + * @returns array */ public function getPermissions(): array { @@ -157,7 +157,7 @@ public function getPermissions(): array } /** - * @param string[] $permissions + * @param array $permissions */ public function setPermissions(array $permissions): self { @@ -165,6 +165,4 @@ public function setPermissions(array $permissions): self return $this; } - - abstract public function asArray(): array; } diff --git a/src/Migration/Source.php b/src/Migration/Source.php index 45ba29a..5a68aac 100644 --- a/src/Migration/Source.php +++ b/src/Migration/Source.php @@ -11,6 +11,10 @@ abstract class Source extends Target */ public array $previousReport = []; + /** + * @param array $resources + * @return void + */ public function callback(array $resources): void { ($this->transferCallback)($resources); @@ -19,7 +23,7 @@ public function callback(array $resources): void /** * Transfer Resources into destination * - * @param string[] $resources Resources to transfer + * @param array $resources Resources to transfer * @param callable $callback Callback to run after transfer */ public function run(array $resources, callable $callback): void @@ -45,22 +49,22 @@ public function run(array $resources, callable $callback): void /** * Export Resources * - * @param string[] $resources Resources to export + * @param array $resources Resources to export * @param int $batchSize Max 100 */ - public function exportResources(array $resources, int $batchSize) + public function exportResources(array $resources, int $batchSize): void { // Convert Resources back into their relevant groups $groups = []; foreach ($resources as $resource) { - if (in_array($resource, Transfer::GROUP_AUTH_RESOURCES)) { + if (\in_array($resource, Transfer::GROUP_AUTH_RESOURCES)) { $groups[Transfer::GROUP_AUTH][] = $resource; - } elseif (in_array($resource, Transfer::GROUP_DATABASES_RESOURCES)) { + } elseif (\in_array($resource, Transfer::GROUP_DATABASES_RESOURCES)) { $groups[Transfer::GROUP_DATABASES][] = $resource; - } elseif (in_array($resource, Transfer::GROUP_STORAGE_RESOURCES)) { + } elseif (\in_array($resource, Transfer::GROUP_STORAGE_RESOURCES)) { $groups[Transfer::GROUP_STORAGE][] = $resource; - } elseif (in_array($resource, Transfer::GROUP_FUNCTIONS_RESOURCES)) { + } elseif (\in_array($resource, Transfer::GROUP_FUNCTIONS_RESOURCES)) { $groups[Transfer::GROUP_FUNCTIONS][] = $resource; } } @@ -92,7 +96,7 @@ public function exportResources(array $resources, int $batchSize) * Export Auth Group * * @param int $batchSize Max 100 - * @param string[] $resources Resources to export + * @param array $resources Resources to export */ abstract protected function exportGroupAuth(int $batchSize, array $resources); @@ -100,7 +104,7 @@ abstract protected function exportGroupAuth(int $batchSize, array $resources); * Export Databases Group * * @param int $batchSize Max 100 - * @param string[] $resources Resources to export + * @param array $resources Resources to export */ abstract protected function exportGroupDatabases(int $batchSize, array $resources); @@ -108,7 +112,7 @@ abstract protected function exportGroupDatabases(int $batchSize, array $resource * Export Storage Group * * @param int $batchSize Max 5 - * @param string[] $resources Resources to export + * @param array $resources Resources to export */ abstract protected function exportGroupStorage(int $batchSize, array $resources); @@ -116,7 +120,7 @@ abstract protected function exportGroupStorage(int $batchSize, array $resources) * Export Functions Group * * @param int $batchSize Max 100 - * @param string[] $resources Resources to export + * @param array $resources Resources to export */ abstract protected function exportGroupFunctions(int $batchSize, array $resources); } diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 6d7b748..64765ba 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -2,6 +2,7 @@ namespace Utopia\Migration\Sources; +use Appwrite\AppwriteException; use Appwrite\Client; use Appwrite\Query; use Appwrite\Services\Databases; @@ -40,25 +41,33 @@ class Appwrite extends Source { -// /** -// * @var Client|null -// */ - protected $client = null; + protected Client $client; - protected string $project = ''; + private Users $users; + private Teams $teams; + private Databases $database; + private Storage $storage; + private Functions $functions; - protected string $key = ''; - public function __construct(string $project, string $endpoint, string $key) - { + public function __construct( + protected string $project, + string $endpoint, + protected string $key + ) { $this->client = (new Client()) ->setEndpoint($endpoint) ->setProject($project) ->setKey($key); + $this->users = new Users($this->client); + $this->teams = new Teams($this->client); + $this->database = new Databases($this->client); + $this->storage = new Storage($this->client); + $this->functions = new Functions($this->client); + $this->endpoint = $endpoint; - $this->project = $project; - $this->key = $key; + $this->headers['X-Appwrite-Project'] = $this->project; $this->headers['X-Appwrite-Key'] = $this->key; } @@ -96,107 +105,126 @@ public static function getSupportedResources(): array ]; } + /** + * @param array $resources + * @return array + * @throws \Exception + */ public function report(array $resources = []): array { $report = []; - $currentPermission = ''; if (empty($resources)) { $resources = $this->getSupportedResources(); } - $usersClient = new Users($this->client); - $teamsClient = new Teams($this->client); - $databaseClient = new Databases($this->client); - $storageClient = new Storage($this->client); - $functionsClient = new Functions($this->client); - // Auth try { - $currentPermission = 'users.read'; - if (in_array(Resource::TYPE_USER, $resources)) { - $report[Resource::TYPE_USER] = $usersClient->list()['total']; + $scope = 'users.read'; + if (\in_array(Resource::TYPE_USER, $resources)) { + $report[Resource::TYPE_USER] = $this->users->list()['total']; } - $currentPermission = 'teams.read'; - if (in_array(Resource::TYPE_TEAM, $resources)) { - $report[Resource::TYPE_TEAM] = $teamsClient->list()['total']; + $scope = 'teams.read'; + if (\in_array(Resource::TYPE_TEAM, $resources)) { + $report[Resource::TYPE_TEAM] = $this->teams->list()['total']; } - if (in_array(Resource::TYPE_MEMBERSHIP, $resources)) { + if (\in_array(Resource::TYPE_MEMBERSHIP, $resources)) { $report[Resource::TYPE_MEMBERSHIP] = 0; - $teams = $teamsClient->list()['teams']; + $teams = $this->teams->list()['teams']; foreach ($teams as $team) { - $report[Resource::TYPE_MEMBERSHIP] += $teamsClient->listMemberships($team['$id'], [Query::limit(1)])['total']; + $report[Resource::TYPE_MEMBERSHIP] += $this->teams->listMemberships( + $team['$id'], + [Query::limit(1)] + )['total']; } } // Databases - $currentPermission = 'databases.read'; - if (in_array(Resource::TYPE_DATABASE, $resources)) { - $report[Resource::TYPE_DATABASE] = $databaseClient->list()['total']; + $scope = 'databases.read'; + if (\in_array(Resource::TYPE_DATABASE, $resources)) { + $report[Resource::TYPE_DATABASE] = $this->database->list()['total']; } - $currentPermission = 'collections.read'; - if (in_array(Resource::TYPE_COLLECTION, $resources)) { + $scope = 'collections.read'; + if (\in_array(Resource::TYPE_COLLECTION, $resources)) { $report[Resource::TYPE_COLLECTION] = 0; - $databases = $databaseClient->list()['databases']; + $databases = $this->database->list()['databases']; foreach ($databases as $database) { - $report[Resource::TYPE_COLLECTION] += $databaseClient->listCollections($database['$id'], [Query::limit(1)])['total']; + $report[Resource::TYPE_COLLECTION] += $this->database->listCollections( + $database['$id'], + [Query::limit(1)] + )['total']; } } - $currentPermission = 'documents.read'; - if (in_array(Resource::TYPE_DOCUMENT, $resources)) { + $scope = 'documents.read'; + if (\in_array(Resource::TYPE_DOCUMENT, $resources)) { $report[Resource::TYPE_DOCUMENT] = 0; - $databases = $databaseClient->list()['databases']; + $databases = $this->database->list()['databases']; foreach ($databases as $database) { - $collections = $databaseClient->listCollections($database['$id'])['collections']; + $collections = $this->database->listCollections($database['$id'])['collections']; foreach ($collections as $collection) { - $report[Resource::TYPE_DOCUMENT] += $databaseClient->listDocuments($database['$id'], $collection['$id'], [Query::limit(1)])['total']; + $report[Resource::TYPE_DOCUMENT] += $this->database->listDocuments( + $database['$id'], + $collection['$id'], + [Query::limit(1)] + )['total']; } } } - $currentPermission = 'attributes.read'; - if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) { + $scope = 'attributes.read'; + if (\in_array(Resource::TYPE_ATTRIBUTE, $resources)) { $report[Resource::TYPE_ATTRIBUTE] = 0; - $databases = $databaseClient->list()['databases']; + $databases = $this->database->list()['databases']; foreach ($databases as $database) { - $collections = $databaseClient->listCollections($database['$id'])['collections']; + $collections = $this->database->listCollections($database['$id'])['collections']; foreach ($collections as $collection) { - $report[Resource::TYPE_ATTRIBUTE] += $databaseClient->listAttributes($database['$id'], $collection['$id'])['total']; + $report[Resource::TYPE_ATTRIBUTE] += $this->database->listAttributes( + $database['$id'], + $collection['$id'] + )['total']; } } } - $currentPermission = 'indexes.read'; - if (in_array(Resource::TYPE_INDEX, $resources)) { + $scope = 'indexes.read'; + if (\in_array(Resource::TYPE_INDEX, $resources)) { $report[Resource::TYPE_INDEX] = 0; - $databases = $databaseClient->list()['databases']; + $databases = $this->database->list()['databases']; foreach ($databases as $database) { - $collections = $databaseClient->listCollections($database['$id'])['collections']; + $collections = $this->database->listCollections($database['$id'])['collections']; foreach ($collections as $collection) { - $report[Resource::TYPE_INDEX] += $databaseClient->listIndexes($database['$id'], $collection['$id'])['total']; + $report[Resource::TYPE_INDEX] += $this->database->listIndexes( + $database['$id'], + $collection['$id'] + )['total']; } } } // Storage - $currentPermission = 'buckets.read'; - if (in_array(Resource::TYPE_BUCKET, $resources)) { - $report[Resource::TYPE_BUCKET] = $storageClient->listBuckets()['total']; + $scope = 'buckets.read'; + if (\in_array(Resource::TYPE_BUCKET, $resources)) { + $report[Resource::TYPE_BUCKET] = $this->storage->listBuckets()['total']; } - $currentPermission = 'files.read'; - if (in_array(Resource::TYPE_FILE, $resources)) { + $scope = 'files.read'; + if (\in_array(Resource::TYPE_FILE, $resources)) { $report[Resource::TYPE_FILE] = 0; $report['size'] = 0; $buckets = []; $lastBucket = null; while (true) { - $currentBuckets = $storageClient->listBuckets($lastBucket ? [Query::cursorAfter($lastBucket)] : [Query::limit(20)])['buckets']; + $currentBuckets = $this->storage->listBuckets( + $lastBucket + ? [Query::cursorAfter($lastBucket)] + : [Query::limit(20)] + )['buckets']; + $buckets = array_merge($buckets, $currentBuckets); $lastBucket = $buckets[count($buckets) - 1]['$id'] ?? null; @@ -210,7 +238,13 @@ public function report(array $resources = []): array $lastFile = null; while (true) { - $currentFiles = $storageClient->listFiles($bucket['$id'], $lastFile ? [Query::cursorAfter($lastFile)] : [Query::limit(20)])['files']; + $currentFiles = $this->storage->listFiles( + $bucket['$id'], + $lastFile + ? [Query::cursorAfter($lastFile)] + : [Query::limit(20)] + )['files']; + $files = array_merge($files, $currentFiles); $lastFile = $files[count($files) - 1]['$id']; @@ -221,21 +255,24 @@ public function report(array $resources = []): array $report[Resource::TYPE_FILE] += count($files); foreach ($files as $file) { - $report['size'] += $storageClient->getFile($bucket['$id'], $file['$id'])['sizeOriginal']; + $report['size'] += $this->storage->getFile( + $bucket['$id'], + $file['$id'] + )['sizeOriginal']; } } $report['size'] = $report['size'] / 1000 / 1000; // MB } // Functions - $currentPermission = 'functions.read'; - if (in_array(Resource::TYPE_FUNCTION, $resources)) { - $report[Resource::TYPE_FUNCTION] = $functionsClient->list()['total']; + $scope = 'functions.read'; + if (\in_array(Resource::TYPE_FUNCTION, $resources)) { + $report[Resource::TYPE_FUNCTION] = $this->functions->list()['total']; } - if (in_array(Resource::TYPE_DEPLOYMENT, $resources)) { + if (\in_array(Resource::TYPE_DEPLOYMENT, $resources)) { $report[Resource::TYPE_DEPLOYMENT] = 0; - $functions = $functionsClient->list()['functions']; + $functions = $this->functions->list()['functions']; foreach ($functions as $function) { if (! empty($function['deployment'])) { $report[Resource::TYPE_DEPLOYMENT] += 1; @@ -243,22 +280,29 @@ public function report(array $resources = []): array } } - if (in_array(Resource::TYPE_ENVIRONMENT_VARIABLE, $resources)) { + if (\in_array(Resource::TYPE_ENVIRONMENT_VARIABLE, $resources)) { $report[Resource::TYPE_ENVIRONMENT_VARIABLE] = 0; - $functions = $functionsClient->list()['functions']; + $functions = $this->functions->list()['functions']; foreach ($functions as $function) { - $report[Resource::TYPE_ENVIRONMENT_VARIABLE] += $functionsClient->listVariables($function['$id'])['total']; + $report[Resource::TYPE_ENVIRONMENT_VARIABLE] += $this->functions->listVariables($function['$id'])['total']; } } - $report['version'] = $this->call('GET', '/health/version', ['X-Appwrite-Key' => '', 'X-Appwrite-Project' => ''])['version']; + $report['version'] = $this->call( + 'GET', + '/health/version', + [ + 'X-Appwrite-Key' => '', + 'X-Appwrite-Project' => '' + ] + )['version']; $this->previousReport = $report; return $report; } catch (\Throwable $e) { if ($e->getCode() === 403) { - throw new \Exception("Missing Permission: {$currentPermission}."); + throw new \Exception("Missing scope: $scope."); } else { throw new \Exception($e->getMessage()); } @@ -269,13 +313,13 @@ public function report(array $resources = []): array * Export Auth Resources * * @param int $batchSize Max 100 - * @param string[] $resources + * @param array $resources * @return void */ - protected function exportGroupAuth(int $batchSize, array $resources) + protected function exportGroupAuth(int $batchSize, array $resources): void { try { - if (in_array(Resource::TYPE_USER, $resources)) { + if (\in_array(Resource::TYPE_USER, $resources)) { $this->exportUsers($batchSize); } } catch (\Throwable $e) { @@ -286,7 +330,7 @@ protected function exportGroupAuth(int $batchSize, array $resources) } try { - if (in_array(Resource::TYPE_TEAM, $resources)) { + if (\in_array(Resource::TYPE_TEAM, $resources)) { $this->exportTeams($batchSize); } } catch (\Throwable $e) { @@ -297,7 +341,7 @@ protected function exportGroupAuth(int $batchSize, array $resources) } try { - if (in_array(Resource::TYPE_MEMBERSHIP, $resources)) { + if (\in_array(Resource::TYPE_MEMBERSHIP, $resources)) { $this->exportMemberships($batchSize); } } catch (\Throwable $e) { @@ -308,9 +352,11 @@ protected function exportGroupAuth(int $batchSize, array $resources) } } - private function exportUsers(int $batchSize) + /** + * @throws AppwriteException + */ + private function exportUsers(int $batchSize): void { - $usersClient = new Users($this->client); $lastDocument = null; // Export Users @@ -323,7 +369,7 @@ private function exportUsers(int $batchSize) $queries[] = Query::cursorAfter($lastDocument); } - $response = $usersClient->list($queries); + $response = $this->users->list($queries); if ($response['total'] == 0) { break; } @@ -355,9 +401,12 @@ private function exportUsers(int $batchSize) } } - private function exportTeams(int $batchSize) + /** + * @throws AppwriteException + */ + private function exportTeams(int $batchSize): void { - $teamsClient = new Teams($this->client); + $this->teams = new Teams($this->client); $lastDocument = null; // Export Teams @@ -370,7 +419,7 @@ private function exportTeams(int $batchSize) $queries[] = Query::cursorAfter($lastDocument); } - $response = $teamsClient->list($queries); + $response = $this->teams->list($queries); if ($response['total'] == 0) { break; } @@ -393,16 +442,18 @@ private function exportTeams(int $batchSize) } } - private function exportMemberships(int $batchSize) + /** + * @throws AppwriteException + * @throws \Exception + */ + private function exportMemberships(int $batchSize): void { - $teamsClient = new Teams($this->client); - - // Export Memberships $cacheTeams = $this->cache->get(Team::getName()); - /** @var array - array where key is user ID */ + + /** @var array $cacheUsers */ $cacheUsers = []; + foreach ($this->cache->get(User::getName()) as $cacheUser) { - /** @var User $cacheUser */ $cacheUsers[$cacheUser->getId()] = $cacheUser; } @@ -419,7 +470,7 @@ private function exportMemberships(int $batchSize) $queries[] = Query::cursorAfter($lastDocument); } - $response = $teamsClient->listMemberships($team->getId(), $queries); + $response = $this->teams->listMemberships($team->getId(), $queries); if ($response['total'] == 0) { break; @@ -432,6 +483,7 @@ private function exportMemberships(int $batchSize) } $memberships[] = new Membership( + $membership['$id'], $team, $user, $membership['roles'], @@ -450,10 +502,10 @@ private function exportMemberships(int $batchSize) } } - protected function exportGroupDatabases(int $batchSize, array $resources) + protected function exportGroupDatabases(int $batchSize, array $resources): void { try { - if (in_array(Resource::TYPE_DATABASE, $resources)) { + if (\in_array(Resource::TYPE_DATABASE, $resources)) { $this->exportDatabases($batchSize); } } catch (\Throwable $e) { @@ -466,7 +518,7 @@ protected function exportGroupDatabases(int $batchSize, array $resources) } try { - if (in_array(Resource::TYPE_COLLECTION, $resources)) { + if (\in_array(Resource::TYPE_COLLECTION, $resources)) { $this->exportCollections($batchSize); } } catch (\Throwable $e) { @@ -479,7 +531,7 @@ protected function exportGroupDatabases(int $batchSize, array $resources) } try { - if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) { + if (\in_array(Resource::TYPE_ATTRIBUTE, $resources)) { $this->exportAttributes($batchSize); } } catch (\Throwable $e) { @@ -492,7 +544,7 @@ protected function exportGroupDatabases(int $batchSize, array $resources) } try { - if (in_array(Resource::TYPE_INDEX, $resources)) { + if (\in_array(Resource::TYPE_INDEX, $resources)) { $this->exportIndexes($batchSize); } } catch (\Throwable $e) { @@ -505,7 +557,7 @@ protected function exportGroupDatabases(int $batchSize, array $resources) } try { - if (in_array(Resource::TYPE_DOCUMENT, $resources)) { + if (\in_array(Resource::TYPE_DOCUMENT, $resources)) { $this->exportDocuments($batchSize); } } catch (\Throwable $e) { @@ -518,7 +570,7 @@ protected function exportGroupDatabases(int $batchSize, array $resources) } } - public function stripMetadata(array $document, bool $root = true) + public function stripMetadata(array $document, bool $root = true): array { if ($root) { unset($document['$id']); @@ -539,9 +591,11 @@ public function stripMetadata(array $document, bool $root = true) return $document; } - private function exportDocuments(int $batchSize) + /** + * @throws AppwriteException + */ + private function exportDocuments(int $batchSize): void { - $databaseClient = new Databases($this->client); $collections = $this->cache->get(Collection::getName()); foreach ($collections as $collection) { @@ -557,7 +611,7 @@ private function exportDocuments(int $batchSize) $queries[] = Query::cursorAfter($lastDocument); } - $response = $databaseClient->listDocuments( + $response = $this->database->listDocuments( $collection->getDatabase()->getId(), $collection->getId(), $queries @@ -570,7 +624,7 @@ private function exportDocuments(int $batchSize) $document = $this->stripMetadata($document); // Certain Appwrite versions allowed for data to be required but null - // This isn't allowed in modern versions so we need to remove it by comparing their attributes and replacing it with default value. + // This isn't allowed in modern versions, so we need to remove it by comparing their attributes and replacing it with default value. $attributes = $this->cache->get(Attribute::getName()); foreach ($attributes as $attribute) { /** @var Attribute $attribute */ @@ -593,7 +647,7 @@ private function exportDocuments(int $batchSize) $document[$attribute->getKey()] = 0.0; break; case Attribute::TYPE_DATETIME: - $document[$attribute->getKey()] = 0; + $document[$attribute->getKey()] = '1970-01-01 00:00:00.000'; break; case Attribute::TYPE_URL: $document[$attribute->getKey()] = 'http://null'; @@ -623,11 +677,14 @@ private function exportDocuments(int $batchSize) } } + /** + * @throws \Exception + */ private function convertAttribute(array $value, Collection $collection): Attribute { switch ($value['type']) { case 'string': - if (! isset($value['format'])) { + if (!isset($value['format'])) { return new Text( $value['key'], $collection, @@ -638,58 +695,52 @@ private function convertAttribute(array $value, Collection $collection): Attribu ); } - switch ($value['format']) { - case 'email': - return new Email( - $value['key'], - $collection, - $value['required'], - $value['array'], - $value['default'] - ); - case 'enum': - return new Enum( - $value['key'], - $collection, - $value['elements'], - $value['required'], - $value['array'], - $value['default'] - ); - case 'url': - return new URL( - $value['key'], - $collection, - $value['required'], - $value['array'], - $value['default'] - ); - case 'ip': - return new IP( - $value['key'], - $collection, - $value['required'], - $value['array'], - $value['default'] - ); - case 'datetime': - return new DateTime( - $value['key'], - $collection, - $value['required'], - $value['array'], - $value['default'] - ); - default: - return new Text( - $value['key'], - $collection, - $value['required'], - $value['array'], - $value['default'], - $value['size'] ?? 0 - ); - } + return match ($value['format']) { + 'email' => new Email( + $value['key'], + $collection, + $value['required'], + $value['array'], + $value['default'] + ), + 'enum' => new Enum( + $value['key'], + $collection, + $value['elements'], + $value['required'], + $value['array'], + $value['default'] + ), + 'url' => new URL( + $value['key'], + $collection, + $value['required'], + $value['array'], + $value['default'] + ), + 'ip' => new IP( + $value['key'], + $collection, + $value['required'], + $value['array'], + $value['default'] + ), + 'datetime' => new DateTime( + $value['key'], + $collection, + $value['required'], + $value['array'], + $value['default'] + ), + default => new Text( + $value['key'], + $collection, + $value['required'], + $value['array'], + $value['default'], + $value['size'] ?? 0 + ), + }; case 'boolean': return new Boolean( $value['key'], @@ -744,9 +795,12 @@ private function convertAttribute(array $value, Collection $collection): Attribu throw new \Exception('Unknown attribute type: '.$value['type']); } - private function exportDatabases(int $batchSize) + /** + * @throws AppwriteException + */ + private function exportDatabases(int $batchSize): void { - $databaseClient = new Databases($this->client); + $this->database = new Databases($this->client); $lastDatabase = null; @@ -759,12 +813,12 @@ private function exportDatabases(int $batchSize) $queries[] = Query::cursorAfter($lastDatabase); } - $response = $databaseClient->list($queries); + $response = $this->database->list($queries); foreach ($response['databases'] as $database) { $newDatabase = new Database( + $database['$id'], $database['name'], - $database['$id'] ); $databases[] = $newDatabase; @@ -784,13 +838,13 @@ private function exportDatabases(int $batchSize) } } - private function exportCollections(int $batchSize) + /** + * @throws AppwriteException + */ + private function exportCollections(int $batchSize): void { - $databaseClient = new Databases($this->client); - - // Transfer Collections - $databases = $this->cache->get(Database::getName()); + foreach ($databases as $database) { $lastCollection = null; @@ -803,7 +857,7 @@ private function exportCollections(int $batchSize) $queries[] = Query::cursorAfter($lastCollection); } - $response = $databaseClient->listCollections( + $response = $this->database->listCollections( $database->getId(), $queries ); @@ -833,11 +887,12 @@ private function exportCollections(int $batchSize) } } - private function exportAttributes(int $batchSize) + /** + * @throws AppwriteException + * @throws \Exception + */ + private function exportAttributes(int $batchSize): void { - $databaseClient = new Databases($this->client); - - // Transfer Attributes $collections = $this->cache->get(Collection::getName()); /** @var Collection[] $collections */ foreach ($collections as $collection) { @@ -851,7 +906,7 @@ private function exportAttributes(int $batchSize) $queries[] = Query::cursorAfter($lastAttribute); } - $response = $databaseClient->listAttributes( + $response = $this->database->listAttributes( $collection->getDatabase()->getId(), $collection->getId(), $queries @@ -860,22 +915,23 @@ private function exportAttributes(int $batchSize) // Remove two way relationship attributes $this->cache->get(Resource::TYPE_ATTRIBUTE); - $knownTwoways = []; + $knownTwoWays = []; foreach ($this->cache->get(Resource::TYPE_ATTRIBUTE) as $attribute) { /** @var Attribute|Relationship $attribute */ if ($attribute->getTypeName() == Attribute::TYPE_RELATIONSHIP && $attribute->getTwoWay()) { - $knownTwoways[] = $attribute->getTwoWayKey(); + $knownTwoWays[] = $attribute->getTwoWayKey(); } } foreach ($response['attributes'] as $attribute) { - if (in_array($attribute['key'], $knownTwoways)) { + /** @var array $attribute */ + if (\in_array($attribute['key'], $knownTwoWays)) { continue; } if ($attribute['type'] === 'relationship') { - $knownTwoways[] = $attribute['twoWayKey']; + $knownTwoWays[] = $attribute['twoWayKey']; } $attributes[] = $this->convertAttribute($attribute, $collection); @@ -895,10 +951,11 @@ private function exportAttributes(int $batchSize) } } - private function exportIndexes(int $batchSize) + /** + * @throws AppwriteException + */ + private function exportIndexes(int $batchSize): void { - $databaseClient = new Databases($this->client); - $collections = $this->cache->get(Resource::TYPE_COLLECTION); // Transfer Indexes @@ -914,7 +971,7 @@ private function exportIndexes(int $batchSize) $queries[] = Query::cursorAfter($lastIndex); } - $response = $databaseClient->listIndexes( + $response = $this->database->listIndexes( $collection->getDatabase()->getId(), $collection->getId(), $queries @@ -964,11 +1021,11 @@ private function calculateTypes(array $user): array return $types; } - protected function exportGroupStorage(int $batchSize, array $resources) + protected function exportGroupStorage(int $batchSize, array $resources): void { try { - if (in_array(Resource::TYPE_BUCKET, $resources)) { - $this->exportBuckets($batchSize, false); + if (\in_array(Resource::TYPE_BUCKET, $resources)) { + $this->exportBuckets($batchSize); } } catch (\Throwable $e) { $this->addError( @@ -980,7 +1037,7 @@ protected function exportGroupStorage(int $batchSize, array $resources) } try { - if (in_array(Resource::TYPE_FILE, $resources)) { + if (\in_array(Resource::TYPE_FILE, $resources)) { $this->exportFiles($batchSize); } } catch (\Throwable $e) { @@ -991,26 +1048,14 @@ protected function exportGroupStorage(int $batchSize, array $resources) ) ); } - - try { - if (in_array(Resource::TYPE_BUCKET, $resources)) { - $this->exportBuckets($batchSize, true); - } - } catch (\Throwable $e) { - $this->addError( - new Exception( - Resource::TYPE_BUCKET, - $e->getMessage() - ) - ); - } } - private function exportBuckets(int $batchSize, bool $updateLimits) + /** + * @throws AppwriteException + */ + private function exportBuckets(int $batchSize): void { - $storageClient = new Storage($this->client); - - $buckets = $storageClient->listBuckets(); + $buckets = $this->storage->listBuckets(); $convertedBuckets = []; @@ -1026,10 +1071,7 @@ private function exportBuckets(int $batchSize, bool $updateLimits) $bucket['compression'], $bucket['encryption'], $bucket['antivirus'], - $updateLimits ); - - $bucket->setUpdateLimits($updateLimits); $convertedBuckets[] = $bucket; } @@ -1040,11 +1082,13 @@ private function exportBuckets(int $batchSize, bool $updateLimits) $this->callback($convertedBuckets); } - private function exportFiles(int $batchSize) + /** + * @throws AppwriteException + */ + private function exportFiles(int $batchSize): void { - $storageClient = new Storage($this->client); - $buckets = $this->cache->get(Bucket::getName()); + foreach ($buckets as $bucket) { /** @var Bucket $bucket */ $lastDocument = null; @@ -1056,7 +1100,7 @@ private function exportFiles(int $batchSize) $queries[] = Query::cursorAfter($lastDocument); } - $response = $storageClient->listFiles( + $response = $this->storage->listFiles( $bucket->getId(), $queries ); @@ -1091,7 +1135,7 @@ private function exportFiles(int $batchSize) } } - private function exportFileData(File $file) + private function exportFileData(File $file): void { // Set the chunk size (5MB) $start = 0; @@ -1113,7 +1157,8 @@ private function exportFileData(File $file) ); // Send the chunk to the callback function - $file->setData($chunkData) + $file + ->setData($chunkData) ->setStart($start) ->setEnd($end); @@ -1129,10 +1174,10 @@ private function exportFileData(File $file) } } - protected function exportGroupFunctions(int $batchSize, array $resources) + protected function exportGroupFunctions(int $batchSize, array $resources): void { try { - if (in_array(Resource::TYPE_FUNCTION, $resources)) { + if (\in_array(Resource::TYPE_FUNCTION, $resources)) { $this->exportFunctions($batchSize); } } catch (\Throwable $e) { @@ -1143,7 +1188,7 @@ protected function exportGroupFunctions(int $batchSize, array $resources) } try { - if (in_array(Resource::TYPE_DEPLOYMENT, $resources)) { + if (\in_array(Resource::TYPE_DEPLOYMENT, $resources)) { $this->exportDeployments($batchSize, true); } } catch (\Throwable $e) { @@ -1154,11 +1199,14 @@ protected function exportGroupFunctions(int $batchSize, array $resources) } } - private function exportFunctions(int $batchSize) + /** + * @throws AppwriteException + */ + private function exportFunctions(int $batchSize): void { - $functionsClient = new Functions($this->client); + $this->functions = new Functions($this->client); - $functions = $functionsClient->list(); + $functions = $this->functions->list(); if ($functions['total'] === 0) { return; @@ -1168,8 +1216,8 @@ private function exportFunctions(int $batchSize) foreach ($functions['functions'] as $function) { $convertedFunc = new Func( - $function['name'], $function['$id'], + $function['name'], $function['runtime'], $function['execute'], $function['enabled'], @@ -1183,6 +1231,7 @@ private function exportFunctions(int $batchSize) foreach ($function['vars'] as $var) { $convertedResources[] = new EnvVar( + $var['$id'], $convertedFunc, $var['key'], $var['value'], @@ -1193,24 +1242,20 @@ private function exportFunctions(int $batchSize) $this->callback($convertedResources); } - private function exportDeployments(int $batchSize, bool $exportOnlyActive = false) + /** + * @throws AppwriteException + */ + private function exportDeployments(int $batchSize, bool $exportOnlyActive = false): void { - $functionsClient = new Functions($this->client); + $this->functions = new Functions($this->client); $functions = $this->cache->get(Func::getName()); - // exportDeploymentData doesn't exist on Appwrite versions prior to 1.4 - $appwriteVersion = $this->call('GET', '/health/version', ['X-Appwrite-Key' => '', 'X-Appwrite-Project' => ''])['version']; - - if (version_compare($appwriteVersion, '1.4.0', '<')) { - return; - } - foreach ($functions as $func) { /** @var Func $func */ $lastDocument = null; if ($exportOnlyActive && $func->getActiveDeployment()) { - $deployment = $functionsClient->getDeployment($func->getId(), $func->getActiveDeployment()); + $deployment = $this->functions->getDeployment($func->getId(), $func->getActiveDeployment()); try { $this->exportDeploymentData($func, $deployment); @@ -1228,7 +1273,7 @@ private function exportDeployments(int $batchSize, bool $exportOnlyActive = fals $queries[] = Query::cursorAfter($lastDocument); } - $response = $functionsClient->listDeployments( + $response = $this->functions->listDeployments( $func->getId(), $queries ); @@ -1316,9 +1361,11 @@ private function exportDeploymentData(Func $func, array $deployment) ); // Send the chunk to the callback function - $deployment->setData($chunkData); - $deployment->setStart($start); - $deployment->setEnd($end); + $deployment + ->setData($chunkData) + ->setStart($start) + ->setEnd($end); + $this->callback([$deployment]); // Update the range diff --git a/src/Migration/Sources/Firebase.php b/src/Migration/Sources/Firebase.php index 3effe6e..cd1ad1c 100644 --- a/src/Migration/Sources/Firebase.php +++ b/src/Migration/Sources/Firebase.php @@ -22,6 +22,9 @@ class Firebase extends Source { + /** + * @var array + */ private array $serviceAccount; private string $projectID; @@ -30,6 +33,9 @@ class Firebase extends Source private int $tokenExpires = 0; + /** + * @param array $serviceAccount + */ public function __construct(array $serviceAccount) { $this->serviceAccount = $serviceAccount; @@ -41,7 +47,7 @@ public static function getName(): string return 'Firebase'; } - private function base64UrlEncode($data) + private function base64UrlEncode(string $data): string { return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($data)); } @@ -51,8 +57,8 @@ private function calculateJWT(): string $jwtClaim = [ 'iss' => $this->serviceAccount['client_email'], 'scope' => 'https://www.googleapis.com/auth/firebase https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/datastore', - 'exp' => time() + 3600, - 'iat' => time(), + 'exp' => \time() + 3600, + 'iat' => \time(), 'aud' => 'https://oauth2.googleapis.com/token', ]; @@ -64,16 +70,23 @@ private function calculateJWT(): string $jwtPayload = $this->base64UrlEncode(json_encode($jwtHeader)).'.'.$this->base64UrlEncode(json_encode($jwtClaim)); $jwtSignature = ''; - openssl_sign($jwtPayload, $jwtSignature, $this->serviceAccount['private_key'], 'sha256'); + + \openssl_sign( + $jwtPayload, + $jwtSignature, + $this->serviceAccount['private_key'], + 'sha256' + ); + $jwtSignature = $this->base64UrlEncode($jwtSignature); - return $jwtPayload.'.'.$jwtSignature; + return $jwtPayload . '.' . $jwtSignature; } /** * Computes the JWT then fetches an auth token from the Google OAuth2 API which is valid for an hour */ - private function authenticate() + private function authenticate(): void { if (time() < $this->tokenExpires) { return; @@ -95,7 +108,7 @@ private function authenticate() } } - protected function call(string $method, string $path = '', array $headers = [], array $params = [], &$responseHeaders = []): array|string + protected function call(string $method, string $path = '', array $headers = [], array $params = [], array &$responseHeaders = []): array|string { $this->authenticate(); @@ -148,7 +161,7 @@ public function report(array $resources = []): array return []; } - protected function exportGroupAuth(int $batchSize, array $resources) + protected function exportGroupAuth(int $batchSize, array $resources): void { // Check if Auth is enabled try { @@ -165,7 +178,7 @@ protected function exportGroupAuth(int $batchSize, array $resources) } try { - if (in_array(Resource::TYPE_USER, $resources)) { + if (\in_array(Resource::TYPE_USER, $resources)) { $this->exportUsers($batchSize); } } catch (\Throwable $e) { @@ -178,7 +191,7 @@ protected function exportGroupAuth(int $batchSize, array $resources) } } - private function exportUsers(int $batchSize) + private function exportUsers(int $batchSize): void { // Fetch our Hash Config $hashConfig = ($this->call('GET', 'https://identitytoolkit.googleapis.com/admin/v2/projects/'.$this->projectID.'/config'))['signIn']['hashConfig']; @@ -258,7 +271,7 @@ private function calculateUserType(array $providerData): array return $types; } - protected function exportGroupDatabases(int $batchSize, array $resources) + protected function exportGroupDatabases(int $batchSize, array $resources): void { // Check if Firestore is enabled try { @@ -275,7 +288,7 @@ protected function exportGroupDatabases(int $batchSize, array $resources) } try { - if (in_array(Resource::TYPE_DATABASE, $resources)) { + if (\in_array(Resource::TYPE_DATABASE, $resources)) { $database = new Database('default', 'default'); $database->setOriginalId('(default)'); $this->callback([$database]); @@ -290,7 +303,7 @@ protected function exportGroupDatabases(int $batchSize, array $resources) } try { - if (in_array(Resource::TYPE_COLLECTION, $resources)) { + if (\in_array(Resource::TYPE_COLLECTION, $resources)) { $this->exportDB($batchSize, in_array(Resource::TYPE_DOCUMENT, $resources), $database); } } catch (\Throwable $e) { @@ -303,7 +316,7 @@ protected function exportGroupDatabases(int $batchSize, array $resources) } } - private function exportDB(int $batchSize, bool $pushDocuments, Database $database) + private function exportDB(int $batchSize, bool $pushDocuments, Database $database): void { $baseURL = "https://firestore.googleapis.com/v1/projects/{$this->projectID}/databases/(default)/documents"; @@ -413,7 +426,7 @@ private function calculateArrayType(Collection $collection, string $key, array $ } } - private function exportCollection(Collection $collection, int $batchSize, bool $transferDocuments) + private function exportCollection(Collection $collection, int $batchSize, bool $transferDocuments): void { $resourceURL = 'https://firestore.googleapis.com/v1/projects/'.$this->projectID.'/databases/'.$collection->getDatabase()->getOriginalId().'/documents/'.$collection->getId(); @@ -528,7 +541,7 @@ private function convertDocument(Collection $collection, array $document): Docum return new Document($documentId, $collection->getDatabase(), $collection, $data, []); } - protected function exportGroupStorage(int $batchSize, array $resources) + protected function exportGroupStorage(int $batchSize, array $resources): void { // Check if storage is enabled try { @@ -549,7 +562,7 @@ protected function exportGroupStorage(int $batchSize, array $resources) } try { - if (in_array(Resource::TYPE_BUCKET, $resources)) { + if (\in_array(Resource::TYPE_BUCKET, $resources)) { $this->exportBuckets($batchSize); } } catch (\Throwable $e) { @@ -560,7 +573,7 @@ protected function exportGroupStorage(int $batchSize, array $resources) } try { - if (in_array(Resource::TYPE_FILE, $resources)) { + if (\in_array(Resource::TYPE_FILE, $resources)) { $this->exportFiles($batchSize); } } catch (\Throwable $e) { @@ -572,7 +585,7 @@ protected function exportGroupStorage(int $batchSize, array $resources) } - private function exportBuckets(int $batchsize) + private function exportBuckets(int $batchsize): void { $endpoint = 'https://storage.googleapis.com/storage/v1/b'; @@ -612,7 +625,7 @@ private function exportBuckets(int $batchsize) } } - private function sanitizeBucketId($id) + private function sanitizeBucketId($id): array|string|null { // Step 1: Check if the ID looks like a URL (contains ".") if (strpos($id, '.') !== false) { @@ -637,7 +650,7 @@ private function sanitizeBucketId($id) return $id; } - private function exportFiles(int $batchsize) + private function exportFiles(int $batchsize): void { $buckets = $this->cache->get(Bucket::getName()); @@ -676,7 +689,7 @@ private function exportFiles(int $batchsize) } } - private function exportFile(File $file) + private function exportFile(File $file): void { $endpoint = 'https://storage.googleapis.com/storage/v1/b/'.$file->getBucket()->getOriginalId().'/o/'.$file->getId().'?alt=media'; $start = 0; diff --git a/src/Migration/Sources/NHost.php b/src/Migration/Sources/NHost.php index 2671354..16f0867 100644 --- a/src/Migration/Sources/NHost.php +++ b/src/Migration/Sources/NHost.php @@ -113,7 +113,7 @@ public function report(array $resources = []): array } // Auth - if (in_array(Resource::TYPE_USER, $resources)) { + if (\in_array(Resource::TYPE_USER, $resources)) { $statement = $db->prepare('SELECT COUNT(*) FROM auth.users'); $statement->execute(); @@ -125,11 +125,11 @@ public function report(array $resources = []): array } // Databases - if (in_array(Resource::TYPE_DATABASE, $resources)) { + if (\in_array(Resource::TYPE_DATABASE, $resources)) { $report[Resource::TYPE_DATABASE] = 1; } - if (in_array(Resource::TYPE_COLLECTION, $resources)) { + if (\in_array(Resource::TYPE_COLLECTION, $resources)) { $statement = $db->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\''); $statement->execute(); @@ -140,7 +140,7 @@ public function report(array $resources = []): array $report[Resource::TYPE_COLLECTION] = $statement->fetchColumn(); } - if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) { + if (\in_array(Resource::TYPE_ATTRIBUTE, $resources)) { $statement = $db->prepare('SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = \'public\''); $statement->execute(); @@ -151,7 +151,7 @@ public function report(array $resources = []): array $report[Resource::TYPE_ATTRIBUTE] = $statement->fetchColumn(); } - if (in_array(Resource::TYPE_INDEX, $resources)) { + if (\in_array(Resource::TYPE_INDEX, $resources)) { $statement = $db->prepare('SELECT COUNT(*) FROM pg_indexes WHERE schemaname = \'public\''); $statement->execute(); @@ -162,7 +162,7 @@ public function report(array $resources = []): array $report[Resource::TYPE_INDEX] = $statement->fetchColumn(); } - if (in_array(Resource::TYPE_DOCUMENT, $resources)) { + if (\in_array(Resource::TYPE_DOCUMENT, $resources)) { $statement = $db->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\''); $statement->execute(); @@ -174,7 +174,7 @@ public function report(array $resources = []): array } // Storage - if (in_array(Resource::TYPE_BUCKET, $resources)) { + if (\in_array(Resource::TYPE_BUCKET, $resources)) { $statement = $db->prepare('SELECT COUNT(*) FROM storage.buckets'); $statement->execute(); @@ -185,7 +185,7 @@ public function report(array $resources = []): array $report[Resource::TYPE_BUCKET] = $statement->fetchColumn(); } - if (in_array(Resource::TYPE_FILE, $resources)) { + if (\in_array(Resource::TYPE_FILE, $resources)) { $statement = $db->prepare('SELECT COUNT(*) FROM storage.files'); $statement->execute(); @@ -210,10 +210,10 @@ public function report(array $resources = []): array return $report; } - protected function exportGroupAuth(int $batchSize, array $resources) + protected function exportGroupAuth(int $batchSize, array $resources): void { try { - if (in_array(Resource::TYPE_USER, $resources)) { + if (\in_array(Resource::TYPE_USER, $resources)) { $this->exportUsers($batchSize); } } catch (\Throwable $e) { @@ -224,7 +224,7 @@ protected function exportGroupAuth(int $batchSize, array $resources) } } - private function exportUsers(int $batchSize) + private function exportUsers(int $batchSize): void { $db = $this->getDatabase(); @@ -265,10 +265,10 @@ private function exportUsers(int $batchSize) } } - protected function exportGroupDatabases(int $batchSize, array $resources) + protected function exportGroupDatabases(int $batchSize, array $resources): void { try { - if (in_array(Resource::TYPE_DATABASE, $resources)) { + if (\in_array(Resource::TYPE_DATABASE, $resources)) { $this->exportDatabases($batchSize); } } catch (\Throwable $e) { @@ -281,7 +281,7 @@ protected function exportGroupDatabases(int $batchSize, array $resources) } try { - if (in_array(Resource::TYPE_COLLECTION, $resources)) { + if (\in_array(Resource::TYPE_COLLECTION, $resources)) { $this->exportCollections($batchSize); } } catch (\Throwable $e) { @@ -294,7 +294,7 @@ protected function exportGroupDatabases(int $batchSize, array $resources) } try { - if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) { + if (\in_array(Resource::TYPE_ATTRIBUTE, $resources)) { $this->exportAttributes($batchSize); } } catch (\Throwable $e) { @@ -307,7 +307,7 @@ protected function exportGroupDatabases(int $batchSize, array $resources) } try { - if (in_array(Resource::TYPE_DOCUMENT, $resources)) { + if (\in_array(Resource::TYPE_DOCUMENT, $resources)) { $this->exportDocuments($batchSize); } } catch (\Throwable $e) { @@ -320,7 +320,7 @@ protected function exportGroupDatabases(int $batchSize, array $resources) } try { - if (in_array(Resource::TYPE_INDEX, $resources)) { + if (\in_array(Resource::TYPE_INDEX, $resources)) { $this->exportIndexes($batchSize); } } catch (\Throwable $e) { @@ -341,7 +341,7 @@ private function exportDatabases(int $batchSize): void $this->callback([$transferDatabase]); } - private function exportCollections(int $batchSize) + private function exportCollections(int $batchSize): void { $databases = $this->cache->get(Database::getName()); $db = $this->getDatabase(); @@ -373,7 +373,7 @@ private function exportCollections(int $batchSize) } } - private function exportAttributes(int $batchSize) + private function exportAttributes(int $batchSize): void { $collections = $this->cache->get(Collection::getName()); $db = $this->getDatabase(); @@ -395,7 +395,7 @@ private function exportAttributes(int $batchSize) } } - private function exportIndexes(int $batchSize) + private function exportIndexes(int $batchSize): void { $collections = $this->cache->get(Collection::getName()); $db = $this->getDatabase(); @@ -420,7 +420,7 @@ private function exportIndexes(int $batchSize) } } - private function exportDocuments(int $batchSize) + private function exportDocuments(int $batchSize): void { $databases = $this->cache->get(Database::getName()); $collections = $this->cache->get(Collection::getName()); @@ -609,10 +609,10 @@ private function calculateUserTypes(array $user): array return $types; } - protected function exportGroupStorage(int $batchSize, array $resources) + protected function exportGroupStorage(int $batchSize, array $resources): void { try { - if (in_array(Resource::TYPE_BUCKET, $resources)) { + if (\in_array(Resource::TYPE_BUCKET, $resources)) { $this->exportBuckets($batchSize); } } catch (\Throwable $e) { @@ -625,7 +625,7 @@ protected function exportGroupStorage(int $batchSize, array $resources) } try { - if (in_array(Resource::TYPE_FILE, $resources)) { + if (\in_array(Resource::TYPE_FILE, $resources)) { $this->exportFiles($batchSize); } } catch (\Throwable $e) { @@ -638,7 +638,7 @@ protected function exportGroupStorage(int $batchSize, array $resources) } } - protected function exportBuckets(int $batchSize) + protected function exportBuckets(int $batchSize): void { $db = $this->getDatabase(); $total = $db->query('SELECT COUNT(*) FROM storage.buckets')->fetchColumn(); @@ -668,7 +668,7 @@ protected function exportBuckets(int $batchSize) } } - private function exportFiles(int $batchSize) + private function exportFiles(int $batchSize): void { $buckets = $this->cache->get(Bucket::getName()); $db = $this->getDatabase(); @@ -706,7 +706,7 @@ private function exportFiles(int $batchSize) } } - private function exportFile(File $file) + private function exportFile(File $file): void { $start = 0; $end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1; diff --git a/src/Migration/Sources/Supabase.php b/src/Migration/Sources/Supabase.php index 32397f1..51581a4 100644 --- a/src/Migration/Sources/Supabase.php +++ b/src/Migration/Sources/Supabase.php @@ -248,7 +248,7 @@ public function report(array $resources = []): array } // Auth - if (in_array(Resource::TYPE_USER, $resources)) { + if (\in_array(Resource::TYPE_USER, $resources)) { $statement = $this->pdo->prepare('SELECT COUNT(*) FROM auth.users'); $statement->execute(); @@ -260,11 +260,11 @@ public function report(array $resources = []): array } // Databases - if (in_array(Resource::TYPE_DATABASE, $resources)) { + if (\in_array(Resource::TYPE_DATABASE, $resources)) { $report[Resource::TYPE_DATABASE] = 1; } - if (in_array(Resource::TYPE_COLLECTION, $resources)) { + if (\in_array(Resource::TYPE_COLLECTION, $resources)) { $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\''); $statement->execute(); @@ -275,7 +275,7 @@ public function report(array $resources = []): array $report[Resource::TYPE_COLLECTION] = $statement->fetchColumn(); } - if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) { + if (\in_array(Resource::TYPE_ATTRIBUTE, $resources)) { $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = \'public\''); $statement->execute(); @@ -286,7 +286,7 @@ public function report(array $resources = []): array $report[Resource::TYPE_ATTRIBUTE] = $statement->fetchColumn(); } - if (in_array(Resource::TYPE_INDEX, $resources)) { + if (\in_array(Resource::TYPE_INDEX, $resources)) { $statement = $this->pdo->prepare('SELECT COUNT(*) FROM pg_indexes WHERE schemaname = \'public\''); $statement->execute(); @@ -297,7 +297,7 @@ public function report(array $resources = []): array $report[Resource::TYPE_INDEX] = $statement->fetchColumn(); } - if (in_array(Resource::TYPE_DOCUMENT, $resources)) { + if (\in_array(Resource::TYPE_DOCUMENT, $resources)) { $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\''); $statement->execute(); @@ -309,7 +309,7 @@ public function report(array $resources = []): array } // Storage - if (in_array(Resource::TYPE_BUCKET, $resources)) { + if (\in_array(Resource::TYPE_BUCKET, $resources)) { $statement = $this->pdo->prepare('SELECT COUNT(*) FROM storage.buckets'); $statement->execute(); @@ -320,7 +320,7 @@ public function report(array $resources = []): array $report[Resource::TYPE_BUCKET] = $statement->fetchColumn(); } - if (in_array(Resource::TYPE_FILE, $resources)) { + if (\in_array(Resource::TYPE_FILE, $resources)) { $statement = $this->pdo->prepare('SELECT COUNT(*) FROM storage.objects'); $statement->execute(); @@ -346,10 +346,10 @@ public function report(array $resources = []): array return $report; } - protected function exportGroupAuth(int $batchSize, array $resources) + protected function exportGroupAuth(int $batchSize, array $resources): void { try { - if (in_array(Resource::TYPE_USER, $resources)) { + if (\in_array(Resource::TYPE_USER, $resources)) { $this->exportUsers($batchSize); } } catch (\Throwable $e) { @@ -360,7 +360,7 @@ protected function exportGroupAuth(int $batchSize, array $resources) } } - private function exportUsers(int $batchSize) + private function exportUsers(int $batchSize): void { $total = $this->pdo->query('SELECT COUNT(*) FROM auth.users')->fetchColumn(); @@ -429,10 +429,10 @@ private function calculateAuthTypes(array $user): array return $types; } - protected function exportGroupStorage(int $batchSize, array $resources) + protected function exportGroupStorage(int $batchSize, array $resources): void { try { - if (in_array(Resource::TYPE_BUCKET, $resources)) { + if (\in_array(Resource::TYPE_BUCKET, $resources)) { $this->exportBuckets($batchSize); } } catch (\Throwable $e) { @@ -443,7 +443,7 @@ protected function exportGroupStorage(int $batchSize, array $resources) } try { - if (in_array(Resource::TYPE_FILE, $resources)) { + if (\in_array(Resource::TYPE_FILE, $resources)) { $this->exportFiles($batchSize); } } catch (\Throwable $e) { @@ -454,7 +454,7 @@ protected function exportGroupStorage(int $batchSize, array $resources) } } - protected function exportBuckets(int $batchSize) + protected function exportBuckets(int $batchSize): void { $statement = $this->pdo->prepare('SELECT * FROM storage.buckets order by created_at'); $statement->execute(); @@ -480,7 +480,7 @@ protected function exportBuckets(int $batchSize) $this->callback($transferBuckets); } - public function exportFiles(int $batchSize) + public function exportFiles(int $batchSize): void { /** * TODO: Supabase has folders, with enough folders within folders this could cause us to hit the max name length @@ -524,7 +524,7 @@ public function exportFiles(int $batchSize) } } - public function exportFile(File $file) + public function exportFile(File $file): void { $start = 0; $end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1; diff --git a/src/Migration/Target.php b/src/Migration/Target.php index b8a9574..c54e513 100644 --- a/src/Migration/Target.php +++ b/src/Migration/Target.php @@ -9,13 +9,16 @@ abstract class Target * * @var array */ - protected $headers = [ + protected array $headers = [ 'Content-Type' => '', ]; - public $cache; + public Cache $cache; - public $errors = []; + /** + * @var array<\Exception> + */ + public array $errors = []; protected $endpoint = ''; @@ -31,7 +34,7 @@ public function registerCache(Cache &$cache): void /** * Run Transfer * - * @param string[] $resources Resources to transfer + * @param array $resources Resources to transfer * @param callable $callback Callback to run after transfer */ abstract public function run(array $resources, callable $callback): void; @@ -45,38 +48,45 @@ abstract public function run(array $resources, callable $callback): void; * On Destinations, this function should just return nothing but still check if the API is available. * If any issues are found then an exception should be thrown with an error message. * - * @param string[] $resources Resources to report + * @param array $resources Resources to report + * @return array */ abstract public function report(array $resources = []): array; /** - * Call - * * Make an API call * + * @param string $method + * @param string $path + * @param array $headers + * @param array $params + * @param array $responseHeaders + * @return array|string * @throws \Exception */ - protected function call(string $method, string $path = '', array $headers = [], array $params = [], &$responseHeaders = []): array|string - { - $headers = array_merge($this->headers, $headers); - $ch = curl_init((str_contains($path, 'http') ? $path.(($method == 'GET' && ! empty($params)) ? '?'.http_build_query($params) : '') : $this->endpoint.$path.(($method == 'GET' && ! empty($params)) ? '?'.http_build_query($params) : ''))); - $responseStatus = -1; - $responseType = ''; - $responseBody = ''; - - switch ($headers['Content-Type']) { - case 'application/json': - $query = json_encode($params); - break; - - case 'multipart/form-data': - $query = $this->flatten($params); - break; - - default: - $query = http_build_query($params); - break; - } + protected function call( + string $method, + string $path = '', + array $headers = [], + array $params = [], + array &$responseHeaders = [] + ): array|string { + $headers = \array_merge($this->headers, $headers); + $ch = \curl_init(( + \str_contains($path, 'http') + ? $path.(($method == 'GET' && ! empty($params)) ? '?' . \http_build_query($params) : '') + : $this->endpoint.$path.( + ($method == 'GET' && ! empty($params)) + ? '?' . \http_build_query($params) + : '' + ) + )); + + $query = match ($headers['Content-Type']) { + 'application/json' => \json_encode($params), + 'multipart/form-data' => $this->flatten($params), + default => \http_build_query($params), + }; foreach ($headers as $i => $header) { $headers[] = $i.':'.$header; @@ -84,51 +94,51 @@ protected function call(string $method, string $path = '', array $headers = [], } if ($method === 'HEAD') { - curl_setopt($ch, CURLOPT_NOBODY, true); + \curl_setopt($ch, CURLOPT_NOBODY, true); } else { - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + \curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); } - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_USERAGENT, php_uname('s').'-'.php_uname('r').':php-'.phpversion()); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) { + \curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + \curl_setopt($ch, CURLOPT_USERAGENT, php_uname('s').'-'.php_uname('r').':php-'.phpversion()); + \curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + \curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + \curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) { $len = strlen($header); $header = explode(':', strtolower($header), 2); - if (count($header) < 2) { // ignore invalid headers + if (\count($header) < 2) { // ignore invalid headers return $len; } - $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]); + $responseHeaders[\strtolower(\trim($header[0]))] = \trim($header[1]); return $len; }); if ($method != 'GET') { - curl_setopt($ch, CURLOPT_POSTFIELDS, $query); + \curl_setopt($ch, CURLOPT_POSTFIELDS, $query); } $responseBody = curl_exec($ch); $responseType = $responseHeaders['Content-Type'] ?? $responseHeaders['content-type'] ?? ''; - $responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $responseStatus = \curl_getinfo($ch, CURLINFO_HTTP_CODE); - switch (substr($responseType, 0, strpos($responseType, ';'))) { + switch (\substr($responseType, 0, \strpos($responseType, ';'))) { case 'application/json': - $responseBody = json_decode($responseBody, true); + $responseBody = \json_decode($responseBody, true); break; } - if (curl_errno($ch)) { - throw new \Exception(curl_error($ch)); + if (\curl_errno($ch)) { + throw new \Exception(\curl_error($ch)); } - curl_close($ch); + \curl_close($ch); if ($responseStatus >= 400) { - if (is_array($responseBody)) { - throw new \Exception(json_encode($responseBody)); + if (\is_array($responseBody)) { + throw new \Exception(\json_encode($responseBody)); } else { throw new \Exception($responseStatus.': '.$responseBody); } @@ -139,6 +149,10 @@ protected function call(string $method, string $path = '', array $headers = [], /** * Flatten params array to PHP multiple format + * + * @param array $data + * @param string $prefix + * @return array */ protected function flatten(array $data, string $prefix = ''): array { @@ -147,7 +161,7 @@ protected function flatten(array $data, string $prefix = ''): array foreach ($data as $key => $value) { $finalKey = $prefix ? "{$prefix}[{$key}]" : $key; - if (is_array($value)) { + if (\is_array($value)) { $output += $this->flatten($value, $finalKey); } else { $output[$finalKey] = $value; @@ -160,7 +174,7 @@ protected function flatten(array $data, string $prefix = ''): array /** * Get Errors * - * @returns Error[] + * @returns array */ public function getErrors(): array { @@ -170,7 +184,7 @@ public function getErrors(): array /** * Set Errors * - * @param Error[] $errors + * @param array $errors */ public function setErrors(array $errors): void { diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index e24ee69..7d5bea7 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -4,52 +4,45 @@ class Transfer { - public const GROUP_GENERAL = 'general'; + public const string GROUP_GENERAL = 'general'; - public const GROUP_AUTH = 'auth'; + public const string GROUP_AUTH = 'auth'; - public const GROUP_STORAGE = 'storage'; + public const string GROUP_STORAGE = 'storage'; - public const GROUP_FUNCTIONS = 'functions'; + public const string GROUP_FUNCTIONS = 'functions'; - public const GROUP_DATABASES = 'databases'; + public const string GROUP_DATABASES = 'databases'; - public const GROUP_SETTINGS = 'settings'; + public const string GROUP_SETTINGS = 'settings'; - public const GROUP_AUTH_RESOURCES = [Resource::TYPE_USER, Resource::TYPE_TEAM, Resource::TYPE_MEMBERSHIP, Resource::TYPE_HASH]; + public const array GROUP_AUTH_RESOURCES = [Resource::TYPE_USER, Resource::TYPE_TEAM, Resource::TYPE_MEMBERSHIP, Resource::TYPE_HASH]; - public const GROUP_STORAGE_RESOURCES = [Resource::TYPE_FILE, Resource::TYPE_BUCKET]; + public const array GROUP_STORAGE_RESOURCES = [Resource::TYPE_FILE, Resource::TYPE_BUCKET]; - public const GROUP_FUNCTIONS_RESOURCES = [Resource::TYPE_FUNCTION, Resource::TYPE_ENVIRONMENT_VARIABLE, Resource::TYPE_DEPLOYMENT]; + public const array GROUP_FUNCTIONS_RESOURCES = [Resource::TYPE_FUNCTION, Resource::TYPE_ENVIRONMENT_VARIABLE, Resource::TYPE_DEPLOYMENT]; - public const GROUP_DATABASES_RESOURCES = [Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_INDEX, Resource::TYPE_ATTRIBUTE, Resource::TYPE_DOCUMENT]; + public const array GROUP_DATABASES_RESOURCES = [Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_INDEX, Resource::TYPE_ATTRIBUTE, Resource::TYPE_DOCUMENT]; - public const GROUP_SETTINGS_RESOURCES = []; + public const array GROUP_SETTINGS_RESOURCES = []; - public const ALL_PUBLIC_RESOURCES = [ - Resource::TYPE_USER, Resource::TYPE_TEAM, - Resource::TYPE_MEMBERSHIP, Resource::TYPE_FILE, - Resource::TYPE_BUCKET, Resource::TYPE_FUNCTION, - Resource::TYPE_ENVIRONMENT_VARIABLE, Resource::TYPE_DEPLOYMENT, - Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, - Resource::TYPE_INDEX, Resource::TYPE_ATTRIBUTE, + public const array ALL_PUBLIC_RESOURCES = [ + Resource::TYPE_USER, + Resource::TYPE_TEAM, + Resource::TYPE_MEMBERSHIP, + Resource::TYPE_FILE, + Resource::TYPE_BUCKET, + Resource::TYPE_FUNCTION, + Resource::TYPE_ENVIRONMENT_VARIABLE, + Resource::TYPE_DEPLOYMENT, + Resource::TYPE_DATABASE, + Resource::TYPE_COLLECTION, + Resource::TYPE_INDEX, + Resource::TYPE_ATTRIBUTE, Resource::TYPE_DOCUMENT, ]; - public const STORAGE_MAX_CHUNK_SIZE = 1024 * 1024 * 5; // 5MB - - public function __construct(Source $source, Destination $destination) - { - $this->source = $source; - $this->destination = $destination; - $this->cache = new Cache(); - - $this->source->registerCache($this->cache); - $this->destination->registerCache($this->cache); - $this->destination->setSource($source); - - return $this; - } + public const int|float STORAGE_MAX_CHUNK_SIZE = 1024 * 1024 * 5; // 5MB protected Source $source; @@ -62,15 +55,38 @@ public function __construct(Source $source, Destination $destination) */ protected Cache $cache; + /** + * @var array + */ protected array $options = []; - protected array $callbacks = []; - + /** + * @var array + */ protected array $events = []; + /** + * @var array + */ protected array $resources = []; - public function getStatusCounters() + public function __construct(Source $source, Destination $destination) + { + $this->source = $source; + $this->destination = $destination; + $this->cache = new Cache(); + + $this->source->registerCache($this->cache); + $this->destination->registerCache($this->cache); + $this->destination->setSource($source); + + return $this; + } + + /** + * @return array> + */ + public function getStatusCounters(): array { $status = []; @@ -95,7 +111,7 @@ public function getStatusCounters() foreach ($this->cache->getAll() as $resources) { foreach ($resources as $resource) { - /** @var resource $resource */ + /** @var Resource $resource */ if (isset($status[$resource->getName()])) { $status[$resource->getName()][$resource->getStatus()]++; if ($status[$resource->getName()]['pending'] > 0) { @@ -112,7 +128,7 @@ public function getStatusCounters() } } - // Process Source Errprs + // Process source errors foreach ($this->source->getErrors() as $error) { if (isset($status[$error->getResourceType()])) { $status[$error->getResourceType()][Resource::STATUS_ERROR]++; @@ -139,10 +155,12 @@ public function getStatusCounters() /** * Transfer Resources between adapters + * + * @param array $resources + * @param callable $callback */ public function run(array $resources, callable $callback): void { - // Allows you to push entire groups if you want. $computedResources = []; foreach ($resources as $resource) { @@ -157,7 +175,7 @@ public function run(array $resources, callable $callback): void $this->resources = $computedResources; - $this->destination->run($computedResources, $callback, $this->source); + $this->destination->run($computedResources, $callback); } /** @@ -180,7 +198,8 @@ public function getCurrentResource(): string /** * Get Transfer Report * - * @param string $statusLevel If no status level is provided, all status types will be returned. + * @param string $statusLevel If no status level is provided, all status types will be returned. + * @return array> */ public function getReport(string $statusLevel = ''): array { diff --git a/tests/Migration/E2E/Sources/SupabaseTest.php b/tests/Migration/E2E/Sources/SupabaseTest.php index 66b8025..c79d9fe 100644 --- a/tests/Migration/E2E/Sources/SupabaseTest.php +++ b/tests/Migration/E2E/Sources/SupabaseTest.php @@ -74,7 +74,8 @@ public function testSourceReport() */ public function testRunTransfer($state) { - $this->transfer->run($this->source->getSupportedResources(), + $this->transfer->run( + $this->source->getSupportedResources(), function () { } ); From 291c02715d9360c2b956a743ab3e149cae51fda5 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 27 May 2024 10:07:34 +0300 Subject: [PATCH 040/185] Remove primitive --- src/Migration/Resource.php | 44 +++++++++++++++++++------------------- src/Migration/Transfer.php | 26 +++++++++++----------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/Migration/Resource.php b/src/Migration/Resource.php index d7b14f4..859e8e8 100644 --- a/src/Migration/Resource.php +++ b/src/Migration/Resource.php @@ -4,57 +4,57 @@ abstract class Resource implements \JsonSerializable { - public const string STATUS_PENDING = 'pending'; + public const STATUS_PENDING = 'pending'; - public const string STATUS_SUCCESS = 'success'; + public const STATUS_SUCCESS = 'success'; - public const string STATUS_ERROR = 'error'; + public const STATUS_ERROR = 'error'; - public const string STATUS_SKIPPED = 'skip'; + public const STATUS_SKIPPED = 'skip'; - public const string STATUS_PROCESSING = 'processing'; + public const STATUS_PROCESSING = 'processing'; - public const string STATUS_WARNING = 'warning'; + public const STATUS_WARNING = 'warning'; /** * For some transfers (namely Firebase) we have to keep resources in cache that do not necessarily need to be Transferred * This status is used to mark resources that are not going to be transferred but are still needed for the transfer to work * e.g Documents are required for Database transfers because of schema tracing in firebase */ - public const string STATUS_DISREGARDED = 'disregarded'; + public const STATUS_DISREGARDED = 'disregarded'; // Master Resources - public const string TYPE_BUCKET = 'bucket'; + public const TYPE_BUCKET = 'bucket'; - public const string TYPE_COLLECTION = 'collection'; + public const TYPE_COLLECTION = 'collection'; - public const string TYPE_DATABASE = 'database'; + public const TYPE_DATABASE = 'database'; - public const string TYPE_DOCUMENT = 'document'; + public const TYPE_DOCUMENT = 'document'; - public const string TYPE_FILE = 'file'; + public const TYPE_FILE = 'file'; - public const string TYPE_USER = 'user'; + public const TYPE_USER = 'user'; - public const string TYPE_TEAM = 'team'; + public const TYPE_TEAM = 'team'; - public const string TYPE_MEMBERSHIP = 'membership'; + public const TYPE_MEMBERSHIP = 'membership'; - public const string TYPE_FUNCTION = 'function'; + public const TYPE_FUNCTION = 'function'; - public const string TYPE_INDEX = 'index'; + public const TYPE_INDEX = 'index'; // Children (Resources that are created by other resources) - public const string TYPE_ATTRIBUTE = 'attribute'; + public const TYPE_ATTRIBUTE = 'attribute'; - public const string TYPE_DEPLOYMENT = 'deployment'; + public const TYPE_DEPLOYMENT = 'deployment'; - public const string TYPE_HASH = 'hash'; + public const TYPE_HASH = 'hash'; - public const string TYPE_ENVIRONMENT_VARIABLE = 'environment variable'; + public const TYPE_ENVIRONMENT_VARIABLE = 'environment variable'; - public const array ALL_RESOURCES = [ + public const ALL_RESOURCES = [ self::TYPE_ATTRIBUTE, self::TYPE_BUCKET, self::TYPE_COLLECTION, diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index 7d5bea7..b3af897 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -4,29 +4,29 @@ class Transfer { - public const string GROUP_GENERAL = 'general'; + public const GROUP_GENERAL = 'general'; - public const string GROUP_AUTH = 'auth'; + public const GROUP_AUTH = 'auth'; - public const string GROUP_STORAGE = 'storage'; + public const GROUP_STORAGE = 'storage'; - public const string GROUP_FUNCTIONS = 'functions'; + public const GROUP_FUNCTIONS = 'functions'; - public const string GROUP_DATABASES = 'databases'; + public const GROUP_DATABASES = 'databases'; - public const string GROUP_SETTINGS = 'settings'; + public const GROUP_SETTINGS = 'settings'; - public const array GROUP_AUTH_RESOURCES = [Resource::TYPE_USER, Resource::TYPE_TEAM, Resource::TYPE_MEMBERSHIP, Resource::TYPE_HASH]; + public const GROUP_AUTH_RESOURCES = [Resource::TYPE_USER, Resource::TYPE_TEAM, Resource::TYPE_MEMBERSHIP, Resource::TYPE_HASH]; - public const array GROUP_STORAGE_RESOURCES = [Resource::TYPE_FILE, Resource::TYPE_BUCKET]; + public const GROUP_STORAGE_RESOURCES = [Resource::TYPE_FILE, Resource::TYPE_BUCKET]; - public const array GROUP_FUNCTIONS_RESOURCES = [Resource::TYPE_FUNCTION, Resource::TYPE_ENVIRONMENT_VARIABLE, Resource::TYPE_DEPLOYMENT]; + public const GROUP_FUNCTIONS_RESOURCES = [Resource::TYPE_FUNCTION, Resource::TYPE_ENVIRONMENT_VARIABLE, Resource::TYPE_DEPLOYMENT]; - public const array GROUP_DATABASES_RESOURCES = [Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_INDEX, Resource::TYPE_ATTRIBUTE, Resource::TYPE_DOCUMENT]; + public const GROUP_DATABASES_RESOURCES = [Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_INDEX, Resource::TYPE_ATTRIBUTE, Resource::TYPE_DOCUMENT]; - public const array GROUP_SETTINGS_RESOURCES = []; + public const GROUP_SETTINGS_RESOURCES = []; - public const array ALL_PUBLIC_RESOURCES = [ + public const ALL_PUBLIC_RESOURCES = [ Resource::TYPE_USER, Resource::TYPE_TEAM, Resource::TYPE_MEMBERSHIP, @@ -42,7 +42,7 @@ class Transfer Resource::TYPE_DOCUMENT, ]; - public const int|float STORAGE_MAX_CHUNK_SIZE = 1024 * 1024 * 5; // 5MB + public const STORAGE_MAX_CHUNK_SIZE = 1024 * 1024 * 5; // 5MB protected Source $source; From 8de76fd4de63391a0e76fc9d4ee0bf9731d84c9f Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 2 Jun 2024 10:44:48 +0300 Subject: [PATCH 041/185] + updateLimits --- src/Migration/Resources/Storage/Bucket.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Migration/Resources/Storage/Bucket.php b/src/Migration/Resources/Storage/Bucket.php index 72916ef..6db8f32 100644 --- a/src/Migration/Resources/Storage/Bucket.php +++ b/src/Migration/Resources/Storage/Bucket.php @@ -30,6 +30,7 @@ public function __construct( private readonly string $compression = 'none', private readonly bool $encryption = false, private readonly bool $antiVirus = false, + private readonly bool $updateLimits = false, ) { $this->id = $id; $this->permissions = $permissions; From b37911e7504d4376b864d9656a37f7161bd9e8c7 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 2 Jun 2024 11:05:02 +0300 Subject: [PATCH 042/185] add hint --- src/Migration/Resources/Storage/Bucket.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Migration/Resources/Storage/Bucket.php b/src/Migration/Resources/Storage/Bucket.php index 6db8f32..f454161 100644 --- a/src/Migration/Resources/Storage/Bucket.php +++ b/src/Migration/Resources/Storage/Bucket.php @@ -18,6 +18,7 @@ class Bucket extends Resource * @param string $compression * @param bool $encryption * @param bool $antiVirus + * @param bool $updateLimits */ public function __construct( string $id = '', From 1de3ba46194c75dda8dede1f9ac4381b9d1e0c69 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 2 Jun 2024 18:53:49 +0300 Subject: [PATCH 043/185] Questions --- src/Migration/Resources/Storage/File.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Migration/Resources/Storage/File.php b/src/Migration/Resources/Storage/File.php index d9bd64b..925600e 100644 --- a/src/Migration/Resources/Storage/File.php +++ b/src/Migration/Resources/Storage/File.php @@ -43,7 +43,7 @@ public static function fromArray(array $array): self { return new self( $array['id'], - Bucket::fromArray($array['bucket']), + Bucket::fromArray($array['bucket']), // Do we need here only the BucketId? $array['name'] ?? '', $array['signature'] ?? '', $array['mimeType'] ?? '', From cf4c64878c0ae6937aa821459474041cf70b2620 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 17 Jun 2024 11:24:01 +0300 Subject: [PATCH 044/185] extractServices --- src/Migration/Transfer.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index b3af897..62738a6 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -224,4 +224,26 @@ public function getReport(string $statusLevel = ''): array return $report; } + + /** + * @throws \Exception + */ + public function extractServices(array $services):array + { + $resources = []; + foreach ($services as $service) { + var_dump("converting resource === " . $service); + $resources = match ($service) { + self::GROUP_FUNCTIONS => array_merge($resources, self::GROUP_FUNCTIONS_RESOURCES), + self::GROUP_STORAGE => array_merge($resources, self::GROUP_STORAGE_RESOURCES), + 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_SETTINGS => array_merge($resources, self::GROUP_SETTINGS_RESOURCES), + default => throw new \Exception('No service group found'), + }; + } + + return $resources; + } } From ce55838583cc237aabd9bdfc898229f17cef130c Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 17 Jun 2024 11:26:44 +0300 Subject: [PATCH 045/185] make it static --- src/Migration/Transfer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index 62738a6..837b279 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -228,7 +228,7 @@ public function getReport(string $statusLevel = ''): array /** * @throws \Exception */ - public function extractServices(array $services):array + public static function extractServices(array $services):array { $resources = []; foreach ($services as $service) { From 84418d4e682f239895c23c77da98c01905657af4 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 23 Jun 2024 11:37:17 +0300 Subject: [PATCH 046/185] Source DBG's --- src/Migration/Source.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Migration/Source.php b/src/Migration/Source.php index 5a68aac..0ef8330 100644 --- a/src/Migration/Source.php +++ b/src/Migration/Source.php @@ -29,18 +29,22 @@ public function callback(array $resources): void public function run(array $resources, callable $callback): void { $this->transferCallback = function (array $returnedResources) use ($callback, $resources) { - $prunedResurces = []; + $prunedResources = []; foreach ($returnedResources as $resource) { - /** @var resource $resource */ + /** @var Resource $resource */ + var_dump($resource->getName()); + var_dump('Source::run method'); + var_dump($resources); + if (! in_array($resource->getName(), $resources)) { $resource->setStatus(Resource::STATUS_SKIPPED); } else { - $prunedResurces[] = $resource; + $prunedResources[] = $resource; } } $callback($returnedResources); - $this->cache->addAll($prunedResurces); + $this->cache->addAll($prunedResources); }; $this->exportResources($resources, 100); From 9dbd7c1c05dc25b68849296bea49efd5ca6ae3cc Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 23 Jun 2024 12:36:53 +0300 Subject: [PATCH 047/185] Source DBG's --- src/Migration/Source.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Migration/Source.php b/src/Migration/Source.php index 0ef8330..46acdf6 100644 --- a/src/Migration/Source.php +++ b/src/Migration/Source.php @@ -32,11 +32,12 @@ public function run(array $resources, callable $callback): void $prunedResources = []; foreach ($returnedResources as $resource) { /** @var Resource $resource */ - var_dump($resource->getName()); var_dump('Source::run method'); + var_dump($resource->getName()); var_dump($resources); if (! in_array($resource->getName(), $resources)) { + var_dump('setStatus === ' . Resource::STATUS_SKIPPED); $resource->setStatus(Resource::STATUS_SKIPPED); } else { $prunedResources[] = $resource; From 80c56b58f9fec60dae19ec2e775fc7536dde1061 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 23 Jun 2024 12:50:34 +0300 Subject: [PATCH 048/185] Question --- src/Migration/Cache.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Migration/Cache.php b/src/Migration/Cache.php index 86ab231..e9d2de1 100644 --- a/src/Migration/Cache.php +++ b/src/Migration/Cache.php @@ -34,6 +34,7 @@ public function add(Resource $resource): void $resourceId = uniqid(); if (isset($this->cache[$resource->getName()][$resourceId])) { $resourceId = uniqid(); + // todo: $resourceId is not used? } $resource->setInternalId(uniqid()); } From bf262a72a58912d3f1f6032e0173e7244d0c542e Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 23 Jun 2024 13:09:25 +0300 Subject: [PATCH 049/185] Question --- src/Migration/Resources/Database/Database.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Migration/Resources/Database/Database.php b/src/Migration/Resources/Database/Database.php index ab9d435..6aefd1f 100644 --- a/src/Migration/Resources/Database/Database.php +++ b/src/Migration/Resources/Database/Database.php @@ -20,6 +20,7 @@ public function __construct( private readonly string $name = '', ) { $this->id = $id; + // Do we need ? $this->name = $name } /** From 4b831e640181a22de7d56632d46470214fa5241b Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 23 Jun 2024 13:11:29 +0300 Subject: [PATCH 050/185] Question --- src/Migration/Resources/Database/Database.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Migration/Resources/Database/Database.php b/src/Migration/Resources/Database/Database.php index 6aefd1f..ab9d435 100644 --- a/src/Migration/Resources/Database/Database.php +++ b/src/Migration/Resources/Database/Database.php @@ -20,7 +20,6 @@ public function __construct( private readonly string $name = '', ) { $this->id = $id; - // Do we need ? $this->name = $name } /** From 351a8308975fe24009a626f8d38fc71387549cf5 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 23 Jun 2024 13:17:05 +0300 Subject: [PATCH 051/185] Appwrite import --- src/Migration/Destinations/Appwrite.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 41733d5..554abba 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -232,6 +232,9 @@ public function report(array $resources = []): array #[Override] protected function import(array $resources, callable $callback): void { + var_dump("Appwrite importing......."); + var_dump($resources); + if (empty($resources)) { return; } @@ -248,6 +251,9 @@ protected function import(array $resources, callable $callback): void default => throw new \Exception('Invalid resource group'), }; } catch (\Throwable $e) { + + var_dump("Appwrite import Throwable"); + if ($e->getCode() === 409) { $resource->setStatus(Resource::STATUS_SKIPPED, $e->getMessage()); } else { From 71fd8b8ac22c72f8eab279c5ba1f8e1dbc722601 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 23 Jun 2024 13:21:44 +0300 Subject: [PATCH 052/185] Question? --- src/Migration/Resources/Database/Database.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Migration/Resources/Database/Database.php b/src/Migration/Resources/Database/Database.php index ab9d435..05dcc38 100644 --- a/src/Migration/Resources/Database/Database.php +++ b/src/Migration/Resources/Database/Database.php @@ -20,6 +20,7 @@ public function __construct( private readonly string $name = '', ) { $this->id = $id; + // Do we need to $this->name = $name; } /** From 06a3277b30910c48c43bc239be8dce2a9015a28d Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 23 Jun 2024 13:39:20 +0300 Subject: [PATCH 053/185] Create Database --- src/Migration/Destinations/Appwrite.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 554abba..88ae789 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -286,10 +286,12 @@ public function importDatabaseResource(Resource $resource): Resource switch ($resource->getName()) { case Resource::TYPE_DATABASE: /** @var Database $resource */ - $this->databases->create( + $response = $this->databases->create( $resource->getId(), $resource->getDBName() ); + //todo: Do we need to check response codes? + var_dump($response); break; case Resource::TYPE_COLLECTION: /** @var Collection $resource */ From 23553bcf45f4b9657550153078d37123dfc94302 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 23 Jun 2024 13:52:00 +0300 Subject: [PATCH 054/185] importDatabaseResource --- src/Migration/Destinations/Appwrite.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 88ae789..4095735 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -254,7 +254,7 @@ protected function import(array $resources, callable $callback): void var_dump("Appwrite import Throwable"); - if ($e->getCode() === 409) { + if ($e->getCode() === 409) { $resource->setStatus(Resource::STATUS_SKIPPED, $e->getMessage()); } else { $resource->setStatus(Resource::STATUS_ERROR, $e->getMessage()); @@ -281,6 +281,9 @@ protected function import(array $resources, callable $callback): void */ public function importDatabaseResource(Resource $resource): Resource { + var_dump("importDatabaseResource" . $resource->getName()); + var_dump("importDatabaseResource - "); + $this->databases = new Databases($this->client); switch ($resource->getName()) { From fdddb5f256936a20076a4e0a8dcd27702e1e87ca Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 23 Jun 2024 13:55:26 +0300 Subject: [PATCH 055/185] dbgs --- src/Migration/Destinations/Appwrite.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 4095735..0da4e57 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -252,7 +252,11 @@ protected function import(array $resources, callable $callback): void }; } catch (\Throwable $e) { - var_dump("Appwrite import Throwable"); + var_dump("Appwrite import Throwable ==== "); + var_dump("getCode ==== "); + var_dump($e->getCode()); + var_dump("getMessage ==== "); + var_dump($e->getMessage()); if ($e->getCode() === 409) { $resource->setStatus(Resource::STATUS_SKIPPED, $e->getMessage()); From 89a7be3cd9d3a0ca5d852854d11acd9869d1f02c Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 23 Jun 2024 13:56:09 +0300 Subject: [PATCH 056/185] Do we need to check API response codes? --- src/Migration/Destinations/Appwrite.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 0da4e57..deb09b0 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -297,7 +297,7 @@ public function importDatabaseResource(Resource $resource): Resource $resource->getId(), $resource->getDBName() ); - //todo: Do we need to check response codes? + //todo: Do we need to check API response codes? var_dump($response); break; case Resource::TYPE_COLLECTION: From cc6247592de5ffbf73e94935881b79e83f0e7c2f Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 23 Jun 2024 14:15:51 +0300 Subject: [PATCH 057/185] Remove comment --- src/Migration/Destinations/Appwrite.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index deb09b0..5b165a7 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -259,6 +259,7 @@ protected function import(array $resources, callable $callback): void var_dump($e->getMessage()); if ($e->getCode() === 409) { + // DATABASE_ALREADY_EXISTS why SKIP? not termination $resource->setStatus(Resource::STATUS_SKIPPED, $e->getMessage()); } else { $resource->setStatus(Resource::STATUS_ERROR, $e->getMessage()); @@ -293,12 +294,10 @@ public function importDatabaseResource(Resource $resource): Resource switch ($resource->getName()) { case Resource::TYPE_DATABASE: /** @var Database $resource */ - $response = $this->databases->create( + $this->databases->create( $resource->getId(), $resource->getDBName() ); - //todo: Do we need to check API response codes? - var_dump($response); break; case Resource::TYPE_COLLECTION: /** @var Collection $resource */ From 397c94c8a3eeb9142774a1308fff69b0f555d275 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 24 Jun 2024 12:12:37 +0300 Subject: [PATCH 058/185] jsonSerialize Collection add id --- src/Migration/Resources/Database/Collection.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Migration/Resources/Database/Collection.php b/src/Migration/Resources/Database/Collection.php index b32d0ee..fc67f98 100644 --- a/src/Migration/Resources/Database/Collection.php +++ b/src/Migration/Resources/Database/Collection.php @@ -46,6 +46,7 @@ public function jsonSerialize(): array { return array_merge([ 'database' => $this->database, + 'id' => $this->id, 'name' => $this->name, 'documentSecurity' => $this->documentSecurity, 'permissions' => $this->permissions, From 13448d15a5e6c6f49d0f101b31d3eb6c545a6908 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 25 Jun 2024 11:08:43 +0300 Subject: [PATCH 059/185] lowercase --- src/Migration/Resources/Database/Attribute.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Migration/Resources/Database/Attribute.php b/src/Migration/Resources/Database/Attribute.php index 6c5982b..ac698d4 100644 --- a/src/Migration/Resources/Database/Attribute.php +++ b/src/Migration/Resources/Database/Attribute.php @@ -9,21 +9,22 @@ abstract class Attribute extends Resource { public const string TYPE_STRING = 'string'; + public const string TYPE_INTEGER = 'int'; public const string TYPE_FLOAT = 'float'; public const string TYPE_BOOLEAN = 'bool'; - public const string TYPE_DATETIME = 'dateTime'; + public const string TYPE_DATETIME = 'datetime'; public const string TYPE_EMAIL = 'email'; public const string TYPE_ENUM = 'enum'; - public const string TYPE_IP = 'IP'; + public const string TYPE_IP = 'ip'; - public const string TYPE_URL = 'URL'; + public const string TYPE_URL = 'url'; public const string TYPE_RELATIONSHIP = 'relationship'; From 0cc2ef85c363d23bdc7525c91e8e5ae08d58ebfa Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 25 Jun 2024 11:50:58 +0300 Subject: [PATCH 060/185] Revert names --- src/Migration/Resources/Database/Attribute.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Migration/Resources/Database/Attribute.php b/src/Migration/Resources/Database/Attribute.php index ac698d4..9e02c92 100644 --- a/src/Migration/Resources/Database/Attribute.php +++ b/src/Migration/Resources/Database/Attribute.php @@ -16,15 +16,15 @@ abstract class Attribute extends Resource public const string TYPE_BOOLEAN = 'bool'; - public const string TYPE_DATETIME = 'datetime'; + public const string TYPE_DATETIME = 'dateTime'; public const string TYPE_EMAIL = 'email'; public const string TYPE_ENUM = 'enum'; - public const string TYPE_IP = 'ip'; + public const string TYPE_IP = 'IP'; - public const string TYPE_URL = 'url'; + public const string TYPE_URL = 'URL'; public const string TYPE_RELATIONSHIP = 'relationship'; From 1fd7e1a77db3c8a06d4edfdc328b4d933d44d704 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 25 Jun 2024 13:18:04 +0300 Subject: [PATCH 061/185] null defaults --- src/Migration/Sources/Appwrite.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 64765ba..82f1588 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -756,8 +756,8 @@ private function convertAttribute(array $value, Collection $collection): Attribu $value['required'], $value['array'], $value['default'], - $value['min'] ?? 0, - $value['max'] ?? 0 + $value['min'] ?? null, + $value['max'] ?? null ); case 'double': return new Decimal( @@ -766,8 +766,8 @@ private function convertAttribute(array $value, Collection $collection): Attribu $value['required'], $value['array'], $value['default'], - $value['min'] ?? 0, - $value['max'] ?? 0 + $value['min'] ?? null, + $value['max'] ?? null ); case 'relationship': return new Relationship( From e031a47385b3b0c8635ba27fdca51e209f754bf3 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 25 Jun 2024 13:24:32 +0300 Subject: [PATCH 062/185] add comment createFloatAttribute --- src/Migration/Destinations/Appwrite.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 5b165a7..2abc882 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -391,6 +391,8 @@ public function createAttribute(Attribute $attribute): void break; case Attribute::TYPE_FLOAT: /** @var Decimal $attribute */ + // todo: Change createFloatAttribute min/max to accept float!!! + $this->databases->createFloatAttribute( $attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), From 3e452f4689d0cddd2325e92ddff79b9cdb550f1a Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 26 Jun 2024 10:22:53 +0300 Subject: [PATCH 063/185] appwrite/appwrite 11.1.* --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d479ccc..5006659 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "php": "8.3", "ext-curl": "*", "ext-openssl": "*", - "appwrite/appwrite": "10.1.*", + "appwrite/appwrite": "11.1.*", "utopia-php/cli": "0.15.*", "utopia-php/database": "0.49.*", "utopia-php/storage": "0.18.*", From 2b491d582f316ca7670c4c23f01259482c0ff2a3 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 26 Jun 2024 10:48:24 +0300 Subject: [PATCH 064/185] Move shutdown to target --- src/Migration/Destination.php | 14 -------------- src/Migration/Target.php | 7 +++++++ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/Migration/Destination.php b/src/Migration/Destination.php index f2205a9..d07c15d 100644 --- a/src/Migration/Destination.php +++ b/src/Migration/Destination.php @@ -41,18 +41,4 @@ public function run(array $resources, callable $callback): void * @param callable $callback Callback to run after import */ abstract protected function import(array $resources, callable $callback): void; - - /** - * Init function - */ - public function init(): void - { - } - - /** - * shutDown function - */ - public function shutdown(): void - { - } } diff --git a/src/Migration/Target.php b/src/Migration/Target.php index c54e513..ca1fef1 100644 --- a/src/Migration/Target.php +++ b/src/Migration/Target.php @@ -195,4 +195,11 @@ public function addError(Exception $error): void { $this->errors[] = $error; } + + /** + * shutDown function + */ + public function shutdown(): void + { + } } From 48e8749e4010df5b26a3e05cb5ba6b5feac9ec31 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 26 Jun 2024 16:59:05 +0300 Subject: [PATCH 065/185] Use Id's --- src/Migration/Resources/Database/Attribute.php | 3 ++- src/Migration/Resources/Database/Collection.php | 3 ++- src/Migration/Resources/Database/Document.php | 5 +++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Migration/Resources/Database/Attribute.php b/src/Migration/Resources/Database/Attribute.php index 9e02c92..3b3e8a4 100644 --- a/src/Migration/Resources/Database/Attribute.php +++ b/src/Migration/Resources/Database/Attribute.php @@ -45,7 +45,8 @@ public function jsonSerialize(): array return [ 'key' => $this->key, 'type' => $this->getTypeName(), - 'collection' => $this->collection, + //'collection' => $this->collection, + 'collectionId' => $this->collection->getId(), 'required' => $this->required, 'array' => $this->array, ]; diff --git a/src/Migration/Resources/Database/Collection.php b/src/Migration/Resources/Database/Collection.php index fc67f98..daa9c5b 100644 --- a/src/Migration/Resources/Database/Collection.php +++ b/src/Migration/Resources/Database/Collection.php @@ -45,7 +45,8 @@ public static function fromArray(array $array): self public function jsonSerialize(): array { return array_merge([ - 'database' => $this->database, + //'database' => $this->database, + 'databaseId' => $this->database->getId(), 'id' => $this->id, 'name' => $this->name, 'documentSecurity' => $this->documentSecurity, diff --git a/src/Migration/Resources/Database/Document.php b/src/Migration/Resources/Database/Document.php index 6af9fda..709a4da 100644 --- a/src/Migration/Resources/Database/Document.php +++ b/src/Migration/Resources/Database/Document.php @@ -46,8 +46,9 @@ public function jsonSerialize(): array { return [ 'id' => $this->id, - 'database' => $this->database, - 'collection' => $this->collection, + //'database' => $this->database, + //'collection' => $this->collection, + 'collectionId' => $this->collection->getId(), 'attributes' => $this->data, 'permissions' => $this->permissions, ]; From ff7f50b973a60845cd66acccccf47f39f92a7439 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 26 Jun 2024 17:56:39 +0300 Subject: [PATCH 066/185] Question --- src/Migration/Resources/Database/Document.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Migration/Resources/Database/Document.php b/src/Migration/Resources/Database/Document.php index 709a4da..1be3441 100644 --- a/src/Migration/Resources/Database/Document.php +++ b/src/Migration/Resources/Database/Document.php @@ -16,7 +16,7 @@ class Document extends Resource */ public function __construct( string $id, - private readonly Database $database, + private readonly Database $database, // why do we need this while it is a part of collection? private readonly Collection $collection, private readonly array $data = [], array $permissions = [] From 72b583285aede13d37c5668f1146b7fd9517427c Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 27 Jun 2024 10:26:01 +0300 Subject: [PATCH 067/185] IndexType --- src/Migration/Destinations/Appwrite.php | 6 +++--- src/Migration/Resources/Database/Attribute.php | 4 ++-- src/Migration/Resources/Database/Collection.php | 4 ++-- src/Migration/Resources/Database/Document.php | 8 ++------ src/Migration/Resources/Database/Index.php | 14 ++++++++++++++ src/Migration/Sources/Appwrite.php | 1 - src/Migration/Sources/Firebase.php | 2 +- src/Migration/Sources/NHost.php | 2 +- src/Migration/Transfer.php | 2 +- 9 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 2abc882..111d9a2 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -283,11 +283,11 @@ protected function import(array $resources, callable $callback): void /** * @throws AppwriteException + * @throws \Exception */ public function importDatabaseResource(Resource $resource): Resource { - var_dump("importDatabaseResource" . $resource->getName()); - var_dump("importDatabaseResource - "); + var_dump("Destination Appwrite::importDatabaseResource === " . $resource->getName()); $this->databases = new Databases($this->client); @@ -316,7 +316,7 @@ public function importDatabaseResource(Resource $resource): Resource $resource->getCollection()->getDatabase()->getId(), $resource->getCollection()->getId(), $resource->getKey(), - $resource->getType(), + Index::getIndexType($resource->getType()), $resource->getAttributes(), $resource->getOrders() ); diff --git a/src/Migration/Resources/Database/Attribute.php b/src/Migration/Resources/Database/Attribute.php index 3b3e8a4..98edfb0 100644 --- a/src/Migration/Resources/Database/Attribute.php +++ b/src/Migration/Resources/Database/Attribute.php @@ -45,8 +45,8 @@ public function jsonSerialize(): array return [ 'key' => $this->key, 'type' => $this->getTypeName(), - //'collection' => $this->collection, - 'collectionId' => $this->collection->getId(), + 'collection' => $this->collection, + //'collectionId' => $this->collection->getId(), 'required' => $this->required, 'array' => $this->array, ]; diff --git a/src/Migration/Resources/Database/Collection.php b/src/Migration/Resources/Database/Collection.php index daa9c5b..ee5fb57 100644 --- a/src/Migration/Resources/Database/Collection.php +++ b/src/Migration/Resources/Database/Collection.php @@ -45,8 +45,8 @@ public static function fromArray(array $array): self public function jsonSerialize(): array { return array_merge([ - //'database' => $this->database, - 'databaseId' => $this->database->getId(), + 'database' => $this->database, + //'databaseId' => $this->database->getId(), 'id' => $this->id, 'name' => $this->name, 'documentSecurity' => $this->documentSecurity, diff --git a/src/Migration/Resources/Database/Document.php b/src/Migration/Resources/Database/Document.php index 1be3441..5416f47 100644 --- a/src/Migration/Resources/Database/Document.php +++ b/src/Migration/Resources/Database/Document.php @@ -9,14 +9,12 @@ class Document extends Resource { /** * @param string $id - * @param Database $database * @param Collection $collection * @param array $data * @param array $permissions */ public function __construct( string $id, - private readonly Database $database, // why do we need this while it is a part of collection? private readonly Collection $collection, private readonly array $data = [], array $permissions = [] @@ -32,7 +30,6 @@ public static function fromArray(array $array): self { return new self( $array['id'], - Database::fromArray($array['database']), Collection::fromArray($array['collection']), $array['attributes'], $array['permissions'] ?? [] @@ -46,9 +43,8 @@ public function jsonSerialize(): array { return [ 'id' => $this->id, - //'database' => $this->database, - //'collection' => $this->collection, - 'collectionId' => $this->collection->getId(), + 'collection' => $this->collection, + //'collectionId' => $this->collection->getId(), 'attributes' => $this->data, 'permissions' => $this->permissions, ]; diff --git a/src/Migration/Resources/Database/Index.php b/src/Migration/Resources/Database/Index.php index a7a64f8..4d12aee 100644 --- a/src/Migration/Resources/Database/Index.php +++ b/src/Migration/Resources/Database/Index.php @@ -2,6 +2,7 @@ namespace Utopia\Migration\Resources\Database; +use Appwrite\Enums\IndexType; use Utopia\Migration\Resource; use Utopia\Migration\Transfer; @@ -105,4 +106,17 @@ public function getOrders(): array { return $this->orders; } + + /** + * @throws \Exception + */ + public static function getIndexType(string $type):IndexType + { + return match ($type) { + Index::TYPE_KEY => IndexType::KEY(), + Index::TYPE_UNIQUE => IndexType::UNIQUE(), + Index::TYPE_FULLTEXT => IndexType::FULLTEXT(), + default => throw new \Exception('Invalid IndexType: ' . $type), + }; + } } diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 82f1588..64b76f7 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -660,7 +660,6 @@ private function exportDocuments(int $batchSize): void $documents[] = new Document( $id, - $collection->getDatabase(), $collection, $cleanData, $permissions diff --git a/src/Migration/Sources/Firebase.php b/src/Migration/Sources/Firebase.php index cd1ad1c..859cb68 100644 --- a/src/Migration/Sources/Firebase.php +++ b/src/Migration/Sources/Firebase.php @@ -538,7 +538,7 @@ private function convertDocument(Collection $collection, array $document): Docum $documentId = preg_replace("/[^A-Za-z0-9\_\-]/", '', $documentId); $documentId = strtolower($documentId); - return new Document($documentId, $collection->getDatabase(), $collection, $data, []); + return new Document($documentId, $collection, $data, []); } protected function exportGroupStorage(int $batchSize, array $resources): void diff --git a/src/Migration/Sources/NHost.php b/src/Migration/Sources/NHost.php index 16f0867..8a5ea0e 100644 --- a/src/Migration/Sources/NHost.php +++ b/src/Migration/Sources/NHost.php @@ -468,7 +468,7 @@ private function exportDocuments(int $batchSize): void } } - $transferDocuments[] = new Document('unique()', $database, $collection, $processedData); + $transferDocuments[] = new Document('unique()', $collection, $processedData); } $this->callback($transferDocuments); diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index 837b279..87397e0 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -228,7 +228,7 @@ public function getReport(string $statusLevel = ''): array /** * @throws \Exception */ - public static function extractServices(array $services):array + public static function extractServices(array $services): array { $resources = []; foreach ($services as $service) { From 66f26dc291b51e179a2d2a84720a0e3377a54957 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 27 Jun 2024 15:19:06 +0300 Subject: [PATCH 068/185] Remove getDatabase --- src/Migration/Destinations/Appwrite.php | 2 +- src/Migration/Resources/Database/Document.php | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 111d9a2..b946e35 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -343,7 +343,7 @@ public function importDatabaseResource(Resource $resource): Resource } $this->databases->createDocument( - $resource->getDatabase()->getId(), + $resource->getCollection()->getDatabase()->getId(), $resource->getCollection()->getId(), $resource->getId(), $resource->getData(), diff --git a/src/Migration/Resources/Database/Document.php b/src/Migration/Resources/Database/Document.php index 5416f47..230f696 100644 --- a/src/Migration/Resources/Database/Document.php +++ b/src/Migration/Resources/Database/Document.php @@ -44,7 +44,6 @@ public function jsonSerialize(): array return [ 'id' => $this->id, 'collection' => $this->collection, - //'collectionId' => $this->collection->getId(), 'attributes' => $this->data, 'permissions' => $this->permissions, ]; @@ -60,11 +59,6 @@ public function getGroup(): string return Transfer::GROUP_DATABASES; } - public function getDatabase(): Database - { - return $this->database; - } - public function getCollection(): Collection { return $this->collection; From 80d03a6fbbcb2885d284a2405955b8a834e4410d Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 27 Jun 2024 16:50:46 +0300 Subject: [PATCH 069/185] Add id to Index.php --- src/Migration/Resources/Database/Index.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Migration/Resources/Database/Index.php b/src/Migration/Resources/Database/Index.php index 4d12aee..7a4146f 100644 --- a/src/Migration/Resources/Database/Index.php +++ b/src/Migration/Resources/Database/Index.php @@ -57,6 +57,7 @@ public static function fromArray(array $array): self public function jsonSerialize(): array { return [ + 'id' => $this->getId(), 'key' => $this->key, 'collection' => $this->collection, 'type' => $this->type, From d253dbd9920295b28391500c5e6950543aff19bb Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 30 Jun 2024 13:49:42 +0300 Subject: [PATCH 070/185] Compression storage type --- src/Migration/Destinations/Appwrite.php | 22 ++++++++++++++++++++-- src/Migration/Resources/Database/Index.php | 13 ------------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index b946e35..9b5eff8 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -4,6 +4,8 @@ use Appwrite\AppwriteException; use Appwrite\Client; +use Appwrite\Enums\Compression; +use Appwrite\Enums\IndexType; use Appwrite\InputFile; use Appwrite\Services\Databases; use Appwrite\Services\Functions; @@ -312,11 +314,19 @@ public function importDatabaseResource(Resource $resource): Resource break; case Resource::TYPE_INDEX: /** @var Index $resource */ + + $type = match ($resource->getType()) { + Index::TYPE_KEY => IndexType::KEY(), + Index::TYPE_UNIQUE => IndexType::UNIQUE(), + Index::TYPE_FULLTEXT => IndexType::FULLTEXT(), + default => throw new \Exception('Invalid IndexType: ' . $resource->getType()), + }; + $this->databases->createIndex( $resource->getCollection()->getDatabase()->getId(), $resource->getCollection()->getId(), $resource->getKey(), - Index::getIndexType($resource->getType()), + $type, $resource->getAttributes(), $resource->getOrders() ); @@ -524,6 +534,14 @@ public function importFileResource(Resource $resource): Resource return $this->importFile($resource); case Resource::TYPE_BUCKET: /** @var Bucket $resource */ + + $compression = match ($resource->getCompression()) { + 'none' => Compression::NONE(), + 'gzip' => Compression::GZIP(), + 'zstd' => Compression::ZSTD(), + default => throw new \Exception('Invalid Compression: ' . $resource->getCompression()), + }; + $response = $this->storage->createBucket( $resource->getId(), $resource->getBucketName(), @@ -532,7 +550,7 @@ public function importFileResource(Resource $resource): Resource $resource->getEnabled(), $resource->getMaxFileSize(), $resource->getAllowedFileExtensions(), - $resource->getCompression(), + $compression, $resource->getEncryption(), $resource->getAntiVirus() ); diff --git a/src/Migration/Resources/Database/Index.php b/src/Migration/Resources/Database/Index.php index 7a4146f..9e06cb7 100644 --- a/src/Migration/Resources/Database/Index.php +++ b/src/Migration/Resources/Database/Index.php @@ -107,17 +107,4 @@ public function getOrders(): array { return $this->orders; } - - /** - * @throws \Exception - */ - public static function getIndexType(string $type):IndexType - { - return match ($type) { - Index::TYPE_KEY => IndexType::KEY(), - Index::TYPE_UNIQUE => IndexType::UNIQUE(), - Index::TYPE_FULLTEXT => IndexType::FULLTEXT(), - default => throw new \Exception('Invalid IndexType: ' . $type), - }; - } } From 375ccd3b26c4ceb9764a21dc3cb85234dea768d1 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 1 Jul 2024 16:16:23 +0300 Subject: [PATCH 071/185] Change dbg --- src/Migration/Destinations/Appwrite.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 9b5eff8..c696241 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -234,14 +234,15 @@ public function report(array $resources = []): array #[Override] protected function import(array $resources, callable $callback): void { - var_dump("Appwrite importing......."); - var_dump($resources); + var_dump("Destinations/Appwrite import...."); if (empty($resources)) { return; } foreach ($resources as $resource) { + var_dump("Importing......." . $resource->getGroup() . ' - ' . $resource->getName()); + $resource->setStatus(Resource::STATUS_PROCESSING); try { From 8121117d24f56bc0b789e4bfeddfefba99068635 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 3 Jul 2024 10:40:47 +0300 Subject: [PATCH 072/185] Fix Runtime Enums --- src/Migration/Destinations/Appwrite.php | 56 ++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index c696241..64e975e 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -6,6 +6,7 @@ use Appwrite\Client; use Appwrite\Enums\Compression; use Appwrite\Enums\IndexType; +use Appwrite\Enums\Runtime; use Appwrite\InputFile; use Appwrite\Services\Databases; use Appwrite\Services\Functions; @@ -808,10 +809,63 @@ public function importFunctionResource(Resource $resource): Resource switch ($resource->getName()) { case Resource::TYPE_FUNCTION: /** @var Func $resource */ + + $runtype = match ($resource->getRuntime()) { + 'node-14.5' => Runtime::NODE145(), + 'node-16.0' => Runtime::NODE160(), + 'node-18.0' => Runtime::NODE180(), + 'node-19.0' => Runtime::NODE190(), + 'node-20.0' => Runtime::NODE200(), + 'node-21.0' => Runtime::NODE210(), + 'php-8.0' => Runtime::PHP80(), + 'php-8.1' => Runtime::PHP81(), + 'php-8.2' => Runtime::PHP82(), + 'php-8.3' => Runtime::PHP83(), + 'ruby-3.0' => Runtime::RUBY30(), + 'ruby-3.1' => Runtime::RUBY31(), + 'ruby-3.2' => Runtime::RUBY32(), + 'ruby-3.3' => Runtime::RUBY33(), + 'python-3.8' => Runtime::PYTHON38(), + 'python-3.9' => Runtime::PYTHON39(), + 'python-3.10' => Runtime::PYTHON310(), + 'python-3.11' => Runtime::PYTHON311(), + 'python-3.12' => Runtime::PYTHON312(), + 'python-ml-3.11' => Runtime::PYTHONML311(), + 'dart-3.0' => Runtime::DART30(), + 'dart-3.1' => Runtime::DART31(), + 'dart-3.3' => Runtime::DART33(), + 'dart-2.15' => Runtime::DART215(), + 'dart-2.16' => Runtime::DART216(), + 'dart-2.17' => Runtime::DART217(), + 'dart-2.18' => Runtime::DART218(), + 'deno-1.21' => Runtime::DENO121(), + 'deno-1.24' => Runtime::DENO124(), + 'deno-1.35' => Runtime::DENO135(), + 'deno-1.40' => Runtime::DENO140(), + 'dotnet-3.1' => Runtime::DOTNET31(), + 'dotnet-6.0' => Runtime::DOTNET60(), + 'dotnet-7.0' => Runtime::DOTNET70(), + 'java-8.0' => Runtime::JAVA80(), + 'java-11.0' => Runtime::JAVA110(), + 'java-17.0' => Runtime::JAVA170(), + 'java-18.0' => Runtime::JAVA180(), + 'java-21.0' => Runtime::JAVA210(), + 'swift-5.5' => Runtime::SWIFT55(), + 'swift-5.8' => Runtime::SWIFT58(), + 'swift-5.9' => Runtime::SWIFT59(), + 'kotlin-1.6' => Runtime::KOTLIN16(), + 'kotlin-1.8' => Runtime::KOTLIN18(), + 'kotlin-1.9' => Runtime::KOTLIN19(), + 'cpp-17' => Runtime::CPP17(), + 'cpp-20' => Runtime::CPP20(), + 'bun-1.0' => Runtime::BUN10(), + default => throw new \Exception('Invalid Runtime: ' . $resource->getRuntime()), + }; + $this->functions->create( $resource->getId(), $resource->getFunctionName(), - $resource->getRuntime(), + $runtype, $resource->getExecute(), $resource->getEvents(), $resource->getSchedule(), From 59f9ba1a88cef9acbfa9add2489be074e9b8b7c9 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 3 Jul 2024 12:23:46 +0300 Subject: [PATCH 073/185] Add hyphen --- src/Migration/Resource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Migration/Resource.php b/src/Migration/Resource.php index 859e8e8..f953b9f 100644 --- a/src/Migration/Resource.php +++ b/src/Migration/Resource.php @@ -52,7 +52,7 @@ abstract class Resource implements \JsonSerializable public const TYPE_HASH = 'hash'; - public const TYPE_ENVIRONMENT_VARIABLE = 'environment variable'; + public const TYPE_ENVIRONMENT_VARIABLE = 'environment-variable'; public const ALL_RESOURCES = [ self::TYPE_ATTRIBUTE, From 9b2da836972f232d3e5772c898760031087525be Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 3 Jul 2024 19:40:10 +0300 Subject: [PATCH 074/185] Fix $end --- src/Migration/Sources/Appwrite.php | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 64b76f7..648c4cf 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -44,11 +44,14 @@ class Appwrite extends Source protected Client $client; private Users $users; + private Teams $teams; + private Databases $database; + private Storage $storage; - private Functions $functions; + private Functions $functions; public function __construct( protected string $project, @@ -108,6 +111,7 @@ public static function getSupportedResources(): array /** * @param array $resources * @return array + * * @throws \Exception */ public function report(array $resources = []): array @@ -293,7 +297,7 @@ public function report(array $resources = []): array '/health/version', [ 'X-Appwrite-Key' => '', - 'X-Appwrite-Project' => '' + 'X-Appwrite-Project' => '', ] )['version']; @@ -314,7 +318,6 @@ public function report(array $resources = []): array * * @param int $batchSize Max 100 * @param array $resources - * @return void */ protected function exportGroupAuth(int $batchSize, array $resources): void { @@ -683,7 +686,7 @@ private function convertAttribute(array $value, Collection $collection): Attribu { switch ($value['type']) { case 'string': - if (!isset($value['format'])) { + if (! isset($value['format'])) { return new Text( $value['key'], $collection, @@ -1321,10 +1324,16 @@ private function exportDeploymentData(Func $func, array $deployment) $responseHeaders ); + $size = mb_strlen($file); + + if ($end > $size) { + $end = $size - 1; + } + $deployment = new Deployment( $deployment['$id'], $func, - strlen($file), + $size, $deployment['entrypoint'], $start, $end, From 25d4726f2290cdaacb4456420bd73751d3ca8c0e Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 4 Jul 2024 11:30:41 +0300 Subject: [PATCH 075/185] Add original id --- src/Migration/Resources/Functions/Deployment.php | 1 + src/Migration/Resources/Functions/EnvVar.php | 1 + src/Migration/Resources/Functions/Func.php | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Migration/Resources/Functions/Deployment.php b/src/Migration/Resources/Functions/Deployment.php index 3f5b95b..5be2bd1 100644 --- a/src/Migration/Resources/Functions/Deployment.php +++ b/src/Migration/Resources/Functions/Deployment.php @@ -44,6 +44,7 @@ public static function fromArray(array $array): self public function jsonSerialize(): array { return [ + 'id' => $this->id, 'func' => $this->func, 'size' => $this->size, 'entrypoint' => $this->entrypoint, diff --git a/src/Migration/Resources/Functions/EnvVar.php b/src/Migration/Resources/Functions/EnvVar.php index 0555d16..2cd7f97 100644 --- a/src/Migration/Resources/Functions/EnvVar.php +++ b/src/Migration/Resources/Functions/EnvVar.php @@ -36,6 +36,7 @@ public static function fromArray(array $array): self public function jsonSerialize(): array { return [ + 'id' => $this->id, 'func' => $this->func, 'key' => $this->key, 'value' => $this->value, diff --git a/src/Migration/Resources/Functions/Func.php b/src/Migration/Resources/Functions/Func.php index 14756d6..0dc0fdf 100644 --- a/src/Migration/Resources/Functions/Func.php +++ b/src/Migration/Resources/Functions/Func.php @@ -57,8 +57,8 @@ public static function fromArray(array $array): self public function jsonSerialize(): array { return [ - 'name' => $this->name, 'id' => $this->id, + 'name' => $this->name, 'execute' => $this->execute, 'enabled' => $this->enabled, 'runtime' => $this->runtime, From f284e65eb40bbb31b47175e24204a593fe6a8b70 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 4 Jul 2024 12:14:56 +0300 Subject: [PATCH 076/185] Add function entrypoint --- src/Migration/Resources/Functions/Func.php | 7 +++++-- src/Migration/Sources/Appwrite.php | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Migration/Resources/Functions/Func.php b/src/Migration/Resources/Functions/Func.php index 0dc0fdf..cfd1f49 100644 --- a/src/Migration/Resources/Functions/Func.php +++ b/src/Migration/Resources/Functions/Func.php @@ -27,7 +27,8 @@ public function __construct( private readonly array $events = [], private readonly string $schedule = '', private readonly int $timeout = 0, - private readonly string $activeDeployment = '' + private readonly string $activeDeployment = '', + private readonly string $entrypoint = '' ) { $this->id = $id; } @@ -47,7 +48,8 @@ public static function fromArray(array $array): self $array['events'] ?? [], $array['schedule'] ?? '', $array['timeout'] ?? 0, - $array['activeDeployment'] ?? '' + $array['activeDeployment'] ?? '', + $array['entrypoint'] ?? '' ); } @@ -66,6 +68,7 @@ public function jsonSerialize(): array 'schedule' => $this->schedule, 'timeout' => $this->timeout, 'activeDeployment' => $this->activeDeployment, + 'entrypoint' => $this->entrypoint, ]; } diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 648c4cf..7e65ab7 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -1226,7 +1226,8 @@ private function exportFunctions(int $batchSize): void $function['events'], $function['schedule'], $function['timeout'], - $function['deployment'] + $function['deployment'], + $function['entrypoint'] ); $convertedResources[] = $convertedFunc; From d4c5aed77bc47980624bb94dcd994ce863bac2ed Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 4 Jul 2024 12:41:46 +0300 Subject: [PATCH 077/185] Add create entrypoint --- src/Migration/Destinations/Appwrite.php | 3 ++- src/Migration/Resources/Functions/Func.php | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 64e975e..1e7cc1f 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -870,7 +870,8 @@ public function importFunctionResource(Resource $resource): Resource $resource->getEvents(), $resource->getSchedule(), $resource->getTimeout(), - $resource->getEnabled() + $resource->getEnabled(), + entrypoint:$resource->getEntrypoint(), ); break; case Resource::TYPE_ENVIRONMENT_VARIABLE: diff --git a/src/Migration/Resources/Functions/Func.php b/src/Migration/Resources/Functions/Func.php index cfd1f49..4f1e56a 100644 --- a/src/Migration/Resources/Functions/Func.php +++ b/src/Migration/Resources/Functions/Func.php @@ -121,4 +121,9 @@ public function getActiveDeployment(): string { return $this->activeDeployment; } + + public function getEntrypoint(): string + { + return $this->entrypoint; + } } From 6309549c446244535cc1f9d9da1b16e8fe1a0c92 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 9 Jul 2024 11:22:38 +0300 Subject: [PATCH 078/185] Remove dbg --- src/Migration/Source.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Migration/Source.php b/src/Migration/Source.php index 46acdf6..495da9f 100644 --- a/src/Migration/Source.php +++ b/src/Migration/Source.php @@ -32,9 +32,7 @@ public function run(array $resources, callable $callback): void $prunedResources = []; foreach ($returnedResources as $resource) { /** @var Resource $resource */ - var_dump('Source::run method'); - var_dump($resource->getName()); - var_dump($resources); + var_dump('Source::run method = ' . $resource->getName()); if (! in_array($resource->getName(), $resources)) { var_dump('setStatus === ' . Resource::STATUS_SKIPPED); From 64e4ffbc14ef3128d60ef2766e4a881cf62a5ee8 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 9 Jul 2024 14:31:41 +0300 Subject: [PATCH 079/185] Relationships Enums --- src/Migration/Destinations/Appwrite.php | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 1e7cc1f..08abaf9 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -6,9 +6,12 @@ use Appwrite\Client; use Appwrite\Enums\Compression; use Appwrite\Enums\IndexType; +use Appwrite\Enums\RelationMutate; +use Appwrite\Enums\RelationshipType; use Appwrite\Enums\Runtime; use Appwrite\InputFile; use Appwrite\Services\Databases; +use Utopia\Database\Database as DatabaseLibrary; use Appwrite\Services\Functions; use Appwrite\Services\Storage; use Appwrite\Services\Teams; @@ -485,15 +488,31 @@ public function createAttribute(Attribute $attribute): void break; case Attribute::TYPE_RELATIONSHIP: /** @var Relationship $attribute */ + + $type = match ($attribute->getRelationType()) { + DatabaseLibrary::RELATION_MANY_TO_MANY => RelationshipType::MANYTOMANY(), + DatabaseLibrary::RELATION_MANY_TO_ONE => RelationshipType::MANYTOONE(), + DatabaseLibrary::RELATION_ONE_TO_MANY => RelationshipType::ONETOMANY(), + DatabaseLibrary::RELATION_ONE_TO_ONE => RelationshipType::ONETOONE(), + default => throw new \Exception('Invalid RelationshipType: ' . $attribute->getRelationType()), + }; + + $mutation = match ($attribute->getRelationType()) { + DatabaseLibrary::RELATION_MUTATE_CASCADE => RelationMutate::CASCADE(), + DatabaseLibrary::RELATION_MUTATE_RESTRICT => RelationMutate::RESTRICT(), + DatabaseLibrary::RELATION_MUTATE_SET_NULL => RelationMutate::SETNULL(), + default => null, + }; + $this->databases->createRelationshipAttribute( $attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getRelatedCollection(), - $attribute->getRelationType(), + $type, $attribute->getTwoWay(), $attribute->getKey(), $attribute->getTwoWayKey(), - $attribute->getOnDelete() + $mutation ); break; default: From b5b4fcf8572ec3c56f2a9dfe858e29be2d6d4930 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 9 Jul 2024 15:13:27 +0300 Subject: [PATCH 080/185] Debug info --- src/Migration/Destinations/Appwrite.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 08abaf9..aa00efe 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -378,6 +378,9 @@ public function importDatabaseResource(Resource $resource): Resource */ public function createAttribute(Attribute $attribute): void { + var_dump('createAttribute ' . $attribute->getTypeName()); + var_dump('createAttribute key ' . $attribute->getKey()); + switch ($attribute->getTypeName()) { case Attribute::TYPE_STRING: /** @var Text $attribute */ @@ -432,6 +435,9 @@ public function createAttribute(Attribute $attribute): void break; case Attribute::TYPE_DATETIME: /** @var DateTime $attribute */ + + var_dump('createDatetimeAttribute key: ' . $attribute->getKey()); + $this->databases->createDatetimeAttribute( $attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), From 39f99bde5943eef5f19960ef608af098c0d4cb83 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 9 Jul 2024 16:51:11 +0300 Subject: [PATCH 081/185] Datetime resource --- .../Database/Attributes/DateTime.php | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/Migration/Resources/Database/Attributes/DateTime.php b/src/Migration/Resources/Database/Attributes/DateTime.php index 09d1906..c7d6717 100644 --- a/src/Migration/Resources/Database/Attributes/DateTime.php +++ b/src/Migration/Resources/Database/Attributes/DateTime.php @@ -3,11 +3,51 @@ namespace Utopia\Migration\Resources\Database\Attributes; use Utopia\Migration\Resources\Database\Attribute; +use Utopia\Migration\Resources\Database\Collection; -class DateTime extends Text +class DateTime extends Attribute { + public function __construct( + string $key, + Collection $collection, + bool $required = false, + bool $array = false, + private readonly ?string $default = null, + ) { + parent::__construct($key, $collection, $required, $array); + } + + /** + * @param array $array + */ + public static function fromArray(array $array): self + { + return new self( + $array['key'], + Collection::fromArray($array['collection']), + $array['required'] ?? false, + $array['array'] ?? false, + $array['default'] ?? null + ); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return array_merge(parent::jsonSerialize(), [ + 'default' => $this->default, + ]); + } + public function getTypeName(): string { return Attribute::TYPE_DATETIME; } + + public function getDefault(): ?string + { + return $this->default; + } } From 5574ecaa4a07743b7a62bc581ace359f5ad8d4cf Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 9 Jul 2024 17:25:49 +0300 Subject: [PATCH 082/185] Datetime resource --- .../Database/Attributes/DateTime.php | 40 +++++-------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/src/Migration/Resources/Database/Attributes/DateTime.php b/src/Migration/Resources/Database/Attributes/DateTime.php index c7d6717..9d7339d 100644 --- a/src/Migration/Resources/Database/Attributes/DateTime.php +++ b/src/Migration/Resources/Database/Attributes/DateTime.php @@ -7,47 +7,29 @@ class DateTime extends Attribute { - public function __construct( - string $key, - Collection $collection, - bool $required = false, - bool $array = false, - private readonly ?string $default = null, - ) { - parent::__construct($key, $collection, $required, $array); - } + protected ?string $default; /** - * @param array $array + * @param ?string $default */ - public static function fromArray(array $array): self + public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, string $default = null) { - return new self( - $array['key'], - Collection::fromArray($array['collection']), - $array['required'] ?? false, - $array['array'] ?? false, - $array['default'] ?? null - ); + parent::__construct($key, $collection, $required, $array); + $this->default = $default; } - /** - * @return array - */ - public function jsonSerialize(): array + public function getDefault(): ?string { - return array_merge(parent::jsonSerialize(), [ - 'default' => $this->default, - ]); + return $this->default; } - public function getTypeName(): string + public function setDefault(string $default): void { - return Attribute::TYPE_DATETIME; + $this->default = $default; } - public function getDefault(): ?string + public function getTypeName(): string { - return $this->default; + return Attribute::TYPE_DATETIME; } } From 1b42474c1e22042987230c029005c1e1f511e051 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 9 Jul 2024 17:32:37 +0300 Subject: [PATCH 083/185] Fix datetime --- .../Database/Attributes/DateTime.php | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/Migration/Resources/Database/Attributes/DateTime.php b/src/Migration/Resources/Database/Attributes/DateTime.php index 9d7339d..4fd09a7 100644 --- a/src/Migration/Resources/Database/Attributes/DateTime.php +++ b/src/Migration/Resources/Database/Attributes/DateTime.php @@ -18,6 +18,32 @@ public function __construct(string $key, Collection $collection, bool $required $this->default = $default; } + public function getTypeName(): string + { + return Attribute::TYPE_DATETIME; + } + + public static function fromArray(array $array): self + { + return new self( + $array['key'], + Collection::fromArray($array['collection']), + $array['required'] ?? false, + $array['array'] ?? false, + $array['default'] ?? null, + ); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return array_merge(parent::jsonSerialize(), [ + 'default' => $this->default, + ]); + } + public function getDefault(): ?string { return $this->default; @@ -27,9 +53,4 @@ public function setDefault(string $default): void { $this->default = $default; } - - public function getTypeName(): string - { - return Attribute::TYPE_DATETIME; - } } From 50b57b846462857bab162e3d30d014c80538b92a Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 10 Jul 2024 10:23:53 +0300 Subject: [PATCH 084/185] Do we have Datetime format? --- src/Migration/Sources/Appwrite.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 7e65ab7..c21dffd 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -727,13 +727,13 @@ private function convertAttribute(array $value, Collection $collection): Attribu $value['array'], $value['default'] ), - 'datetime' => new DateTime( - $value['key'], - $collection, - $value['required'], - $value['array'], - $value['default'] - ), +// 'datetime' => new DateTime( +// $value['key'], +// $collection, +// $value['required'], +// $value['array'], +// $value['default'] +// ), default => new Text( $value['key'], $collection, From 1b0f2deef3c4dc80f263d9239add2032d1a6676f Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 14 Jul 2024 13:16:47 +0300 Subject: [PATCH 085/185] exportDocuments Appwrite --- src/Migration/Sources/Appwrite.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index c21dffd..1569f21 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -614,6 +614,15 @@ private function exportDocuments(int $batchSize): void $queries[] = Query::cursorAfter($lastDocument); } + $attributes = $this->cache->get(Attribute::getName()); + foreach ($attributes as $attribute) { + /** @var Attribute $attribute */ + if ($attribute->getCollection()->getId() === $collection->getId()) { + var_dump(' === exportDocuments exportDocuments exportDocuments === '); + var_dump($attribute); + } + } + $response = $this->database->listDocuments( $collection->getDatabase()->getId(), $collection->getId(), @@ -915,7 +924,6 @@ private function exportAttributes(int $batchSize): void ); // Remove two way relationship attributes - $this->cache->get(Resource::TYPE_ATTRIBUTE); $knownTwoWays = []; From 2e69fbc5581c445de2136709cff44d543e44a345 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 14 Jul 2024 14:00:10 +0300 Subject: [PATCH 086/185] Revert playground.php --- playground.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/playground.php b/playground.php index 41e36da..571a768 100644 --- a/playground.php +++ b/playground.php @@ -10,7 +10,7 @@ use Dotenv\Dotenv; use Utopia\CLI\Console; use Utopia\Migration\Destinations\Appwrite as AppwriteDestination; -use Utopia\Migration\Destinations\Backup; +use Utopia\Migration\Destinations\Local; use Utopia\Migration\Sources\Appwrite; use Utopia\Migration\Sources\Firebase; use Utopia\Migration\Sources\NHost; @@ -63,12 +63,14 @@ $_ENV['DESTINATION_APPWRITE_TEST_KEY'] ); -/**xx +$destinationLocal = new Local(__DIR__.'/localBackup/'); + +/** * Initialise Transfer Class */ $transfer = new Transfer( $sourceAppwrite, - new Backup(__DIR__.'/localBackup/') + $destinationAppwrite ); /** From 9635d266221031c5a0db01f8c7d97daabf1ef72e Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 14 Jul 2024 14:57:07 +0300 Subject: [PATCH 087/185] Add select query --- src/Migration/Sources/Appwrite.php | 31 +++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 1569f21..ffaf48c 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -613,15 +613,28 @@ private function exportDocuments(int $batchSize): void if ($lastDocument) { $queries[] = Query::cursorAfter($lastDocument); } - - $attributes = $this->cache->get(Attribute::getName()); - foreach ($attributes as $attribute) { - /** @var Attribute $attribute */ - if ($attribute->getCollection()->getId() === $collection->getId()) { - var_dump(' === exportDocuments exportDocuments exportDocuments === '); - var_dump($attribute); - } - } +// +// $selects = []; +// $attributes = $this->cache->get(Attribute::getName()); +// foreach ($attributes as $attribute) { +// /** @var Attribute $attribute */ +// if ($attribute->getCollection()->getId() === $collection->getId()) { +// +// var_dump(' === exportDocuments === '); +// var_dump($attribute->getKey()); +// var_dump($attribute); +// $selects[] = $attribute->getKey(); +// if($attribute->getTypeName() === Attribute::TYPE_RELATIONSHIP){ +// var_dump(' === this is TYPE_RELATIONSHIP === '); +// } +// } +// } +// +// if(!empty($selects)){ +// $queries[] = Query::select($selects); +// } + + $queries[] = Query::select(['*', '$id', '$permissions', '$createdAt', '$updatedAt']); $response = $this->database->listDocuments( $collection->getDatabase()->getId(), From 8e0e95d69f8fc711d7c9a79d633fa91bca525107 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 14 Jul 2024 15:01:51 +0300 Subject: [PATCH 088/185] Remove dates --- src/Migration/Sources/Appwrite.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index ffaf48c..4496543 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -634,7 +634,7 @@ private function exportDocuments(int $batchSize): void // $queries[] = Query::select($selects); // } - $queries[] = Query::select(['*', '$id', '$permissions', '$createdAt', '$updatedAt']); + $queries[] = Query::select(['*', '$id', '$permissions']); $response = $this->database->listDocuments( $collection->getDatabase()->getId(), From 76cc27613fe0dfb44db89b3c9d12e78aa93f4af5 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 15 Jul 2024 10:21:43 +0300 Subject: [PATCH 089/185] stripMetadata --- src/Migration/Sources/Appwrite.php | 41 ++++++++++++++++-------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 4496543..a72277c 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -575,21 +575,25 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void public function stripMetadata(array $document, bool $root = true): array { - if ($root) { - unset($document['$id']); - } - - unset($document['$permissions']); unset($document['$collectionId']); - unset($document['$updatedAt']); - unset($document['$createdAt']); unset($document['$databaseId']); - foreach ($document as $key => $value) { - if (is_array($value)) { - $document[$key] = $this->stripMetadata($value, false); - } - } +// +// if ($root) { +// unset($document['$id']); +// } +// +// unset($document['$permissions']); +// +// unset($document['$updatedAt']); +// unset($document['$createdAt']); +// unset($document['$tenant']); +// +// foreach ($document as $key => $value) { +// if (is_array($value)) { +// $document[$key] = $this->stripMetadata($value, false); +// } +// } return $document; } @@ -634,7 +638,7 @@ private function exportDocuments(int $batchSize): void // $queries[] = Query::select($selects); // } - $queries[] = Query::select(['*', '$id', '$permissions']); + $queries[] = Query::select(['*', '$id', '$permissions']); // We want Relations flat! $response = $this->database->listDocuments( $collection->getDatabase()->getId(), @@ -643,9 +647,8 @@ private function exportDocuments(int $batchSize): void ); foreach ($response['documents'] as $document) { - $id = $document['$id']; - $permissions = $document['$permissions']; - + //$id = $document['$id']; + //$permissions = $document['$permissions']; $document = $this->stripMetadata($document); // Certain Appwrite versions allowed for data to be required but null @@ -684,12 +687,12 @@ private function exportDocuments(int $batchSize): void $cleanData = $this->stripMetadata($document); $documents[] = new Document( - $id, + $document['$id'], $collection, $cleanData, - $permissions + $document['$permissions'] ); - $lastDocument = $id; + $lastDocument = $document['$id']; } $this->callback($documents); From a416b4d237122a01aab1486aaeef01f8b011986e Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 15 Jul 2024 10:38:14 +0300 Subject: [PATCH 090/185] add $updatedAt $createdAt --- src/Migration/Sources/Appwrite.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index a72277c..f5ddf04 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -573,7 +573,7 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void } } - public function stripMetadata(array $document, bool $root = true): array + public function stripMetadata(array $document): array { unset($document['$collectionId']); unset($document['$databaseId']); @@ -638,7 +638,7 @@ private function exportDocuments(int $batchSize): void // $queries[] = Query::select($selects); // } - $queries[] = Query::select(['*', '$id', '$permissions']); // We want Relations flat! + $queries[] = Query::select(['*', '$id', '$permissions', '$updatedAt', '$createdAt']); // We want Relations flat! $response = $this->database->listDocuments( $collection->getDatabase()->getId(), From 6f9c6275ee1315288ca772e236c9beea73b4a2ec Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 15 Jul 2024 16:45:11 +0900 Subject: [PATCH 091/185] Implement Single Resource Migration --- phpunit.xml | 3 + src/Migration/Destination.php | 9 +- src/Migration/Source.php | 9 +- src/Migration/Sources/Appwrite.php | 30 ++++ src/Migration/Target.php | 5 +- src/Migration/Transfer.php | 26 ++- .../Mock.php => Adapters/MockDestination.php} | 34 +++- tests/Migration/Adapters/MockSource.php | 156 ++++++++++++++++++ tests/Migration/E2E/Sources/Base.php | 4 +- tests/Migration/E2E/Sources/NHostTest.php | 4 +- tests/Migration/E2E/Sources/SupabaseTest.php | 4 +- tests/Migration/unit/General/TransferTest.php | 57 +++++++ 12 files changed, 318 insertions(+), 23 deletions(-) rename tests/Migration/{E2E/Adapters/Mock.php => Adapters/MockDestination.php} (61%) create mode 100644 tests/Migration/Adapters/MockSource.php create mode 100644 tests/Migration/unit/General/TransferTest.php diff --git a/phpunit.xml b/phpunit.xml index acf2d6f..f312d5f 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -10,5 +10,8 @@ ./tests/Migration/E2E + + ./tests/Migration/unit + diff --git a/src/Migration/Destination.php b/src/Migration/Destination.php index d07c15d..d5f576d 100644 --- a/src/Migration/Destination.php +++ b/src/Migration/Destination.php @@ -24,14 +24,15 @@ public function setSource(Source $source): self /** * Transfer Resources to Destination from Source callback * - * @param array $resources Resources to transfer - * @param callable $callback Callback to run after transfer + * @param array $resources Resources to transfer + * @param callable $callback Callback to run after transfer + * @param string $rootResourceId Root resource ID, If enabled you can only transfer a single root resource */ - public function run(array $resources, callable $callback): void + public function run(array $resources, callable $callback, string $rootResourceId = ''): void { $this->source->run($resources, function (array $resources) use ($callback) { $this->import($resources, $callback); - }); + }, $rootResourceId); } /** diff --git a/src/Migration/Source.php b/src/Migration/Source.php index 495da9f..0c8bbf6 100644 --- a/src/Migration/Source.php +++ b/src/Migration/Source.php @@ -23,11 +23,14 @@ public function callback(array $resources): void /** * Transfer Resources into destination * - * @param array $resources Resources to transfer - * @param callable $callback Callback to run after transfer + * @param array $resources Resources to transfer + * @param callable $callback Callback to run after transfer + * @param string $rootResourceId Root resource ID, If enabled you can only transfer a single root resource */ - public function run(array $resources, callable $callback): void + public function run(array $resources, callable $callback, string $rootResourceId = ''): void { + $this->rootResourceId = $rootResourceId; + $this->transferCallback = function (array $returnedResources) use ($callback, $resources) { $prunedResources = []; foreach ($returnedResources as $resource) { diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 4496543..97dd921 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -362,6 +362,12 @@ private function exportUsers(int $batchSize): void { $lastDocument = null; + // Root Level Resource + if (!empty($this->rootResourceId)) { + $this->callback([$this->users->get($this->rootResourceId)]); + return; + } + // Export Users while (true) { $users = []; @@ -412,6 +418,12 @@ private function exportTeams(int $batchSize): void $this->teams = new Teams($this->client); $lastDocument = null; + // Root Level Resource + if (!empty($this->rootResourceId)) { + $this->callback([$this->teams->get($this->rootResourceId)]); + return; + } + // Export Teams while (true) { $teams = []; @@ -828,6 +840,12 @@ private function exportDatabases(int $batchSize): void $lastDatabase = null; + // Root Level Resource + if (!empty($this->rootResourceId)) { + $this->callback([$this->database->get($this->rootResourceId)]); + return; + } + // Transfer Databases while (true) { $queries = [Query::limit($batchSize)]; @@ -1078,6 +1096,12 @@ protected function exportGroupStorage(int $batchSize, array $resources): void */ private function exportBuckets(int $batchSize): void { + // Root Level Resource + if (!empty($this->rootResourceId)) { + $this->callback([$this->storage->getBucket($this->rootResourceId)]); + return; + } + $buckets = $this->storage->listBuckets(); $convertedBuckets = []; @@ -1229,6 +1253,12 @@ private function exportFunctions(int $batchSize): void { $this->functions = new Functions($this->client); + // Root Level Resource + if (!empty($this->rootResourceId)) { + $this->callback([$this->functions->get($this->rootResourceId)]); + return; + } + $functions = $this->functions->list(); if ($functions['total'] === 0) { diff --git a/src/Migration/Target.php b/src/Migration/Target.php index ca1fef1..2d83fc4 100644 --- a/src/Migration/Target.php +++ b/src/Migration/Target.php @@ -22,6 +22,8 @@ abstract class Target protected $endpoint = ''; + protected $rootResourceId = ''; + abstract public static function getName(): string; abstract public static function getSupportedResources(): array; @@ -36,8 +38,9 @@ public function registerCache(Cache &$cache): void * * @param array $resources Resources to transfer * @param callable $callback Callback to run after transfer + * @param string $rootResourceId Root resource ID, If enabled you can only transfer a single root resource */ - abstract public function run(array $resources, callable $callback): void; + abstract public function run(array $resources, callable $callback, string $rootResourceId = ''): void; /** * Report Resources diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index 87397e0..c6c377b 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -42,6 +42,14 @@ class Transfer Resource::TYPE_DOCUMENT, ]; + public const ROOT_RESOURCES = [ + Resource::TYPE_BUCKET, + Resource::TYPE_DATABASE, + Resource::TYPE_FUNCTION, + Resource::TYPE_USER, + Resource::TYPE_TEAM + ]; + public const STORAGE_MAX_CHUNK_SIZE = 1024 * 1024 * 5; // 5MB protected Source $source; @@ -156,10 +164,11 @@ public function getStatusCounters(): array /** * Transfer Resources between adapters * - * @param array $resources - * @param callable $callback + * @param array $resources Resources to transfer + * @param callable $callback Callback to run after transfer + * @param string $rootResourceId Root resource ID, If enabled you can only transfer a single root resource */ - public function run(array $resources, callable $callback): void + public function run(array $resources, callable $callback, string $rootResourceId = ''): void { // Allows you to push entire groups if you want. $computedResources = []; @@ -173,9 +182,18 @@ public function run(array $resources, callable $callback): void $computedResources = array_map('strtolower', $computedResources); + // Check we don't have multiple root resources if rootResourceId is set + if ($rootResourceId) { + $rootResourceCount = count(array_intersect($computedResources, self::ROOT_RESOURCES)); + + if ($rootResourceCount > 1) { + throw new \Exception('Multiple root resources found. Only one root resource can be transferred at a time if using $rootResourceId.'); + } + } + $this->resources = $computedResources; - $this->destination->run($computedResources, $callback); + $this->destination->run($computedResources, $callback, $rootResourceId); } /** diff --git a/tests/Migration/E2E/Adapters/Mock.php b/tests/Migration/Adapters/MockDestination.php similarity index 61% rename from tests/Migration/E2E/Adapters/Mock.php rename to tests/Migration/Adapters/MockDestination.php index e2c318a..d36a865 100644 --- a/tests/Migration/E2E/Adapters/Mock.php +++ b/tests/Migration/Adapters/MockDestination.php @@ -1,17 +1,32 @@ data[$group] ?? []; + } + + public function getResourceTypeData(string $group, string $resourceType): array + { + return array_keys($this->data[$group][$resourceType]) ?? []; + } + + public function getResourceById(string $group, string $resourceType, string $resourceId): ?Resource + { + return $this->data[$group][$resourceType][$resourceId] ?? null; + } + public static function getName(): string { - return 'Mock'; + return 'MockDestination'; } public static function getSupportedResources(): array @@ -37,12 +52,12 @@ public static function getSupportedResources(): array public function import(array $resources, callable $callback): void { foreach ($resources as $resource) { - /** @var resource $resource */ + /** @var Resource $resource */ switch ($resource->getName()) { case 'Deployment': /** @var Deployment $resource */ if ($resource->getStart() === 0) { - $this->data[$resource->getGroup()][$resource->getName()][$resource->getInternalId()] = $resource->asArray(); + $this->data[$resource->getGroup()][$resource->getName()][$resource->getId()] = $resource; } // file_put_contents($this->path . 'deployments/' . $resource->getId() . '.tar.gz', $resource->getData(), FILE_APPEND); @@ -52,6 +67,15 @@ public function import(array $resources, callable $callback): void break; } + if (!key_exists($resource->getGroup(), $this->data)) { + $this->data[$resource->getGroup()] = []; + } + + if (!key_exists($resource->getName(), $this->data[$resource->getGroup()])) { + $this->data[$resource->getGroup()][$resource->getName()] = []; + } + + $this->data[$resource->getGroup()][$resource->getName()][$resource->getId()] = $resource; $resource->setStatus(Resource::STATUS_SUCCESS); $this->cache->update($resource); } diff --git a/tests/Migration/Adapters/MockSource.php b/tests/Migration/Adapters/MockSource.php new file mode 100644 index 0000000..56174ba --- /dev/null +++ b/tests/Migration/Adapters/MockSource.php @@ -0,0 +1,156 @@ +getGroup(), $this->mockResources)) { + $this->mockResources[$resource->getGroup()] = []; + } + + if (!key_exists($resource->getName(), $this->mockResources[$resource->getGroup()])) { + $this->mockResources[$resource->getGroup()][$resource->getName()] = []; + } + + $this->mockResources[$resource->getGroup()][$resource->getName()][$resource->getId()] = $resource; + } + + public function getMockResources(): array + { + return $this->mockResources; + } + + public function getMockResourcesByType(string $group, string $type): array + { + return array_values($this->mockResources[$group][$type]) ?? []; + } + + public function getMockResourceById(string $group, string $type, string $id): ?Resource + { + return $this->mockResources[$group][$type][$id] ?? null; + } + + public function clearMockResources(): void + { + $this->mockResources = []; + } + + private function handleResourceTransfer(string $group, string $type): void + { + if (in_array($type, Transfer::ROOT_RESOURCES) && !empty($this->rootResourceId)) { + $this->callback([$this->getMockResourceById($group, $type, $this->rootResourceId)]); + return; + } + + $resources = $this->getMockResourcesByType($group, $type) ?? []; + $this->callback($resources); + return; + } + + public static function getName(): string + { + return 'MockSource'; + } + + public static function getSupportedResources(): array + { + return [ + Resource::TYPE_ATTRIBUTE, + Resource::TYPE_BUCKET, + Resource::TYPE_COLLECTION, + Resource::TYPE_DATABASE, + Resource::TYPE_DOCUMENT, + Resource::TYPE_FILE, + Resource::TYPE_FUNCTION, + Resource::TYPE_DEPLOYMENT, + Resource::TYPE_HASH, + Resource::TYPE_INDEX, + Resource::TYPE_USER, + Resource::TYPE_ENVIRONMENT_VARIABLE, + Resource::TYPE_TEAM, + Resource::TYPE_MEMBERSHIP, + ]; + } + + public function report(array $resources = []): array + { + return []; + } + + /** + * Export Auth Group + * + * @param int $batchSize Max 100 + * @param string[] $resources Resources to export + */ + protected function exportGroupAuth(int $batchSize, array $resources) + { + foreach (Transfer::GROUP_AUTH_RESOURCES as $resource) { + if (!\in_array($resource, $resources)) { + continue; + } + + $this->handleResourceTransfer(Transfer::GROUP_AUTH, $resource); + } + } + + /** + * Export Databases Group + * + * @param int $batchSize Max 100 + * @param string[] $resources Resources to export + */ + protected function exportGroupDatabases(int $batchSize, array $resources) + { + foreach (Transfer::GROUP_DATABASES_RESOURCES as $resource) { + if (!\in_array($resource, $resources)) { + continue; + } + + $this->handleResourceTransfer(Transfer::GROUP_DATABASES, $resource); + } + } + + /** + * Export Storage Group + * + * @param int $batchSize Max 5 + * @param string[] $resources Resources to export + */ + protected function exportGroupStorage(int $batchSize, array $resources) + { + foreach (Transfer::GROUP_STORAGE_RESOURCES as $resource) { + if (!\in_array($resource, $resources)) { + continue; + } + + $this->handleResourceTransfer(Transfer::GROUP_STORAGE, $resource); + } + } + + /** + * Export Functions Group + * + * @param int $batchSize Max 100 + * @param string[] $resources Resources to export + */ + protected function exportGroupFunctions(int $batchSize, array $resources) + { + foreach (Transfer::GROUP_FUNCTIONS_RESOURCES as $resource) { + if (!\in_array($resource, $resources)) { + continue; + } + + $this->handleResourceTransfer(Transfer::GROUP_FUNCTIONS, $resource); + } + } +} \ No newline at end of file diff --git a/tests/Migration/E2E/Sources/Base.php b/tests/Migration/E2E/Sources/Base.php index c5a7f23..e70305c 100644 --- a/tests/Migration/E2E/Sources/Base.php +++ b/tests/Migration/E2E/Sources/Base.php @@ -7,7 +7,7 @@ use Utopia\Migration\Resource; use Utopia\Migration\Source; use Utopia\Migration\Transfer; -use Utopia\Tests\E2E\Adapters\Mock; +use Utopia\Tests\Adapters\MockDestination; abstract class Base extends TestCase { @@ -23,7 +23,7 @@ protected function setUp(): void throw new \Exception('Source not set'); } - $this->destination = new Mock(); + $this->destination = new MockDestination(); $this->transfer = new Transfer($this->source, $this->destination); } diff --git a/tests/Migration/E2E/Sources/NHostTest.php b/tests/Migration/E2E/Sources/NHostTest.php index 4412b3f..2fe9615 100644 --- a/tests/Migration/E2E/Sources/NHostTest.php +++ b/tests/Migration/E2E/Sources/NHostTest.php @@ -7,7 +7,7 @@ use Utopia\Migration\Source; use Utopia\Migration\Sources\NHost; use Utopia\Migration\Transfer; -use Utopia\Tests\E2E\Adapters\Mock; +use Utopia\Tests\Adapters\MockDestination; class NHostTest extends Base { @@ -73,7 +73,7 @@ protected function setUp(): void $this->source->pdo = new \PDO('pgsql:host=nhost-db'.';port=5432;dbname=postgres', 'postgres', 'postgres'); $this->source->storageURL = 'http://nhost-storage'; - $this->destination = new Mock(); + $this->destination = new MockDestination(); $this->transfer = new Transfer($this->source, $this->destination); } diff --git a/tests/Migration/E2E/Sources/SupabaseTest.php b/tests/Migration/E2E/Sources/SupabaseTest.php index c79d9fe..b4f45bb 100644 --- a/tests/Migration/E2E/Sources/SupabaseTest.php +++ b/tests/Migration/E2E/Sources/SupabaseTest.php @@ -7,7 +7,7 @@ use Utopia\Migration\Source; use Utopia\Migration\Sources\Supabase; use Utopia\Migration\Transfer; -use Utopia\Tests\E2E\Adapters\Mock; +use Utopia\Tests\Adapters\MockDestination; class SupabaseTest extends Base { @@ -53,7 +53,7 @@ protected function setUp(): void 'postgres' ); - $this->destination = new Mock(); + $this->destination = new MockDestination(); $this->transfer = new Transfer($this->source, $this->destination); } diff --git a/tests/Migration/unit/General/TransferTest.php b/tests/Migration/unit/General/TransferTest.php new file mode 100644 index 0000000..7378e0f --- /dev/null +++ b/tests/Migration/unit/General/TransferTest.php @@ -0,0 +1,57 @@ +source = new MockSource(); + $this->destination = new MockDestination(); + + $this->transfer = new Transfer( + $this->source, + $this->destination + ); + } + + public function testRootResourceId(): void + { + /** + * TEST FOR FAILURE + * Make sure we can't create a transfer with multiple root resources when supplying a rootResourceId + */ + try { + $this->transfer->run([Resource::TYPE_USER, Resource::TYPE_DATABASE], function () {}, 'rootResourceId'); + $this->fail('Multiple Root resources should not be allowed'); + } catch (\Exception $e) { + $this->assertEquals('Multiple root resources found. Only one root resource can be transferred at a time if using $rootResourceId.', $e->getMessage()); + } + + $this->source->pushMockResource(new Database('test', 'test')); + $this->source->pushMockResource(new Database('test2', 'test')); + + /** + * TEST FOR SUCCESS + */ + $this->transfer->run([Resource::TYPE_DATABASE], function () {}, 'test'); + $this->assertEquals(1, count($this->destination->getResourceTypeData(Transfer::GROUP_DATABASES, Resource::TYPE_DATABASE))); + + $database = $this->destination->getResourceById(Transfer::GROUP_DATABASES, Resource::TYPE_DATABASE, 'test'); + /** @var Database $database */ + $this->assertNotNull($database); + $this->assertEquals('test', $database->getDBName()); + $this->assertEquals('test', $database->getId()); + } +} \ No newline at end of file From 9e5b81e62994f7caa9970cd3a50781712e358977 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 15 Jul 2024 16:46:54 +0900 Subject: [PATCH 092/185] Run Linter --- src/Migration/Destinations/Appwrite.php | 2 +- src/Migration/Resources/Database/Index.php | 1 - src/Migration/Sources/Appwrite.php | 54 +++++++++---------- tests/Migration/Adapters/MockSource.php | 3 +- tests/Migration/unit/General/TransferTest.php | 4 +- 5 files changed, 31 insertions(+), 33 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index aa00efe..549f690 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -11,12 +11,12 @@ use Appwrite\Enums\Runtime; use Appwrite\InputFile; use Appwrite\Services\Databases; -use Utopia\Database\Database as DatabaseLibrary; use Appwrite\Services\Functions; use Appwrite\Services\Storage; use Appwrite\Services\Teams; use Appwrite\Services\Users; use Override; +use Utopia\Database\Database as DatabaseLibrary; use Utopia\Migration\Destination; use Utopia\Migration\Exception; use Utopia\Migration\Resource; diff --git a/src/Migration/Resources/Database/Index.php b/src/Migration/Resources/Database/Index.php index 9e06cb7..8c8ee7c 100644 --- a/src/Migration/Resources/Database/Index.php +++ b/src/Migration/Resources/Database/Index.php @@ -2,7 +2,6 @@ namespace Utopia\Migration\Resources\Database; -use Appwrite\Enums\IndexType; use Utopia\Migration\Resource; use Utopia\Migration\Transfer; diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 97dd921..e02c604 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -625,26 +625,26 @@ private function exportDocuments(int $batchSize): void if ($lastDocument) { $queries[] = Query::cursorAfter($lastDocument); } -// -// $selects = []; -// $attributes = $this->cache->get(Attribute::getName()); -// foreach ($attributes as $attribute) { -// /** @var Attribute $attribute */ -// if ($attribute->getCollection()->getId() === $collection->getId()) { -// -// var_dump(' === exportDocuments === '); -// var_dump($attribute->getKey()); -// var_dump($attribute); -// $selects[] = $attribute->getKey(); -// if($attribute->getTypeName() === Attribute::TYPE_RELATIONSHIP){ -// var_dump(' === this is TYPE_RELATIONSHIP === '); -// } -// } -// } -// -// if(!empty($selects)){ -// $queries[] = Query::select($selects); -// } + // + // $selects = []; + // $attributes = $this->cache->get(Attribute::getName()); + // foreach ($attributes as $attribute) { + // /** @var Attribute $attribute */ + // if ($attribute->getCollection()->getId() === $collection->getId()) { + // + // var_dump(' === exportDocuments === '); + // var_dump($attribute->getKey()); + // var_dump($attribute); + // $selects[] = $attribute->getKey(); + // if($attribute->getTypeName() === Attribute::TYPE_RELATIONSHIP){ + // var_dump(' === this is TYPE_RELATIONSHIP === '); + // } + // } + // } + // + // if(!empty($selects)){ + // $queries[] = Query::select($selects); + // } $queries[] = Query::select(['*', '$id', '$permissions']); @@ -761,13 +761,13 @@ private function convertAttribute(array $value, Collection $collection): Attribu $value['array'], $value['default'] ), -// 'datetime' => new DateTime( -// $value['key'], -// $collection, -// $value['required'], -// $value['array'], -// $value['default'] -// ), + // 'datetime' => new DateTime( + // $value['key'], + // $collection, + // $value['required'], + // $value['array'], + // $value['default'] + // ), default => new Text( $value['key'], $collection, diff --git a/tests/Migration/Adapters/MockSource.php b/tests/Migration/Adapters/MockSource.php index 56174ba..02020ac 100644 --- a/tests/Migration/Adapters/MockSource.php +++ b/tests/Migration/Adapters/MockSource.php @@ -2,7 +2,6 @@ namespace Utopia\Tests\Adapters; -use Utopia\Migration\Exception; use Utopia\Migration\Resource; use Utopia\Migration\Source; use Utopia\Migration\Transfer; @@ -153,4 +152,4 @@ protected function exportGroupFunctions(int $batchSize, array $resources) $this->handleResourceTransfer(Transfer::GROUP_FUNCTIONS, $resource); } } -} \ No newline at end of file +} diff --git a/tests/Migration/unit/General/TransferTest.php b/tests/Migration/unit/General/TransferTest.php index 7378e0f..76566f4 100644 --- a/tests/Migration/unit/General/TransferTest.php +++ b/tests/Migration/unit/General/TransferTest.php @@ -25,7 +25,7 @@ public function setup(): void $this->destination ); } - + public function testRootResourceId(): void { /** @@ -54,4 +54,4 @@ public function testRootResourceId(): void $this->assertEquals('test', $database->getDBName()); $this->assertEquals('test', $database->getId()); } -} \ No newline at end of file +} From 89b30ced75f4483b64802571bd4158be17a6407e Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 15 Jul 2024 16:49:47 +0900 Subject: [PATCH 093/185] Fix Tests --- .gitignore | 1 - composer.lock | 2820 +++++++++++++++++ tests/Migration/unit/General/TransferTest.php | 2 +- 3 files changed, 2821 insertions(+), 2 deletions(-) create mode 100644 composer.lock diff --git a/.gitignore b/.gitignore index 9c0bf12..a12c277 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -composer.lock /vendor/ /.idea/ *.cache diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..d7a0f37 --- /dev/null +++ b/composer.lock @@ -0,0 +1,2820 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "aa00134eaea94873c4f096e0ea2df767", + "packages": [ + { + "name": "appwrite/appwrite", + "version": "11.1.0", + "source": { + "type": "git", + "url": "https://github.com/appwrite/sdk-for-php.git", + "reference": "1d043f543acdb17b9fdb440b1b2dd208e400bad3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/1d043f543acdb17b9fdb440b1b2dd208e400bad3", + "reference": "1d043f543acdb17b9fdb440b1b2dd208e400bad3", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "php": ">=7.1.0" + }, + "require-dev": { + "mockery/mockery": "^1.6.6", + "phpunit/phpunit": "^10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Appwrite\\": "src/Appwrite" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Appwrite is an open-source self-hosted backend server that abstract and simplify complex and repetitive development tasks behind a very simple REST API", + "support": { + "email": "team@appwrite.io", + "issues": "https://github.com/appwrite/sdk-for-php/issues", + "source": "https://github.com/appwrite/sdk-for-php/tree/11.1.0", + "url": "https://appwrite.io/support" + }, + "time": "2024-06-26T07:03:23+00:00" + }, + { + "name": "jean85/pretty-package-versions", + "version": "2.0.6", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/f9fdd29ad8e6d024f52678b570e5593759b550b4", + "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0.0", + "php": "^7.1|^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.4", + "vimeo/psalm": "^4.3" + }, + "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.0.6" + }, + "time": "2024-03-08T09:58:59+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": "symfony/polyfill-php80", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/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.30.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-05-31T15:07:36+00:00" + }, + { + "name": "utopia-php/cache", + "version": "0.10.2", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/cache.git", + "reference": "b22c6eb6d308de246b023efd0fc9758aee8b8247" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/b22c6eb6d308de246b023efd0fc9758aee8b8247", + "reference": "b22c6eb6d308de246b023efd0fc9758aee8b8247", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-memcached": "*", + "ext-redis": "*", + "php": ">=8.0" + }, + "require-dev": { + "laravel/pint": "1.2.*", + "phpstan/phpstan": "1.9.x-dev", + "phpunit/phpunit": "^9.3", + "vimeo/psalm": "4.13.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Cache\\": "src/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A simple cache library to manage application cache storing, loading and purging", + "keywords": [ + "cache", + "framework", + "php", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/cache/issues", + "source": "https://github.com/utopia-php/cache/tree/0.10.2" + }, + "time": "2024-06-25T20:36:35+00:00" + }, + { + "name": "utopia-php/cli", + "version": "0.15.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/cli.git", + "reference": "ccb7c8125ffe0254fef8f25744bfa376eb7bd0ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/cli/zipball/ccb7c8125ffe0254fef8f25744bfa376eb7bd0ea", + "reference": "ccb7c8125ffe0254fef8f25744bfa376eb7bd0ea", + "shasum": "" + }, + "require": { + "php": ">=7.4", + "utopia-php/framework": "0.*.*" + }, + "require-dev": { + "laravel/pint": "1.2.*", + "phpunit/phpunit": "^9.3", + "squizlabs/php_codesniffer": "^3.6", + "vimeo/psalm": "4.0.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\CLI\\": "src/CLI" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A simple CLI library to manage command line applications", + "keywords": [ + "cli", + "command line", + "framework", + "php", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/cli/issues", + "source": "https://github.com/utopia-php/cli/tree/0.15.0" + }, + "time": "2023-03-01T05:55:14+00:00" + }, + { + "name": "utopia-php/database", + "version": "0.49.14", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/database.git", + "reference": "415588c0b98edee9d72cdfe269ff79b14cd8f56d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/database/zipball/415588c0b98edee9d72cdfe269ff79b14cd8f56d", + "reference": "415588c0b98edee9d72cdfe269ff79b14cd8f56d", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-pdo": "*", + "php": ">=8.0", + "utopia-php/cache": "0.10.*", + "utopia-php/framework": "0.33.*", + "utopia-php/mongo": "0.3.*" + }, + "require-dev": { + "fakerphp/faker": "^1.14", + "laravel/pint": "1.13.*", + "pcov/clobber": "^2.0", + "phpstan/phpstan": "1.10.*", + "phpunit/phpunit": "^9.4", + "rregeer/phpunit-coverage-check": "^0.3.1", + "swoole/ide-helper": "4.8.0", + "utopia-php/cli": "^0.14.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Database\\": "src/Database" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A simple library to manage application persistence using multiple database adapters", + "keywords": [ + "database", + "framework", + "php", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/database/issues", + "source": "https://github.com/utopia-php/database/tree/0.49.14" + }, + "time": "2024-06-20T02:39:23+00:00" + }, + { + "name": "utopia-php/dsn", + "version": "0.2.1", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/dsn.git", + "reference": "42ee37a3d1785100b2f69091c9d4affadb6846eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/dsn/zipball/42ee37a3d1785100b2f69091c9d4affadb6846eb", + "reference": "42ee37a3d1785100b2f69091c9d4affadb6846eb", + "shasum": "" + }, + "require": { + "php": ">=8.0" + }, + "require-dev": { + "laravel/pint": "1.2.*", + "phpunit/phpunit": "^9.3", + "squizlabs/php_codesniffer": "^3.6", + "vimeo/psalm": "4.0.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\DSN\\": "src/DSN" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A simple library for parsing and managing Data Source Names ( DSNs )", + "keywords": [ + "dsn", + "framework", + "php", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/dsn/issues", + "source": "https://github.com/utopia-php/dsn/tree/0.2.1" + }, + "time": "2024-05-07T02:01:25+00:00" + }, + { + "name": "utopia-php/framework", + "version": "0.33.6", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/http.git", + "reference": "8fe57da0cecd57e3b17cd395b4a666a24f4c07a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/http/zipball/8fe57da0cecd57e3b17cd395b4a666a24f4c07a6", + "reference": "8fe57da0cecd57e3b17cd395b4a666a24f4c07a6", + "shasum": "" + }, + "require": { + "php": ">=8.0" + }, + "require-dev": { + "laravel/pint": "^1.2", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.25" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A simple, light and advanced PHP framework", + "keywords": [ + "framework", + "php", + "upf" + ], + "support": { + "issues": "https://github.com/utopia-php/http/issues", + "source": "https://github.com/utopia-php/http/tree/0.33.6" + }, + "time": "2024-03-21T18:10:57+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/storage", + "version": "0.18.4", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/storage.git", + "reference": "94ab8758fabcefee5c5fa723616e45719833f922" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/storage/zipball/94ab8758fabcefee5c5fa723616e45719833f922", + "reference": "94ab8758fabcefee5c5fa723616e45719833f922", + "shasum": "" + }, + "require": { + "ext-brotli": "*", + "ext-fileinfo": "*", + "ext-lz4": "*", + "ext-snappy": "*", + "ext-xz": "*", + "ext-zlib": "*", + "ext-zstd": "*", + "php": ">=8.0", + "utopia-php/framework": "0.*.*", + "utopia-php/system": "0.*.*" + }, + "require-dev": { + "laravel/pint": "1.2.*", + "phpunit/phpunit": "^9.3", + "vimeo/psalm": "4.0.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Storage\\": "src/Storage" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A simple Storage library to manage application storage", + "keywords": [ + "framework", + "php", + "storage", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/storage/issues", + "source": "https://github.com/utopia-php/storage/tree/0.18.4" + }, + "time": "2024-04-02T08:24:09+00:00" + }, + { + "name": "utopia-php/system", + "version": "0.8.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/system.git", + "reference": "a2cbfb3c69b9ecb8b6f06c5774f3cf279ea7665e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/system/zipball/a2cbfb3c69b9ecb8b6f06c5774f3cf279ea7665e", + "reference": "a2cbfb3c69b9ecb8b6f06c5774f3cf279ea7665e", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "require-dev": { + "laravel/pint": "1.13.*", + "phpstan/phpstan": "1.10.*", + "phpunit/phpunit": "9.6.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\System\\": "src/System" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eldad Fux", + "email": "eldad@appwrite.io" + }, + { + "name": "Torsten Dittmann", + "email": "torsten@appwrite.io" + } + ], + "description": "A simple library for obtaining information about the host's system.", + "keywords": [ + "framework", + "php", + "system", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/system/issues", + "source": "https://github.com/utopia-php/system/tree/0.8.0" + }, + "time": "2024-04-01T10:22:28+00:00" + } + ], + "packages-dev": [ + { + "name": "graham-campbell/result-type", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/fbd48bce38f73f8a4ec8583362e732e4095e5862", + "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2023-11-12T22:16:48+00:00" + }, + { + "name": "laravel/pint", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/pint.git", + "reference": "08bcf51e520a5e5aea458fc600ac4869f6934a66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/pint/zipball/08bcf51e520a5e5aea458fc600ac4869f6934a66", + "reference": "08bcf51e520a5e5aea458fc600ac4869f6934a66", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "ext-tokenizer": "*", + "ext-xml": "*", + "php": "^8.1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.21.1", + "illuminate/view": "^10.5.1", + "laravel-zero/framework": "^10.1.2", + "mockery/mockery": "^1.5.1", + "nunomaduro/larastan": "^2.5.1", + "nunomaduro/termwind": "^1.15.1", + "pestphp/pest": "^2.4.0" + }, + "bin": [ + "builds/pint" + ], + "type": "project", + "autoload": { + "psr-4": { + "App\\": "app/", + "Database\\Seeders\\": "database/seeders/", + "Database\\Factories\\": "database/factories/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "An opinionated code formatter for PHP.", + "homepage": "https://laravel.com", + "keywords": [ + "format", + "formatter", + "lint", + "linter", + "php" + ], + "support": { + "issues": "https://github.com/laravel/pint/issues", + "source": "https://github.com/laravel/pint" + }, + "time": "2023-08-30T07:53:32+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.12.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2024-06-12T14:39:25+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.1.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" + }, + "time": "2024-07-01T20:03:41+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.2", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/80735db690fe4fc5c76dfa7f9b770634285fa820", + "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2023-11-12T21:59:55+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.11.7", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/52d2bbfdcae7f895915629e4694e9497d0f8e28d", + "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2024-07-06T11:17:41+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "10.1.15", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae", + "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.0", + "phpunit/php-text-template": "^3.0", + "sebastian/code-unit-reverse-lookup": "^3.0", + "sebastian/complexity": "^3.0", + "sebastian/environment": "^6.0", + "sebastian/lines-of-code": "^2.0", + "sebastian/version": "^4.0", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.1" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.15" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-06-29T08:25:15+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "4.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T06:24:48+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:56:09+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T14:07:24+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:57:52+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "10.5.27", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "2425f713b2a5350568ccb1a2d3984841a23e83c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2425f713b2a5350568ccb1a2d3984841a23e83c5", + "reference": "2425f713b2a5350568ccb1a2d3984841a23e83c5", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.15", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-invoker": "^4.0.0", + "phpunit/php-text-template": "^3.0.1", + "phpunit/php-timer": "^6.0.0", + "sebastian/cli-parser": "^2.0.1", + "sebastian/code-unit": "^2.0.0", + "sebastian/comparator": "^5.0.1", + "sebastian/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.2", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.0", + "sebastian/type": "^4.0.0", + "sebastian/version": "^4.0.1" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.27" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2024-07-10T11:48:06+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:12:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:58:43+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:59:15+00:00" + }, + { + "name": "sebastian/comparator", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2db5010a484d53ebf536087a70b4a5423c102372" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2db5010a484d53ebf536087a70b4a5423c102372", + "reference": "2db5010a484d53ebf536087a70b4a5423c102372", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-14T13:18:12+00:00" + }, + { + "name": "sebastian/complexity", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "68ff824baeae169ec9f2137158ee529584553799" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:37:17+00:00" + }, + { + "name": "sebastian/diff", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:15:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "6.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-23T08:47:14+00:00" + }, + { + "name": "sebastian/exporter", + "version": "5.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "955288482d97c19a372d3f31006ab3f37da47adf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", + "reference": "955288482d97c19a372d3f31006ab3f37da47adf", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:17:12+00:00" + }, + { + "name": "sebastian/global-state", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:19:19+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:38:20+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:08:32+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:06:18+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "05909fb5bc7df4c52992396d0116aed689f93712" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", + "reference": "05909fb5bc7df4c52992396d0116aed689f93712", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:05:40+00:00" + }, + { + "name": "sebastian/type", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:10:45+00:00" + }, + { + "name": "sebastian/version", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-07T11:34:05+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.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-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.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-06-19T12:30:46+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.6.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", + "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.2", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.2", + "symfony/polyfill-ctype": "^1.24", + "symfony/polyfill-mbstring": "^1.24", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2023-11-12T22:43:29+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "8.3", + "ext-curl": "*", + "ext-openssl": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/tests/Migration/unit/General/TransferTest.php b/tests/Migration/unit/General/TransferTest.php index 76566f4..26baf71 100644 --- a/tests/Migration/unit/General/TransferTest.php +++ b/tests/Migration/unit/General/TransferTest.php @@ -1,6 +1,6 @@ Date: Mon, 15 Jul 2024 10:57:57 +0300 Subject: [PATCH 094/185] Remove strip function --- src/Migration/Sources/Appwrite.php | 39 +++++++----------------------- 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index f5ddf04..4659ffd 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -573,31 +573,6 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void } } - public function stripMetadata(array $document): array - { - unset($document['$collectionId']); - unset($document['$databaseId']); - -// -// if ($root) { -// unset($document['$id']); -// } -// -// unset($document['$permissions']); -// -// unset($document['$updatedAt']); -// unset($document['$createdAt']); -// unset($document['$tenant']); -// -// foreach ($document as $key => $value) { -// if (is_array($value)) { -// $document[$key] = $this->stripMetadata($value, false); -// } -// } - - return $document; - } - /** * @throws AppwriteException */ @@ -649,10 +624,16 @@ private function exportDocuments(int $batchSize): void foreach ($response['documents'] as $document) { //$id = $document['$id']; //$permissions = $document['$permissions']; - $document = $this->stripMetadata($document); + + $data = $document; + + unset($data['$permissions']); + unset($data['$collectionId']); + unset($data['$databaseId']); + unset($data['$id']); // Certain Appwrite versions allowed for data to be required but null - // This isn't allowed in modern versions, so we need to remove it by comparing their attributes and replacing it with default value. + // This isn't allowed in modern versions so we need to remove it by comparing their attributes and replacing it with default value. $attributes = $this->cache->get(Attribute::getName()); foreach ($attributes as $attribute) { /** @var Attribute $attribute */ @@ -684,12 +665,10 @@ private function exportDocuments(int $batchSize): void } } - $cleanData = $this->stripMetadata($document); - $documents[] = new Document( $document['$id'], $collection, - $cleanData, + $data, $document['$permissions'] ); $lastDocument = $document['$id']; From 647d52aa46e62e984a183e8994b85c2219408510 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 15 Jul 2024 11:07:46 +0300 Subject: [PATCH 095/185] No clonings --- src/Migration/Sources/Appwrite.php | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 4659ffd..ce50e88 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -592,7 +592,7 @@ private function exportDocuments(int $batchSize): void if ($lastDocument) { $queries[] = Query::cursorAfter($lastDocument); } -// + // $selects = []; // $attributes = $this->cache->get(Attribute::getName()); // foreach ($attributes as $attribute) { @@ -622,15 +622,13 @@ private function exportDocuments(int $batchSize): void ); foreach ($response['documents'] as $document) { - //$id = $document['$id']; - //$permissions = $document['$permissions']; - - $data = $document; + $id = $document['$id']; + $permissions = $document['$permissions']; - unset($data['$permissions']); - unset($data['$collectionId']); - unset($data['$databaseId']); - unset($data['$id']); + unset($document['$id']); + unset($document['$permissions']); + unset($document['$collectionId']); + unset($document['$databaseId']); // Certain Appwrite versions allowed for data to be required but null // This isn't allowed in modern versions so we need to remove it by comparing their attributes and replacing it with default value. @@ -666,12 +664,12 @@ private function exportDocuments(int $batchSize): void } $documents[] = new Document( - $document['$id'], + $id, $collection, - $data, - $document['$permissions'] + $document, + $permissions ); - $lastDocument = $document['$id']; + $lastDocument = $id; } $this->callback($documents); From 8a0d3ef0bee6acf0bcdedb96ee749f19279dec6f Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 15 Jul 2024 11:22:20 +0300 Subject: [PATCH 096/185] line --- src/Migration/Sources/Appwrite.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index ce50e88..1c672e7 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -669,6 +669,7 @@ private function exportDocuments(int $batchSize): void $document, $permissions ); + $lastDocument = $id; } From 962574058b4406c4c4e8055852936de0539bfba4 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 15 Jul 2024 20:52:11 +0900 Subject: [PATCH 097/185] Run Linter --- src/Migration/Sources/Appwrite.php | 38 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index ff24a4c..f47adcf 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -605,25 +605,25 @@ private function exportDocuments(int $batchSize): void $queries[] = Query::cursorAfter($lastDocument); } -// $selects = []; -// $attributes = $this->cache->get(Attribute::getName()); -// foreach ($attributes as $attribute) { -// /** @var Attribute $attribute */ -// if ($attribute->getCollection()->getId() === $collection->getId()) { -// -// var_dump(' === exportDocuments === '); -// var_dump($attribute->getKey()); -// var_dump($attribute); -// $selects[] = $attribute->getKey(); -// if($attribute->getTypeName() === Attribute::TYPE_RELATIONSHIP){ -// var_dump(' === this is TYPE_RELATIONSHIP === '); -// } -// } -// } -// -// if(!empty($selects)){ -// $queries[] = Query::select($selects); -// } + // $selects = []; + // $attributes = $this->cache->get(Attribute::getName()); + // foreach ($attributes as $attribute) { + // /** @var Attribute $attribute */ + // if ($attribute->getCollection()->getId() === $collection->getId()) { + // + // var_dump(' === exportDocuments === '); + // var_dump($attribute->getKey()); + // var_dump($attribute); + // $selects[] = $attribute->getKey(); + // if($attribute->getTypeName() === Attribute::TYPE_RELATIONSHIP){ + // var_dump(' === this is TYPE_RELATIONSHIP === '); + // } + // } + // } + // + // if(!empty($selects)){ + // $queries[] = Query::select($selects); + // } $queries[] = Query::select(['*', '$id', '$permissions', '$updatedAt', '$createdAt']); // We want Relations flat! From ec38e468cd7c958ade1ccd9ee268e905f6210739 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 15 Jul 2024 17:46:41 +0300 Subject: [PATCH 098/185] Convert null to empty string --- src/Migration/Transfer.php | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index c6c377b..1e4b97e 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -47,7 +47,7 @@ class Transfer Resource::TYPE_DATABASE, Resource::TYPE_FUNCTION, Resource::TYPE_USER, - Resource::TYPE_TEAM + Resource::TYPE_TEAM, ]; public const STORAGE_MAX_CHUNK_SIZE = 1024 * 1024 * 5; // 5MB @@ -119,7 +119,7 @@ public function getStatusCounters(): array foreach ($this->cache->getAll() as $resources) { foreach ($resources as $resource) { - /** @var Resource $resource */ + /** @var resource $resource */ if (isset($status[$resource->getName()])) { $status[$resource->getName()][$resource->getStatus()]++; if ($status[$resource->getName()]['pending'] > 0) { @@ -164,11 +164,13 @@ public function getStatusCounters(): array /** * Transfer Resources between adapters * - * @param array $resources Resources to transfer - * @param callable $callback Callback to run after transfer - * @param string $rootResourceId Root resource ID, If enabled you can only transfer a single root resource + * @param array $resources Resources to transfer + * @param callable $callback Callback to run after transfer + * @param string|null $rootResourceId Root resource ID, If enabled you can only transfer a single root resource + * + * @throws \Exception */ - public function run(array $resources, callable $callback, string $rootResourceId = ''): void + public function run(array $resources, callable $callback, string $rootResourceId = null): void { // Allows you to push entire groups if you want. $computedResources = []; @@ -183,6 +185,9 @@ public function run(array $resources, callable $callback, string $rootResourceId $computedResources = array_map('strtolower', $computedResources); // Check we don't have multiple root resources if rootResourceId is set + + $rootResourceId = $rootResourceId ?? ''; // Convert null to empty string + if ($rootResourceId) { $rootResourceCount = count(array_intersect($computedResources, self::ROOT_RESOURCES)); @@ -216,7 +221,7 @@ public function getCurrentResource(): string /** * Get Transfer Report * - * @param string $statusLevel If no status level is provided, all status types will be returned. + * @param string $statusLevel If no status level is provided, all status types will be returned. * @return array> */ public function getReport(string $statusLevel = ''): array @@ -250,7 +255,7 @@ public static function extractServices(array $services): array { $resources = []; foreach ($services as $service) { - var_dump("converting resource === " . $service); + var_dump('converting resource === '.$service); $resources = match ($service) { self::GROUP_FUNCTIONS => array_merge($resources, self::GROUP_FUNCTIONS_RESOURCES), self::GROUP_STORAGE => array_merge($resources, self::GROUP_STORAGE_RESOURCES), From 0d8df05d688f2e08577783a82e7ff77edee6052d Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 16 Jul 2024 14:09:27 +1200 Subject: [PATCH 099/185] Sync main --- .gitignore | 3 +- bin/MigrationCLI.php | 107 +++++++++++++++--- composer.json | 4 +- src/Migration/Destinations/Appwrite.php | 19 ++-- src/Migration/Exception.php | 19 +++- src/Migration/Resources/Auth/User.php | 29 +---- src/Migration/Sources/Appwrite.php | 107 ++++++++++++------ src/Migration/Sources/Firebase.php | 71 ++++++------ src/Migration/Sources/NHost.php | 78 +++++++------ src/Migration/Sources/Supabase.php | 51 ++++----- src/Migration/Transfer.php | 5 +- tests/Migration/E2E/Sources/Base.php | 2 +- tests/Migration/E2E/Sources/NHostTest.php | 6 +- tests/Migration/E2E/Sources/SupabaseTest.php | 6 +- .../{ => Unit}/Adapters/MockDestination.php | 6 +- .../{ => Unit}/Adapters/MockSource.php | 2 +- .../{unit => Unit}/General/TransferTest.php | 9 +- 17 files changed, 309 insertions(+), 215 deletions(-) rename tests/Migration/{ => Unit}/Adapters/MockDestination.php (93%) rename tests/Migration/{ => Unit}/Adapters/MockSource.php (99%) rename tests/Migration/{unit => Unit}/General/TransferTest.php (87%) diff --git a/.gitignore b/.gitignore index a12c277..d16782c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ test-service-account.json .DS_Store localBackup/ .vscode/ -.env.prod \ No newline at end of file +.env.prod +serviceAccount.json \ No newline at end of file diff --git a/bin/MigrationCLI.php b/bin/MigrationCLI.php index 1db5bdf..540e0da 100644 --- a/bin/MigrationCLI.php +++ b/bin/MigrationCLI.php @@ -3,8 +3,14 @@ require_once __DIR__.'/.././vendor/autoload.php'; use Dotenv\Dotenv; +use Utopia\Migration\Destination; use Utopia\Migration\Destinations\Appwrite as DestinationsAppwrite; +use Utopia\Migration\Destinations\Local; +use Utopia\Migration\Source; use Utopia\Migration\Sources\Appwrite; +use Utopia\Migration\Sources\Firebase; +use Utopia\Migration\Sources\NHost; +use Utopia\Migration\Sources\Supabase; use Utopia\Migration\Transfer; /** @@ -14,6 +20,10 @@ class MigrationCLI { protected Transfer $transfer; + protected mixed $source; + + protected mixed $destination; + /** * Prints the current status of migrations as a table after wiping the screen */ @@ -24,10 +34,81 @@ public function drawFrame(): void $statusCounters = $this->transfer->getStatusCounters(); $mask = "| %15.15s | %-7.7s | %10.10s | %7.7s | %7.7s | %8.8s |\n"; - printf($mask, 'Resource', 'Pending', 'Processing', 'Skipped', 'Warning', 'Success'); - printf($mask, '-------------', '-------------', '-------------', '-------------', '-------------', '-------------'); + printf($mask, 'Resource', 'Pending', 'Processing', 'Skipped', 'Warning', 'Error', 'Success'); + printf($mask, '-------------', '-------------', '-------------', '-------------', '-------------', '-------------', '-------------'); foreach ($statusCounters as $resource => $data) { - printf($mask, $resource, $data['pending'], $data['processing'], $data['skip'], $data['warning'], $data['success']); + printf($mask, $resource, $data['pending'], $data['processing'], $data['skip'], $data['warning'], $data['error'], $data['success']); + } + + // Render Errors + $destErrors = $this->destination->getErrors(); + if (! empty($destErrors)) { + echo "\n\nDestination Errors:\n"; + foreach ($destErrors as $error) { + /** @var Utopia\Migration\Exception $error */ + echo $error->getResourceName().'['.$error->getResourceId().'] - '.$error->getMessage()."\n"; + } + } + + $sourceErrors = $this->source->getErrors(); + if (! empty($sourceErrors)) { + echo "\n\nSource Errors:\n"; + foreach ($sourceErrors as $error) { + /** @var Utopia\Migration\Exception $error */ + echo $error->getResourceGroup().'['.$error->getResourceId().'] - '.$error->getMessage()."\n"; + } + } + } + + public function getSource(): Source + { + switch ($_ENV['SOURCE_PROVIDER']) { + case 'appwrite': + return new Appwrite( + $_ENV['SOURCE_APPWRITE_TEST_PROJECT'], + $_ENV['SOURCE_APPWRITE_TEST_ENDPOINT'], + $_ENV['SOURCE_APPWRITE_TEST_KEY'] + ); + case 'supabase': + return new Supabase( + $_ENV['SOURCE_SUPABASE_TEST_ENDPOINT'], + $_ENV['SOURCE_SUPABASE_TEST_KEY'], + $_ENV['SOURCE_SUPABASE_TEST_HOST'], + $_ENV['SOURCE_SUPABASE_TEST_DATBASE_NAME'], + $_ENV['SOURCE_SUPABASE_TEST_DATABASE_USER'], + $_ENV['SOURCE_SUPABASE_TEST_DATABASE_PASSWORD'] + ); + case 'firebase': + return new Firebase( + json_decode(file_get_contents(__DIR__.'/serviceAccount.json'), true) + ); + case 'nhost': + return new NHost( + $_ENV['SOURCE_NHOST_TEST_SUBDOMAIN'], + $_ENV['SOURCE_NHOST_TEST_REGION'], + $_ENV['SOURCE_NHOST_TEST_ADMIN_SECRET'], + $_ENV['SOURCE_NHOST_TEST_DATABASE_NAME'], + $_ENV['SOURCE_NHOST_TEST_DATABASE_USER'], + $_ENV['SOURCE_NHOST_TEST_DATABASE_PASSWORD'] + ); + default: + throw new Exception('Invalid source provider'); + } + } + + public function getDestination(): Destination + { + switch ($_ENV['DESTINATION_PROVIDER']) { + case 'appwrite': + return new DestinationsAppwrite( + $_ENV['DESTINATION_APPWRITE_TEST_PROJECT'], + $_ENV['DESTINATION_APPWRITE_TEST_ENDPOINT'], + $_ENV['DESTINATION_APPWRITE_TEST_KEY'] + ); + case 'local': + return new Local('./localBackup'); + default: + throw new Exception('Invalid destination provider'); } } @@ -39,39 +120,29 @@ public function start(): void /** * Initialise All Source Adapters */ - $source = new Appwrite( - $_ENV['SOURCE_APPWRITE_TEST_PROJECT'], - $_ENV['SOURCE_APPWRITE_TEST_ENDPOINT'], - $_ENV['SOURCE_APPWRITE_TEST_KEY'] - ); + $this->source = $this->getSource(); // $source->report(); - $destination = new DestinationsAppwrite( - $_ENV['DESTINATION_APPWRITE_TEST_PROJECT'], - $_ENV['DESTINATION_APPWRITE_TEST_ENDPOINT'], - $_ENV['DESTINATION_APPWRITE_TEST_KEY'] - ); + $this->destination = $this->getDestination(); /** * Initialise Transfer Class */ $this->transfer = new Transfer( - $source, - $destination + $this->source, + $this->destination ); /** * Run Transfer */ $this->transfer->run( - $source->getSupportedResources(), + $this->source->getSupportedResources(), function (array $resources) { $this->drawFrame(); } ); - - var_dump('Complete'); } } diff --git a/composer.json b/composer.json index 5006659..dd9759f 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,9 @@ } }, "autoload-dev": { - "psr-4": {"Utopia\\Tests\\": "tests/Migration"} + "psr-4": { + "Utopia\\Tests\\": "tests/Migration" + } }, "scripts": { "test": "./vendor/bin/phpunit", diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 549f690..36858b0 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -272,10 +272,12 @@ protected function import(array $resources, callable $callback): void $resource->setStatus(Resource::STATUS_ERROR, $e->getMessage()); $this->addError(new Exception( - resourceType: $resource->getGroup(), + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), message: $e->getMessage(), code: $e->getCode(), - resourceId: $resource->getId() + previous: $e )); } @@ -666,22 +668,15 @@ public function importAuthResource(Resource $resource): Resource switch ($resource->getName()) { case Resource::TYPE_USER: /** @var User $resource */ - if (\in_array(User::TYPE_PASSWORD, $resource->getTypes())) { + if (! empty($resource->getPasswordHash())) { $this->importPasswordUser($resource); - } elseif (\in_array(User::TYPE_OAUTH, $resource->getTypes())) { - $resource->setStatus( - Resource::STATUS_WARNING, - 'OAuth users cannot be imported.' - ); - - return $resource; } else { $this->users->create( $resource->getId(), $resource->getEmail(), - in_array(User::TYPE_PHONE, $resource->getTypes()) ? $resource->getPhone() : null, + $resource->getPhone(), null, - $resource->getName() + $resource->getUsername() ); } diff --git a/src/Migration/Exception.php b/src/Migration/Exception.php index 7200628..22bac9b 100644 --- a/src/Migration/Exception.php +++ b/src/Migration/Exception.php @@ -4,26 +4,35 @@ class Exception extends \Exception { - public string $resourceType; + public string $resourceName; + + public string $resourceGroup; public string $resourceId; public function __construct( - string $resourceType, + string $resourceName, + string $resourceGroup, string $message, int $code = 0, ?\Throwable $previous = null, string $resourceId = '' ) { + $this->resourceName = $resourceName; $this->resourceId = $resourceId; - $this->resourceType = $resourceType; + $this->resourceGroup = $resourceGroup; parent::__construct($message, $code, $previous); } - public function getResourceType(): string + public function getResourceName(): string + { + return $this->resourceName; + } + + public function getResourceGroup(): string { - return $this->resourceType; + return $this->resourceGroup; } public function getResourceId(): string diff --git a/src/Migration/Resources/Auth/User.php b/src/Migration/Resources/Auth/User.php index ea7d11a..e70f856 100644 --- a/src/Migration/Resources/Auth/User.php +++ b/src/Migration/Resources/Auth/User.php @@ -7,16 +7,6 @@ class User extends Resource { - public const string TYPE_PASSWORD = 'password'; - - public const string TYPE_PHONE = 'phone'; - - public const string TYPE_ANONYMOUS = 'anonymous'; - - public const string TYPE_MAGIC = 'magic'; - - public const string TYPE_OAUTH = 'oauth'; - /** * @param string $id * @param string $email @@ -36,8 +26,7 @@ public function __construct( private readonly string $email = '', private readonly string $username = '', private readonly ?Hash $passwordHash = null, - private readonly string $phone = '', - private readonly array $types = [self::TYPE_ANONYMOUS], + private readonly ?string $phone = null, private readonly array $labels = [], private readonly string $oauthProvider = '', private readonly bool $emailVerified = false, @@ -60,7 +49,6 @@ public static function fromArray(array $array): self $array['username'] ?? '', $array['passwordHash'] ?? null, $array['phone'] ?? '', - $array['types'] ?? [self::TYPE_ANONYMOUS], $array['labels'] ?? [], $array['oauthProvider'] ?? '', $array['emailVerified'] ?? false, @@ -81,7 +69,6 @@ public function jsonSerialize(): array 'username' => $this->username, 'passwordHash' => $this->passwordHash, 'phone' => $this->phone, - 'types' => $this->types, 'labels' => $this->labels, 'oauthProvider' => $this->oauthProvider, 'emailVerified' => $this->emailVerified, @@ -102,14 +89,14 @@ public static function getName(): string /** * Get Email */ - public function getEmail(): string + public function getEmail(): ?string { return $this->email; } /** * Get Username */ - public function getUsername(): string + public function getUsername(): ?string { return $this->username; } @@ -125,19 +112,11 @@ public function getPasswordHash(): ?Hash /** * Get Phone */ - public function getPhone(): string + public function getPhone(): ?string { return $this->phone; } - /** - * Get Type - */ - public function getTypes(): array - { - return $this->types; - } - /** * Get Labels */ diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index f47adcf..568a3ef 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -328,7 +328,10 @@ protected function exportGroupAuth(int $batchSize, array $resources): void } catch (\Throwable $e) { $this->addError(new Exception( Resource::TYPE_USER, - $e->getMessage() + Transfer::GROUP_AUTH, + $e->getMessage(), + $e->getCode(), + $e )); } @@ -339,7 +342,10 @@ protected function exportGroupAuth(int $batchSize, array $resources): void } catch (\Throwable $e) { $this->addError(new Exception( Resource::TYPE_TEAM, - $e->getMessage() + Transfer::GROUP_AUTH, + $e->getMessage(), + $e->getCode(), + $e )); } @@ -350,7 +356,10 @@ protected function exportGroupAuth(int $batchSize, array $resources): void } catch (\Throwable $e) { $this->addError(new Exception( Resource::TYPE_MEMBERSHIP, - $e->getMessage() + Transfer::GROUP_AUTH, + $e->getMessage(), + $e->getCode(), + $e )); } } @@ -386,11 +395,10 @@ private function exportUsers(int $batchSize): void foreach ($response['users'] as $user) { $users[] = new User( $user['$id'], - $user['email'], - $user['name'], + empty($user['email']) ? null : $user['email'], + empty($user['name']) ? null : $user['name'], $user['password'] ? new Hash($user['password'], algorithm: $user['hash']) : null, - $user['phone'], - $this->calculateTypes($user), + empty($user['phone']) ? null : $user['phone'], $user['labels'] ?? [], '', $user['emailVerification'] ?? false, @@ -527,7 +535,10 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void $this->addError( new Exception( Resource::TYPE_DATABASE, - $e->getMessage() + Transfer::GROUP_DATABASES, + $e->getMessage(), + $e->getCode(), + $e ) ); } @@ -540,7 +551,10 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void $this->addError( new Exception( Resource::TYPE_COLLECTION, - $e->getMessage() + Transfer::GROUP_DATABASES, + $e->getMessage(), + $e->getCode(), + $e ) ); } @@ -553,7 +567,10 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void $this->addError( new Exception( Resource::TYPE_ATTRIBUTE, - $e->getMessage() + Transfer::GROUP_DATABASES, + $e->getMessage(), + $e->getCode(), + $e ) ); } @@ -566,7 +583,10 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void $this->addError( new Exception( Resource::TYPE_INDEX, - $e->getMessage() + Transfer::GROUP_DATABASES, + $e->getMessage(), + $e->getCode(), + $e ) ); } @@ -579,7 +599,10 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void $this->addError( new Exception( Resource::TYPE_DOCUMENT, - $e->getMessage() + Transfer::GROUP_DATABASES, + $e->getMessage(), + $e->getCode(), + $e ) ); } @@ -1024,26 +1047,7 @@ private function exportIndexes(int $batchSize): void } } - private function calculateTypes(array $user): array - { - if (empty($user['email']) && empty($user['phone'])) { - return [User::TYPE_ANONYMOUS]; - } - - $types = []; - - if (! empty($user['email']) && ! empty($user['password'])) { - $types[] = User::TYPE_PASSWORD; - } - - if (! empty($user['phone'])) { - $types[] = User::TYPE_PHONE; - } - - return $types; - } - - protected function exportGroupStorage(int $batchSize, array $resources): void + protected function exportGroupStorage(int $batchSize, array $resources) { try { if (\in_array(Resource::TYPE_BUCKET, $resources)) { @@ -1053,7 +1057,10 @@ protected function exportGroupStorage(int $batchSize, array $resources): void $this->addError( new Exception( Resource::TYPE_BUCKET, - $e->getMessage() + Transfer::GROUP_STORAGE, + $e->getMessage(), + $e->getCode(), + $e ) ); } @@ -1066,7 +1073,26 @@ protected function exportGroupStorage(int $batchSize, array $resources): void $this->addError( new Exception( Resource::TYPE_FILE, - $e->getMessage() + Transfer::GROUP_STORAGE, + $e->getMessage(), + $e->getCode(), + $e + ) + ); + } + + try { + if (in_array(Resource::TYPE_BUCKET, $resources)) { + $this->exportBuckets($batchSize, true); + } + } catch (\Throwable $e) { + $this->addError( + new Exception( + Resource::TYPE_BUCKET, + Transfer::GROUP_STORAGE, + $e->getMessage(), + $e->getCode(), + $e ) ); } @@ -1146,7 +1172,8 @@ private function exportFiles(int $batchSize): void )); } catch (\Throwable $e) { $this->addError(new Exception( - resourceType: Resource::TYPE_FILE, + resourceName: Resource::TYPE_FILE, + resourceGroup: Transfer::GROUP_STORAGE, message: $e->getMessage(), code: $e->getCode(), resourceId: $file['$id'] @@ -1211,7 +1238,10 @@ protected function exportGroupFunctions(int $batchSize, array $resources): void } catch (\Throwable $e) { $this->addError(new Exception( Resource::TYPE_FUNCTION, - $e->getMessage() + Transfer::GROUP_FUNCTIONS, + $e->getMessage(), + $e->getCode(), + $e )); } @@ -1222,7 +1252,10 @@ protected function exportGroupFunctions(int $batchSize, array $resources): void } catch (\Throwable $e) { $this->addError(new Exception( Resource::TYPE_DEPLOYMENT, - $e->getMessage() + Transfer::GROUP_FUNCTIONS, + $e->getMessage(), + $e->getCode(), + $e )); } } diff --git a/src/Migration/Sources/Firebase.php b/src/Migration/Sources/Firebase.php index 859cb68..03950a4 100644 --- a/src/Migration/Sources/Firebase.php +++ b/src/Migration/Sources/Firebase.php @@ -185,7 +185,10 @@ protected function exportGroupAuth(int $batchSize, array $resources): void $this->addError( new Exception( Resource::TYPE_USER, - $e->getMessage() + Transfer::GROUP_AUTH, + $e->getMessage(), + $e->getCode(), + $e ) ); } @@ -223,19 +226,26 @@ private function exportUsers(int $batchSize): void $nextPageToken = $response['nextPageToken'] ?? null; foreach ($result as $user) { - $users[] = new User( + $transferUser = new User( $user['localId'] ?? '', - $user['email'] ?? '', - $user['displayName'] ?? $user['email'] ?? '', - new Hash($user['passwordHash'] ?? '', $user['salt'] ?? '', Hash::ALGORITHM_SCRYPT_MODIFIED, $hashConfig['saltSeparator'] ?? '', $hashConfig['signerKey'] ?? ''), - $user['phoneNumber'] ?? '', - $this->calculateUserType($user['providerUserInfo'] ?? []), + $user['email'] ?? null, + $user['displayName'] ?? $user['email'] ?? null, + null, + $user['phoneNumber'] ?? null, [], '', $user['emailVerified'] ?? false, false, // Can't get phone number status on firebase :/ $user['disabled'] ?? false ); + + if (array_key_exists('passwordHash', $user)) { + $transferUser->setPasswordHash( + new Hash($user['passwordHash'], $user['salt'] ?? '', Hash::ALGORITHM_SCRYPT_MODIFIED, $hashConfig['saltSeparator'] ?? '', $hashConfig['signerKey'] ?? '') + ); + } + + $users[] = $transferUser; } $this->callback($users); @@ -246,32 +256,7 @@ private function exportUsers(int $batchSize): void } } - private function calculateUserType(array $providerData): array - { - if (count($providerData) === 0) { - return [User::TYPE_ANONYMOUS]; - } - - $types = []; - - foreach ($providerData as $provider) { - switch ($provider['providerId']) { - case 'password': - $types[] = User::TYPE_PASSWORD; - break; - case 'phone': - $types[] = User::TYPE_PHONE; - break; - default: - $types[] = User::TYPE_OAUTH; - break; - } - } - - return $types; - } - - protected function exportGroupDatabases(int $batchSize, array $resources): void + protected function exportGroupDatabases(int $batchSize, array $resources) { // Check if Firestore is enabled try { @@ -297,7 +282,10 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void $this->addError( new Exception( Resource::TYPE_DATABASE, - $e->getMessage() + Transfer::GROUP_DATABASES, + $e->getMessage(), + $e->getCode(), + $e ) ); } @@ -310,7 +298,10 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void $this->addError( new Exception( Resource::TYPE_COLLECTION, - $e->getMessage() + Transfer::GROUP_DATABASES, + $e->getMessage(), + $e->getCode(), + $e ) ); } @@ -568,7 +559,10 @@ protected function exportGroupStorage(int $batchSize, array $resources): void } catch (\Throwable $e) { $this->addError(new Exception( Resource::TYPE_BUCKET, - $e->getMessage() + Transfer::GROUP_STORAGE, + $e->getMessage(), + $e->getCode(), + $e )); } @@ -579,7 +573,10 @@ protected function exportGroupStorage(int $batchSize, array $resources): void } catch (\Throwable $e) { $this->addError(new Exception( Resource::TYPE_FILE, - $e->getMessage() + Transfer::GROUP_STORAGE, + $e->getMessage(), + $e->getCode(), + $e )); } diff --git a/src/Migration/Sources/NHost.php b/src/Migration/Sources/NHost.php index 8a5ea0e..e0dc95d 100644 --- a/src/Migration/Sources/NHost.php +++ b/src/Migration/Sources/NHost.php @@ -219,7 +219,10 @@ protected function exportGroupAuth(int $batchSize, array $resources): void } catch (\Throwable $e) { $this->addError(new Exception( Resource::TYPE_USER, - $e->getMessage() + Transfer::GROUP_AUTH, + $e->getMessage(), + $e->getCode(), + $e )); } } @@ -245,13 +248,12 @@ private function exportUsers(int $batchSize): void $transferUsers = []; foreach ($users as $user) { - $transferUsers[] = new User( + $transferUser = new User( $user['id'], - $user['email'] ?? '', - $user['display_name'] ?? '', - new Hash($user['password_hash'], '', Hash::ALGORITHM_BCRYPT), - $user['phone_number'] ?? '', - $this->calculateUserTypes($user), + $user['email'] ?? null, + $user['display_name'] ?? null, + null, + $user['phone_number'] ?? null, [], '', $user['email_verified'] ?? false, @@ -259,6 +261,12 @@ private function exportUsers(int $batchSize): void $user['disabled'] ?? false, [] ); + + if (array_key_exists('password_hash', $user)) { + $transferUser->setPasswordHash(new Hash($user['password_hash'], '', Hash::ALGORITHM_BCRYPT)); + } + + $transferUsers[] = $transferUser; } $this->callback($transferUsers); @@ -275,7 +283,10 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void $this->addError( new Exception( Resource::TYPE_DATABASE, - $e->getMessage() + Transfer::GROUP_DATABASES, + $e->getMessage(), + $e->getCode(), + $e ) ); } @@ -288,7 +299,10 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void $this->addError( new Exception( Resource::TYPE_COLLECTION, - $e->getMessage() + Transfer::GROUP_DATABASES, + $e->getMessage(), + $e->getCode(), + $e ) ); } @@ -301,7 +315,10 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void $this->addError( new Exception( Resource::TYPE_ATTRIBUTE, - $e->getMessage() + Transfer::GROUP_DATABASES, + $e->getMessage(), + $e->getCode(), + $e ) ); } @@ -314,7 +331,10 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void $this->addError( new Exception( Resource::TYPE_DOCUMENT, - $e->getMessage() + Transfer::GROUP_DATABASES, + $e->getMessage(), + $e->getCode(), + $e ) ); } @@ -327,7 +347,10 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void $this->addError( new Exception( Resource::TYPE_INDEX, - $e->getMessage() + Transfer::GROUP_DATABASES, + $e->getMessage(), + $e->getCode(), + $e ) ); } @@ -590,26 +613,7 @@ private function convertIndex(array $index, Collection $collection): Index|false } } - private function calculateUserTypes(array $user): array - { - if (empty($user['password_hash']) && empty($user['phone_number'])) { - return [User::TYPE_ANONYMOUS]; - } - - $types = []; - - if (! empty($user['password_hash'])) { - $types[] = User::TYPE_PASSWORD; - } - - if (! empty($user['phone_number'])) { - $types[] = User::TYPE_PHONE; - } - - return $types; - } - - protected function exportGroupStorage(int $batchSize, array $resources): void + protected function exportGroupStorage(int $batchSize, array $resources) { try { if (\in_array(Resource::TYPE_BUCKET, $resources)) { @@ -619,7 +623,10 @@ protected function exportGroupStorage(int $batchSize, array $resources): void $this->addError( new Exception( Resource::TYPE_BUCKET, - $e->getMessage() + Transfer::GROUP_STORAGE, + $e->getMessage(), + $e->getCode(), + $e ) ); } @@ -632,7 +639,10 @@ protected function exportGroupStorage(int $batchSize, array $resources): void $this->addError( new Exception( Resource::TYPE_FILE, - $e->getMessage() + Transfer::GROUP_STORAGE, + $e->getMessage(), + $e->getCode(), + $e ) ); } diff --git a/src/Migration/Sources/Supabase.php b/src/Migration/Sources/Supabase.php index 51581a4..ebc7833 100644 --- a/src/Migration/Sources/Supabase.php +++ b/src/Migration/Sources/Supabase.php @@ -355,7 +355,10 @@ protected function exportGroupAuth(int $batchSize, array $resources): void } catch (\Throwable $e) { $this->addError(new Exception( Resource::TYPE_BUCKET, - $e->getMessage() + Transfer::GROUP_STORAGE, + $e->getMessage(), + $e->getCode(), + $e )); } } @@ -379,13 +382,12 @@ private function exportUsers(int $batchSize): void $transferUsers = []; foreach ($users as $user) { - $transferUsers[] = new User( + $transferUser = new User( $user['id'], - $user['email'] ?? '', + $user['email'] ?? null, '', - new Hash($user['encrypted_password'], '', Hash::ALGORITHM_BCRYPT), - $user['phone'] ?? '', - $this->calculateAuthTypes($user), + null, + $user['phone'] ?? null, [], '', ! empty($user['email_confirmed_at']), @@ -393,6 +395,12 @@ private function exportUsers(int $batchSize): void false, [] ); + + if (array_key_exists('encrypted_password', $user)) { + $transferUser->setPasswordHash(new Hash($user['encrypted_password'], '', Hash::ALGORITHM_BCRYPT)); + } + + $transferUsers[] = $transferUser; } $this->callback($transferUsers); @@ -410,26 +418,7 @@ private function convertMimes(array $mimes): array return $extensions; } - private function calculateAuthTypes(array $user): array - { - if (empty($user['encrypted_password']) && empty($user['phone'])) { - return [User::TYPE_ANONYMOUS]; - } - - $types = []; - - if (! empty($user['encrypted_password'])) { - $types[] = User::TYPE_PASSWORD; - } - - if (! empty($user['phone'])) { - $types[] = User::TYPE_PHONE; - } - - return $types; - } - - protected function exportGroupStorage(int $batchSize, array $resources): void + protected function exportGroupStorage(int $batchSize, array $resources) { try { if (\in_array(Resource::TYPE_BUCKET, $resources)) { @@ -438,7 +427,10 @@ protected function exportGroupStorage(int $batchSize, array $resources): void } catch (\Throwable $e) { $this->addError(new Exception( Resource::TYPE_BUCKET, - $e->getMessage() + Transfer::GROUP_STORAGE, + $e->getMessage(), + $e->getCode(), + $e )); } @@ -449,7 +441,10 @@ protected function exportGroupStorage(int $batchSize, array $resources): void } catch (\Throwable $e) { $this->addError(new Exception( Resource::TYPE_BUCKET, - $e->getMessage() + Transfer::GROUP_STORAGE, + $e->getMessage(), + $e->getCode(), + $e )); } } diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index 1e4b97e..eae930d 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -131,8 +131,9 @@ public function getStatusCounters(): array // Process Destination Errors foreach ($this->destination->getErrors() as $error) { - if (isset($status[$error->getResourceType()])) { - $status[$error->getResourceType()][Resource::STATUS_ERROR]++; + /** @var Exception $error */ + if (isset($status[$error->getResourceGroup()])) { + $status[$error->getResourceGroup()][Resource::STATUS_ERROR]++; } } diff --git a/tests/Migration/E2E/Sources/Base.php b/tests/Migration/E2E/Sources/Base.php index e70305c..009c147 100644 --- a/tests/Migration/E2E/Sources/Base.php +++ b/tests/Migration/E2E/Sources/Base.php @@ -2,12 +2,12 @@ namespace Utopia\Tests\E2E\Sources; +use Migration\Unit\Adapters\MockDestination; use PHPUnit\Framework\TestCase; use Utopia\Migration\Destination; use Utopia\Migration\Resource; use Utopia\Migration\Source; use Utopia\Migration\Transfer; -use Utopia\Tests\Adapters\MockDestination; abstract class Base extends TestCase { diff --git a/tests/Migration/E2E/Sources/NHostTest.php b/tests/Migration/E2E/Sources/NHostTest.php index 2fe9615..b6bfe4b 100644 --- a/tests/Migration/E2E/Sources/NHostTest.php +++ b/tests/Migration/E2E/Sources/NHostTest.php @@ -2,12 +2,12 @@ namespace Utopia\Tests\E2E\Sources; +use Migration\Unit\Adapters\MockDestination; use Utopia\Migration\Destination; use Utopia\Migration\Resource; use Utopia\Migration\Source; use Utopia\Migration\Sources\NHost; use Utopia\Migration\Transfer; -use Utopia\Tests\Adapters\MockDestination; class NHostTest extends Base { @@ -96,8 +96,7 @@ public function testRunTransfer($state) { $this->transfer->run( $this->source->getSupportedResources(), - function () { - } + function () {} ); $this->assertEquals(0, count($this->transfer->getReport('error'))); @@ -157,7 +156,6 @@ public function testValidateUserTransfer($state): void $this->assertEquals('$2a$10$ARQ/f.K6OmCjZ8XF0U.6fezPMlxDqsmcl0Rs6xQVkvj62u7gcSzOW', $foundUser->getPasswordHash()->getHash()); $this->assertEquals('bcrypt', $foundUser->getPasswordHash()->getAlgorithm()); $this->assertEquals('test@test.com', $foundUser->getUsername()); - $this->assertEquals(['password'], $foundUser->getTypes()); } /** diff --git a/tests/Migration/E2E/Sources/SupabaseTest.php b/tests/Migration/E2E/Sources/SupabaseTest.php index b4f45bb..556dc02 100644 --- a/tests/Migration/E2E/Sources/SupabaseTest.php +++ b/tests/Migration/E2E/Sources/SupabaseTest.php @@ -2,12 +2,12 @@ namespace Utopia\Tests\E2E\Sources; +use Migration\Unit\Adapters\MockDestination; use Utopia\Migration\Destination; use Utopia\Migration\Resource; use Utopia\Migration\Source; use Utopia\Migration\Sources\Supabase; use Utopia\Migration\Transfer; -use Utopia\Tests\Adapters\MockDestination; class SupabaseTest extends Base { @@ -76,8 +76,7 @@ public function testRunTransfer($state) { $this->transfer->run( $this->source->getSupportedResources(), - function () { - } + function () {} ); $this->assertEquals(0, count($this->transfer->getReport('error'))); @@ -137,7 +136,6 @@ public function testValidateUserTransfer($state): void $this->assertEquals('success', $foundUser->getStatus()); $this->assertEquals('$2a$10$NGZAAOfXeheUoH9V3dnRoeR.r3J5ynnSZ6KjvHxOUlV8XUrulJzQa', $foundUser->getPasswordHash()->getHash()); $this->assertEquals('bcrypt', $foundUser->getPasswordHash()->getAlgorithm()); - $this->assertEquals(['password'], $foundUser->getTypes()); } /** diff --git a/tests/Migration/Adapters/MockDestination.php b/tests/Migration/Unit/Adapters/MockDestination.php similarity index 93% rename from tests/Migration/Adapters/MockDestination.php rename to tests/Migration/Unit/Adapters/MockDestination.php index d36a865..b6970fa 100644 --- a/tests/Migration/Adapters/MockDestination.php +++ b/tests/Migration/Unit/Adapters/MockDestination.php @@ -1,9 +1,11 @@ transfer->run([Resource::TYPE_DATABASE], function () {}, 'test'); - $this->assertEquals(1, count($this->destination->getResourceTypeData(Transfer::GROUP_DATABASES, Resource::TYPE_DATABASE))); + $this->assertCount(1, $this->destination->getResourceTypeData(Transfer::GROUP_DATABASES, Resource::TYPE_DATABASE)); $database = $this->destination->getResourceById(Transfer::GROUP_DATABASES, Resource::TYPE_DATABASE, 'test'); /** @var Database $database */ From f43269ec2acaf829eadc0b146d2b3e5fccf5b590 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 16 Jul 2024 14:09:54 +1200 Subject: [PATCH 100/185] Fix stan --- src/Migration/Destinations/Appwrite.php | 11 ++++++----- src/Migration/Resources/Auth/User.php | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 36858b0..dc1db69 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -6,6 +6,7 @@ use Appwrite\Client; use Appwrite\Enums\Compression; use Appwrite\Enums\IndexType; +use Appwrite\Enums\PasswordHash; use Appwrite\Enums\RelationMutate; use Appwrite\Enums\RelationshipType; use Appwrite\Enums\Runtime; @@ -183,7 +184,7 @@ public function report(array $resources = []): array $this->databases->listIndexes('', ''); $scope = 'indexes.write'; - $this->databases->createIndex('', '', '', '', []); + $this->databases->createIndex('', '', '', IndexType::KEY(), []); } if (\in_array(Resource::TYPE_DOCUMENT, $resources)) { @@ -217,7 +218,7 @@ public function report(array $resources = []): array $this->functions->list(); $scope = 'functions.write'; - $this->functions->create('', '', ''); + $this->functions->create('', '', Runtime::NODE180()); } } catch (AppwriteException $e) { @@ -782,7 +783,7 @@ public function importPasswordUser(User $user): ?array $user->getId(), $user->getEmail(), $hash->getHash(), - 'sha256', + PasswordHash::SHA256(), empty($user->getUsername()) ? null : $user->getUsername() ); break; @@ -830,7 +831,7 @@ public function importFunctionResource(Resource $resource): Resource case Resource::TYPE_FUNCTION: /** @var Func $resource */ - $runtype = match ($resource->getRuntime()) { + $runtime = match ($resource->getRuntime()) { 'node-14.5' => Runtime::NODE145(), 'node-16.0' => Runtime::NODE160(), 'node-18.0' => Runtime::NODE180(), @@ -885,7 +886,7 @@ public function importFunctionResource(Resource $resource): Resource $this->functions->create( $resource->getId(), $resource->getFunctionName(), - $runtype, + $runtime, $resource->getExecute(), $resource->getEvents(), $resource->getSchedule(), diff --git a/src/Migration/Resources/Auth/User.php b/src/Migration/Resources/Auth/User.php index e70f856..cdc1c41 100644 --- a/src/Migration/Resources/Auth/User.php +++ b/src/Migration/Resources/Auth/User.php @@ -89,7 +89,7 @@ public static function getName(): string /** * Get Email */ - public function getEmail(): ?string + public function getEmail(): string { return $this->email; } From 93a9a402461a36cb68568714aeb952ed1f1ad3fd Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Tue, 16 Jul 2024 11:28:37 +0900 Subject: [PATCH 101/185] Fix Test Imports --- tests/Migration/E2E/Sources/Base.php | 2 +- tests/Migration/E2E/Sources/NHostTest.php | 2 +- tests/Migration/E2E/Sources/SupabaseTest.php | 2 +- tests/Migration/Unit/General/TransferTest.php | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Migration/E2E/Sources/Base.php b/tests/Migration/E2E/Sources/Base.php index 009c147..7b5bd40 100644 --- a/tests/Migration/E2E/Sources/Base.php +++ b/tests/Migration/E2E/Sources/Base.php @@ -2,12 +2,12 @@ namespace Utopia\Tests\E2E\Sources; -use Migration\Unit\Adapters\MockDestination; use PHPUnit\Framework\TestCase; use Utopia\Migration\Destination; use Utopia\Migration\Resource; use Utopia\Migration\Source; use Utopia\Migration\Transfer; +use Utopia\Tests\Unit\Adapters\MockDestination; abstract class Base extends TestCase { diff --git a/tests/Migration/E2E/Sources/NHostTest.php b/tests/Migration/E2E/Sources/NHostTest.php index b6bfe4b..26d1663 100644 --- a/tests/Migration/E2E/Sources/NHostTest.php +++ b/tests/Migration/E2E/Sources/NHostTest.php @@ -2,12 +2,12 @@ namespace Utopia\Tests\E2E\Sources; -use Migration\Unit\Adapters\MockDestination; use Utopia\Migration\Destination; use Utopia\Migration\Resource; use Utopia\Migration\Source; use Utopia\Migration\Sources\NHost; use Utopia\Migration\Transfer; +use Utopia\Tests\Unit\Adapters\MockDestination; class NHostTest extends Base { diff --git a/tests/Migration/E2E/Sources/SupabaseTest.php b/tests/Migration/E2E/Sources/SupabaseTest.php index 556dc02..e1f0c7f 100644 --- a/tests/Migration/E2E/Sources/SupabaseTest.php +++ b/tests/Migration/E2E/Sources/SupabaseTest.php @@ -2,12 +2,12 @@ namespace Utopia\Tests\E2E\Sources; -use Migration\Unit\Adapters\MockDestination; use Utopia\Migration\Destination; use Utopia\Migration\Resource; use Utopia\Migration\Source; use Utopia\Migration\Sources\Supabase; use Utopia\Migration\Transfer; +use Utopia\Tests\Unit\Adapters\MockDestination; class SupabaseTest extends Base { diff --git a/tests/Migration/Unit/General/TransferTest.php b/tests/Migration/Unit/General/TransferTest.php index 3931460..44db38c 100644 --- a/tests/Migration/Unit/General/TransferTest.php +++ b/tests/Migration/Unit/General/TransferTest.php @@ -2,12 +2,12 @@ namespace Utopia\Tests\Unit\General; -use Migration\Unit\Adapters\MockDestination; -use Migration\Unit\Adapters\MockSource; use PHPUnit\Framework\TestCase; use Utopia\Migration\Resource; use Utopia\Migration\Resources\Database\Database; use Utopia\Migration\Transfer; +use Utopia\Tests\Unit\Adapters\MockDestination; +use Utopia\Tests\Unit\Adapters\MockSource; class TransferTest extends TestCase { From f38c17ab9311a97948dcd66bc1c989122fc34cec Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Tue, 16 Jul 2024 12:21:06 +0900 Subject: [PATCH 102/185] Fix Tests, update to new error reporting method --- phpunit.xml | 2 +- playground.php | 102 ------------------- src/Migration/Sources/Firebase.php | 14 +-- src/Migration/Sources/NHost.php | 12 ++- src/Migration/Sources/Supabase.php | 12 ++- src/Migration/Transfer.php | 5 +- tests/Migration/E2E/Sources/NHostTest.php | 50 +++++++-- tests/Migration/E2E/Sources/SupabaseTest.php | 49 +++++++-- 8 files changed, 102 insertions(+), 144 deletions(-) delete mode 100644 playground.php diff --git a/phpunit.xml b/phpunit.xml index f312d5f..fad0dec 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -11,7 +11,7 @@ ./tests/Migration/E2E - ./tests/Migration/unit + ./tests/Migration/Unit diff --git a/playground.php b/playground.php deleted file mode 100644 index 571a768..0000000 --- a/playground.php +++ /dev/null @@ -1,102 +0,0 @@ -load(); - -/** - * Initialise All Source Adapters - */ -$sourceAppwrite = new Appwrite( - $_ENV['SOURCE_APPWRITE_TEST_PROJECT'], - $_ENV['SOURCE_APPWRITE_TEST_ENDPOINT'], - $_ENV['SOURCE_APPWRITE_TEST_KEY'] -); - -// $firebase = json_decode($_ENV['FIREBASE_TEST_ACCOUNT'], true); - -// $sourceFirebase = new Firebase( -// $firebase, -// $firebase['project_id'] ?? '', -// ); - -// $sourceNHost = new NHost( -// $_ENV['NHOST_TEST_SUBDOMAIN'] ?? '', -// $_ENV['NHOST_TEST_REGION'] ?? '', -// $_ENV['NHOST_TEST_SECRET'] ?? '', -// $_ENV['NHOST_TEST_DATABASE'] ?? '', -// $_ENV['NHOST_TEST_USERNAME'] ?? '', -// $_ENV['NHOST_TEST_PASSWORD'] ?? '', -// ); - -// $sourceSupabase = new Supabase( -// $_ENV['SUPABASE_TEST_ENDPOINT'] ?? '', -// $_ENV['SUPABASE_TEST_KEY'] ?? '', -// $_ENV['SUPABASE_TEST_HOST'] ?? '', -// $_ENV['SUPABASE_TEST_DATABASE'] ?? '', -// $_ENV['SUPABASE_TEST_USERNAME'] ?? '', -// $_ENV['SUPABASE_TEST_PASSWORD'] ?? '', -// ); - -/** - * Initialise All Destination Adapters - */ -$destinationAppwrite = new AppwriteDestination( - $_ENV['DESTINATION_APPWRITE_TEST_PROJECT'], - $_ENV['DESTINATION_APPWRITE_TEST_ENDPOINT'], - $_ENV['DESTINATION_APPWRITE_TEST_KEY'] -); - -$destinationLocal = new Local(__DIR__.'/localBackup/'); - -/** - * Initialise Transfer Class - */ -$transfer = new Transfer( - $sourceAppwrite, - $destinationAppwrite -); - -/** - * Run Transfer - */ -$transfer->run( - $sourceAppwrite->getSupportedResources(), - function (array $resources) use ($transfer) { - var_dump($transfer->getStatusCounters()); - } -); - -$report = []; - -$cache = $transfer->getCache()->getAll(); - -if (count($sourceAppwrite->getErrors()) > 0) { - foreach ($sourceAppwrite->getErrors() as $error) { - /* @var \Utopia\Migration\Exception $error */ - Console::error('[Source] ['.$error->getResourceType().'] '.$error->getMessage()); - } -} - -if (count($destinationAppwrite->getErrors()) > 0) { - foreach ($destinationAppwrite->getErrors() as $error) { - /* @var \Utopia\Migration\Exception $error */ - Console::error('[Destination] ['.$error->getResourceType().'] '.$error->getMessage()); - } -} diff --git a/src/Migration/Sources/Firebase.php b/src/Migration/Sources/Firebase.php index 03950a4..084d785 100644 --- a/src/Migration/Sources/Firebase.php +++ b/src/Migration/Sources/Firebase.php @@ -226,11 +226,17 @@ private function exportUsers(int $batchSize): void $nextPageToken = $response['nextPageToken'] ?? null; foreach ($result as $user) { + $hash = null; + + if (array_key_exists('passwordHash', $user)) { + $hash = new Hash($user['passwordHash'], $user['salt'] ?? '', Hash::ALGORITHM_SCRYPT_MODIFIED, $hashConfig['saltSeparator'] ?? '', $hashConfig['signerKey'] ?? ''); + } + $transferUser = new User( $user['localId'] ?? '', $user['email'] ?? null, $user['displayName'] ?? $user['email'] ?? null, - null, + $hash, $user['phoneNumber'] ?? null, [], '', @@ -239,12 +245,6 @@ private function exportUsers(int $batchSize): void $user['disabled'] ?? false ); - if (array_key_exists('passwordHash', $user)) { - $transferUser->setPasswordHash( - new Hash($user['passwordHash'], $user['salt'] ?? '', Hash::ALGORITHM_SCRYPT_MODIFIED, $hashConfig['saltSeparator'] ?? '', $hashConfig['signerKey'] ?? '') - ); - } - $users[] = $transferUser; } diff --git a/src/Migration/Sources/NHost.php b/src/Migration/Sources/NHost.php index e0dc95d..a7d8dec 100644 --- a/src/Migration/Sources/NHost.php +++ b/src/Migration/Sources/NHost.php @@ -248,11 +248,17 @@ private function exportUsers(int $batchSize): void $transferUsers = []; foreach ($users as $user) { + $hash = null; + + if (array_key_exists('password_hash', $user)) { + $hash = new Hash($user['password_hash'], '', Hash::ALGORITHM_BCRYPT); + } + $transferUser = new User( $user['id'], $user['email'] ?? null, $user['display_name'] ?? null, - null, + $hash, $user['phone_number'] ?? null, [], '', @@ -262,10 +268,6 @@ private function exportUsers(int $batchSize): void [] ); - if (array_key_exists('password_hash', $user)) { - $transferUser->setPasswordHash(new Hash($user['password_hash'], '', Hash::ALGORITHM_BCRYPT)); - } - $transferUsers[] = $transferUser; } diff --git a/src/Migration/Sources/Supabase.php b/src/Migration/Sources/Supabase.php index ebc7833..222b308 100644 --- a/src/Migration/Sources/Supabase.php +++ b/src/Migration/Sources/Supabase.php @@ -382,11 +382,17 @@ private function exportUsers(int $batchSize): void $transferUsers = []; foreach ($users as $user) { + $hash = null; + + if (array_key_exists('encrypted_password', $user)) { + $hash = new Hash($user['encrypted_password'], '', Hash::ALGORITHM_BCRYPT); + } + $transferUser = new User( $user['id'], $user['email'] ?? null, '', - null, + $hash, $user['phone'] ?? null, [], '', @@ -396,10 +402,6 @@ private function exportUsers(int $batchSize): void [] ); - if (array_key_exists('encrypted_password', $user)) { - $transferUser->setPasswordHash(new Hash($user['encrypted_password'], '', Hash::ALGORITHM_BCRYPT)); - } - $transferUsers[] = $transferUser; } diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index eae930d..994dd14 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -139,8 +139,9 @@ public function getStatusCounters(): array // Process source errors foreach ($this->source->getErrors() as $error) { - if (isset($status[$error->getResourceType()])) { - $status[$error->getResourceType()][Resource::STATUS_ERROR]++; + /** @var Exception $error */ + if (isset($status[$error->getResourceName()])) { + $status[$error->getResourceName()][Resource::STATUS_ERROR]++; } } diff --git a/tests/Migration/E2E/Sources/NHostTest.php b/tests/Migration/E2E/Sources/NHostTest.php index 26d1663..02cff7a 100644 --- a/tests/Migration/E2E/Sources/NHostTest.php +++ b/tests/Migration/E2E/Sources/NHostTest.php @@ -104,32 +104,60 @@ function () {} return array_merge($state, [ 'transfer' => $this->transfer, 'source' => $this->source, + 'destination' => $this->destination, ]); } /** * @depends testRunTransfer */ - public function testValidateTransfer($state) + public function testValidateSourceErrors($state) { - $statusCounters = $state['transfer']->getStatusCounters(); + /** @var Transfer $transfer */ + $transfer = $state['transfer']; + + /** @var Source $source */ + $source = $state['source']; + + $statusCounters = $transfer->getStatusCounters(); $this->assertNotEmpty($statusCounters); - foreach ($statusCounters as $resource => $counters) { - $this->assertNotEmpty($counters); + $errors = $source->getErrors(); - if ($counters[Resource::STATUS_ERROR] > 0) { - $this->fail('Resource '.$resource.' has '.$counters[Resource::STATUS_ERROR].' errors'); + foreach ($errors as $error) { + /** @var Exception $error */ + $this->fail('[Source] Resource: "' . $error->getResourceName() . '" Failed with: ' . $error->getMessage()); + } - return; - } + return $state; + } + + /** + * @depends testValidateSourceErrors + */ + public function testValidateDestinationErrors($state) + { + /** @var Transfer $transfer */ + $transfer = $state['transfer']; + + /** @var Destination $destination */ + $destination = $state['destination']; + + $statusCounters = $transfer->getStatusCounters(); + $this->assertNotEmpty($statusCounters); + + $errors = $destination->getErrors(); + + foreach ($errors as $error) { + /** @var Exception $error */ + $this->fail('[Destination] Resource: "' . $error->getResourceName() . '" Failed with: ' . $error->getMessage()); } return $state; } /** - * @depends testValidateTransfer + * @depends testValidateDestinationErrors */ public function testValidateUserTransfer($state): void { @@ -159,7 +187,7 @@ public function testValidateUserTransfer($state): void } /** - * @depends testValidateTransfer + * @depends testValidateDestinationErrors */ public function testValidateDatabaseTransfer($state): void { @@ -212,7 +240,7 @@ public function testValidateDatabaseTransfer($state): void } /** - * @depends testValidateTransfer + * @depends testValidateDestinationErrors */ public function testValidateStorageTransfer($state): void { diff --git a/tests/Migration/E2E/Sources/SupabaseTest.php b/tests/Migration/E2E/Sources/SupabaseTest.php index e1f0c7f..020008a 100644 --- a/tests/Migration/E2E/Sources/SupabaseTest.php +++ b/tests/Migration/E2E/Sources/SupabaseTest.php @@ -90,26 +90,53 @@ function () {} /** * @depends testRunTransfer */ - public function testValidateTransfer($state) + public function testValidateSourceErrors($state) { - $statusCounters = $state['transfer']->getStatusCounters(); + /** @var Transfer $transfer */ + $transfer = $state['transfer']; + + /** @var Source $source */ + $source = $state['source']; + + $statusCounters = $transfer->getStatusCounters(); $this->assertNotEmpty($statusCounters); - foreach ($statusCounters as $resource => $counters) { - $this->assertNotEmpty($counters); + $errors = $source->getErrors(); - if ($counters[Resource::STATUS_ERROR] > 0) { - $this->fail('Resource '.$resource.' has '.$counters[Resource::STATUS_ERROR].' errors'); + foreach ($errors as $error) { + /** @var Exception $error */ + $this->fail('[Source] Resource: "' . $error->getResourceName() . '" Failed with: ' . $error->getMessage()); + } - return; - } + return $state; + } + + /** + * @depends testValidateSourceErrors + */ + public function testValidateDestinationErrors($state) + { + /** @var Transfer $transfer */ + $transfer = $state['transfer']; + + /** @var Destination $destination */ + $destination = $state['destination']; + + $statusCounters = $transfer->getStatusCounters(); + $this->assertNotEmpty($statusCounters); + + $errors = $destination->getErrors(); + + foreach ($errors as $error) { + /** @var Exception $error */ + $this->fail('[Destination] Resource: "' . $error->getResourceName() . '" Failed with: ' . $error->getMessage()); } return $state; } /** - * @depends testValidateTransfer + * @depends testValidateDestinationErrors */ public function testValidateUserTransfer($state): void { @@ -139,7 +166,7 @@ public function testValidateUserTransfer($state): void } /** - * @depends testValidateTransfer + * @depends testValidateDestinationErrors */ public function testValidateDatabaseTransfer($state): void { @@ -218,7 +245,7 @@ public function testValidateDatabaseTransfer($state): void } /** - * @depends testValidateTransfer + * @depends testValidateDestinationErrors */ public function testValidateStorageTransfer($state): void { From ba73eeb90bbd724b4b40caf2fdf000d9c50ec459 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Tue, 16 Jul 2024 12:23:30 +0900 Subject: [PATCH 103/185] Update SupabaseTest.php --- tests/Migration/E2E/Sources/SupabaseTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Migration/E2E/Sources/SupabaseTest.php b/tests/Migration/E2E/Sources/SupabaseTest.php index 020008a..8ac2de5 100644 --- a/tests/Migration/E2E/Sources/SupabaseTest.php +++ b/tests/Migration/E2E/Sources/SupabaseTest.php @@ -84,6 +84,7 @@ function () {} return array_merge($state, [ 'transfer' => $this->transfer, 'source' => $this->source, + 'destination' => $this->destination, ]); } From 1f51c2921475ac7a545bc55aeafc650e729fe5bc Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 16 Jul 2024 09:51:10 +0300 Subject: [PATCH 104/185] rootResourceId Query --- src/Migration/Sources/Appwrite.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index f47adcf..945b119 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -822,14 +822,19 @@ private function exportDatabases(int $batchSize): void $lastDatabase = null; // Root Level Resource - if (!empty($this->rootResourceId)) { - $this->callback([$this->database->get($this->rootResourceId)]); - return; - } +// if (!empty($this->rootResourceId)) { +// $this->callback([$this->database->get($this->rootResourceId)]); +// return; +// } // Transfer Databases while (true) { $queries = [Query::limit($batchSize)]; + + if (!empty($this->rootResourceId)) { + $queries[] = Query::equal('$id', $this->rootResourceId); + } + $databases = []; if ($lastDatabase) { From 140962f082b3ccb999648b02f7259f07ca844fe9 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 16 Jul 2024 10:07:21 +0300 Subject: [PATCH 105/185] rootResourceId try again --- src/Migration/Sources/Appwrite.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 945b119..f22a131 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -822,18 +822,18 @@ private function exportDatabases(int $batchSize): void $lastDatabase = null; // Root Level Resource -// if (!empty($this->rootResourceId)) { -// $this->callback([$this->database->get($this->rootResourceId)]); -// return; -// } + if (!empty($this->rootResourceId)) { + $this->callback([$this->database->get($this->rootResourceId)]); + return; + } // Transfer Databases while (true) { $queries = [Query::limit($batchSize)]; - if (!empty($this->rootResourceId)) { - $queries[] = Query::equal('$id', $this->rootResourceId); - } +// if (!empty($this->rootResourceId)) { +// $queries[] = Query::equal('$id', $this->rootResourceId); +// } $databases = []; From 4edd51d3232eba0cdf6eaada431c98b83be19f84 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 16 Jul 2024 12:33:49 +0300 Subject: [PATCH 106/185] Add query rootResourceId --- src/Migration/Sources/Appwrite.php | 62 ++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index f22a131..d3315d1 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -363,10 +363,10 @@ private function exportUsers(int $batchSize): void $lastDocument = null; // Root Level Resource - if (!empty($this->rootResourceId)) { - $this->callback([$this->users->get($this->rootResourceId)]); - return; - } +// if (!empty($this->rootResourceId)) { +// $this->callback([$this->users->get($this->rootResourceId)]); +// return; +// } // Export Users while (true) { @@ -374,6 +374,10 @@ private function exportUsers(int $batchSize): void $queries = [Query::limit($batchSize)]; + if (!empty($this->rootResourceId)) { + $queries[] = Query::equal('$id', $this->rootResourceId); + } + if ($lastDocument) { $queries[] = Query::cursorAfter($lastDocument); } @@ -419,10 +423,10 @@ private function exportTeams(int $batchSize): void $lastDocument = null; // Root Level Resource - if (!empty($this->rootResourceId)) { - $this->callback([$this->teams->get($this->rootResourceId)]); - return; - } +// if (!empty($this->rootResourceId)) { +// $this->callback([$this->teams->get($this->rootResourceId)]); +// return; +// } // Export Teams while (true) { @@ -430,6 +434,10 @@ private function exportTeams(int $batchSize): void $queries = [Query::limit($batchSize)]; + if (!empty($this->rootResourceId)) { + $queries[] = Query::equal('$id', $this->rootResourceId); + } + if ($lastDocument) { $queries[] = Query::cursorAfter($lastDocument); } @@ -822,18 +830,18 @@ private function exportDatabases(int $batchSize): void $lastDatabase = null; // Root Level Resource - if (!empty($this->rootResourceId)) { - $this->callback([$this->database->get($this->rootResourceId)]); - return; - } +// if (!empty($this->rootResourceId)) { +// $this->callback([$this->database->get($this->rootResourceId)]); +// return; +// } // Transfer Databases while (true) { $queries = [Query::limit($batchSize)]; -// if (!empty($this->rootResourceId)) { -// $queries[] = Query::equal('$id', $this->rootResourceId); -// } + if (!empty($this->rootResourceId)) { + $queries[] = Query::equal('$id', $this->rootResourceId); + } $databases = []; @@ -1083,12 +1091,18 @@ protected function exportGroupStorage(int $batchSize, array $resources): void private function exportBuckets(int $batchSize): void { // Root Level Resource +// if (!empty($this->rootResourceId)) { +// $this->callback([$this->storage->getBucket($this->rootResourceId)]); +// return; +// } + + $queries = []; + if (!empty($this->rootResourceId)) { - $this->callback([$this->storage->getBucket($this->rootResourceId)]); - return; + $queries[] = Query::equal('$id', $this->rootResourceId); } - $buckets = $this->storage->listBuckets(); + $buckets = $this->storage->listBuckets($queries); $convertedBuckets = []; @@ -1240,12 +1254,18 @@ private function exportFunctions(int $batchSize): void $this->functions = new Functions($this->client); // Root Level Resource +// if (!empty($this->rootResourceId)) { +// $this->callback([$this->functions->get($this->rootResourceId)]); +// return; +// } + + $queries = []; + if (!empty($this->rootResourceId)) { - $this->callback([$this->functions->get($this->rootResourceId)]); - return; + $queries[] = Query::equal('$id', $this->rootResourceId); } - $functions = $this->functions->list(); + $functions = $this->functions->list($queries); if ($functions['total'] === 0) { return; From 614922bf3efecd20660414588b812eafb64836b8 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 18 Jul 2024 15:22:05 +0300 Subject: [PATCH 107/185] Many2Many --- src/Migration/Sources/Appwrite.php | 76 +++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 21 deletions(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index d3315d1..27df813 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -613,27 +613,30 @@ private function exportDocuments(int $batchSize): void $queries[] = Query::cursorAfter($lastDocument); } - // $selects = []; - // $attributes = $this->cache->get(Attribute::getName()); - // foreach ($attributes as $attribute) { - // /** @var Attribute $attribute */ - // if ($attribute->getCollection()->getId() === $collection->getId()) { - // - // var_dump(' === exportDocuments === '); - // var_dump($attribute->getKey()); - // var_dump($attribute); - // $selects[] = $attribute->getKey(); - // if($attribute->getTypeName() === Attribute::TYPE_RELATIONSHIP){ - // var_dump(' === this is TYPE_RELATIONSHIP === '); - // } - // } - // } - // - // if(!empty($selects)){ - // $queries[] = Query::select($selects); - // } - - $queries[] = Query::select(['*', '$id', '$permissions', '$updatedAt', '$createdAt']); // We want Relations flat! + $selects = ['*', '$id', '$permissions', '$updatedAt', '$createdAt']; // We want Relations flat! + $manyToMany = []; + + $attributes = $this->cache->get(Attribute::getName()); + foreach ($attributes as $attribute) { + /** @var Attribute|Relationship $attribute */ + if ( + $attribute->getCollection()->getId() === $collection->getId() && + $attribute->getTypeName() === Attribute::TYPE_RELATIONSHIP && + $attribute->getSide() === 'parent' && + $attribute->getRelationType() == 'manyToMany' + ) { + /** + * Blockers: + * we should use but Does not work properly: + * $selects[] = $attribute->getKey() . '.$id'; + * when selecting for a relation we get all relations not just the one we were asking. + * when selecting for a relation like select(*, relation.$id) , all relations get resolve + */ + $manyToMany[] = $attribute->getKey(); + } + } + + $queries[] = Query::select($selects); $response = $this->database->listDocuments( $collection->getDatabase()->getId(), @@ -642,6 +645,33 @@ private function exportDocuments(int $batchSize): void ); foreach ($response['documents'] as $document) { + /** + * Hack for many 2 many + */ + if(!empty($manyToMany)){ + $stack = ['$id']; // Adding $id becuase can't select only relations + foreach ($manyToMany as $relation){ + $stack[] = $relation . '.$id'; + } + + $doc = $this->database->getDocument( + $collection->getDatabase()->getId(), + $collection->getId(), + $document['$id'], + [Query::select($stack)] + ); + + foreach ($manyToMany as $relation){ + $document[$relation] = []; + foreach ($doc[$relation] as $relationDocument){ + $document[$relation][] = $relationDocument['$id']; + } + } + } + /** + * end Hack for many 2 many + */ + $id = $document['$id']; $permissions = $document['$permissions']; @@ -966,6 +996,10 @@ private function exportAttributes(int $batchSize): void } if ($attribute['type'] === 'relationship') { + if ($attribute['side'] === 'child'){ + continue; + } + $knownTwoWays[] = $attribute['twoWayKey']; } From 2d56af3a58c5da2ce812d30c3ba9716dfeb14e8c Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 18 Jul 2024 15:28:38 +0300 Subject: [PATCH 108/185] Change name --- src/Migration/Sources/Appwrite.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 27df813..7f7d00a 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -661,10 +661,10 @@ private function exportDocuments(int $batchSize): void [Query::select($stack)] ); - foreach ($manyToMany as $relation){ - $document[$relation] = []; - foreach ($doc[$relation] as $relationDocument){ - $document[$relation][] = $relationDocument['$id']; + foreach ($manyToMany as $key){ + $document[$key] = []; + foreach ($doc[$key] as $relationDocument){ + $document[$key][] = $relationDocument['$id']; } } } From 514395faca7c3294750332f286317ea550d1b88c Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 18 Jul 2024 15:39:46 +0300 Subject: [PATCH 109/185] Remove dbg --- src/Migration/Source.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Migration/Source.php b/src/Migration/Source.php index 0c8bbf6..6a8f529 100644 --- a/src/Migration/Source.php +++ b/src/Migration/Source.php @@ -35,8 +35,6 @@ public function run(array $resources, callable $callback, string $rootResourceId $prunedResources = []; foreach ($returnedResources as $resource) { /** @var Resource $resource */ - var_dump('Source::run method = ' . $resource->getName()); - if (! in_array($resource->getName(), $resources)) { var_dump('setStatus === ' . Resource::STATUS_SKIPPED); $resource->setStatus(Resource::STATUS_SKIPPED); From c1e6427b900b2dddd40ec98878286b64ec766fb7 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 21 Jul 2024 13:06:54 +0300 Subject: [PATCH 110/185] Use relation side VS twoWay --- src/Migration/Sources/Appwrite.php | 70 +++++++++++------------------- 1 file changed, 26 insertions(+), 44 deletions(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 7f7d00a..bb323bc 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -363,10 +363,10 @@ private function exportUsers(int $batchSize): void $lastDocument = null; // Root Level Resource -// if (!empty($this->rootResourceId)) { -// $this->callback([$this->users->get($this->rootResourceId)]); -// return; -// } + // if (!empty($this->rootResourceId)) { + // $this->callback([$this->users->get($this->rootResourceId)]); + // return; + // } // Export Users while (true) { @@ -423,10 +423,10 @@ private function exportTeams(int $batchSize): void $lastDocument = null; // Root Level Resource -// if (!empty($this->rootResourceId)) { -// $this->callback([$this->teams->get($this->rootResourceId)]); -// return; -// } + // if (!empty($this->rootResourceId)) { + // $this->callback([$this->teams->get($this->rootResourceId)]); + // return; + // } // Export Teams while (true) { @@ -648,9 +648,9 @@ private function exportDocuments(int $batchSize): void /** * Hack for many 2 many */ - if(!empty($manyToMany)){ + if(!empty($manyToMany)) { $stack = ['$id']; // Adding $id becuase can't select only relations - foreach ($manyToMany as $relation){ + foreach ($manyToMany as $relation) { $stack[] = $relation . '.$id'; } @@ -661,9 +661,9 @@ private function exportDocuments(int $batchSize): void [Query::select($stack)] ); - foreach ($manyToMany as $key){ + foreach ($manyToMany as $key) { $document[$key] = []; - foreach ($doc[$key] as $relationDocument){ + foreach ($doc[$key] as $relationDocument) { $document[$key][] = $relationDocument['$id']; } } @@ -860,10 +860,10 @@ private function exportDatabases(int $batchSize): void $lastDatabase = null; // Root Level Resource -// if (!empty($this->rootResourceId)) { -// $this->callback([$this->database->get($this->rootResourceId)]); -// return; -// } + // if (!empty($this->rootResourceId)) { + // $this->callback([$this->database->get($this->rootResourceId)]); + // return; + // } // Transfer Databases while (true) { @@ -978,29 +978,11 @@ private function exportAttributes(int $batchSize): void $queries ); - // Remove two way relationship attributes - - $knownTwoWays = []; - - foreach ($this->cache->get(Resource::TYPE_ATTRIBUTE) as $attribute) { - /** @var Attribute|Relationship $attribute */ - if ($attribute->getTypeName() == Attribute::TYPE_RELATIONSHIP && $attribute->getTwoWay()) { - $knownTwoWays[] = $attribute->getTwoWayKey(); - } - } - foreach ($response['attributes'] as $attribute) { /** @var array $attribute */ - if (\in_array($attribute['key'], $knownTwoWays)) { - continue; - } - - if ($attribute['type'] === 'relationship') { - if ($attribute['side'] === 'child'){ - continue; - } - $knownTwoWays[] = $attribute['twoWayKey']; + if ($attribute['type'] === 'relationship' && $attribute['side'] === 'child') { + continue; } $attributes[] = $this->convertAttribute($attribute, $collection); @@ -1125,10 +1107,10 @@ protected function exportGroupStorage(int $batchSize, array $resources): void private function exportBuckets(int $batchSize): void { // Root Level Resource -// if (!empty($this->rootResourceId)) { -// $this->callback([$this->storage->getBucket($this->rootResourceId)]); -// return; -// } + // if (!empty($this->rootResourceId)) { + // $this->callback([$this->storage->getBucket($this->rootResourceId)]); + // return; + // } $queries = []; @@ -1288,10 +1270,10 @@ private function exportFunctions(int $batchSize): void $this->functions = new Functions($this->client); // Root Level Resource -// if (!empty($this->rootResourceId)) { -// $this->callback([$this->functions->get($this->rootResourceId)]); -// return; -// } + // if (!empty($this->rootResourceId)) { + // $this->callback([$this->functions->get($this->rootResourceId)]); + // return; + // } $queries = []; From d63024ce593124a5ec47f9606386bc01ba27424c Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 21 Jul 2024 15:03:39 +0300 Subject: [PATCH 111/185] Fix no length --- src/Migration/Sources/Appwrite.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index bb323bc..7ad4c5f 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -1035,6 +1035,7 @@ private function exportIndexes(int $batchSize): void $collection, $index['type'], $index['attributes'], + [], $index['orders'] ); } From 7b2e974e0ceb4e3b0e68ed7fbe057db793b12b7e Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 22 Jul 2024 15:55:37 +0300 Subject: [PATCH 112/185] Add x-appwrite-preserve-dates header --- src/Migration/Destinations/Appwrite.php | 3 +- src/Migration/Sources/Appwrite.php | 40 ++++--------------------- 2 files changed, 8 insertions(+), 35 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 549f690..7b5b0e4 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -68,7 +68,8 @@ public function __construct(string $project, string $endpoint, string $key) $this->client = (new Client()) ->setEndpoint($endpoint) ->setProject($project) - ->setKey($key); + ->setKey($key) + ->addHeader('x-appwrite-preserve-dates', 'true'); $this->databases = new Databases($this->client); $this->functions = new Functions($this->client); diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 7ad4c5f..df8d405 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -362,13 +362,6 @@ private function exportUsers(int $batchSize): void { $lastDocument = null; - // Root Level Resource - // if (!empty($this->rootResourceId)) { - // $this->callback([$this->users->get($this->rootResourceId)]); - // return; - // } - - // Export Users while (true) { $users = []; @@ -376,6 +369,7 @@ private function exportUsers(int $batchSize): void if (!empty($this->rootResourceId)) { $queries[] = Query::equal('$id', $this->rootResourceId); + $queries[] = Query::limit(1); } if ($lastDocument) { @@ -422,13 +416,6 @@ private function exportTeams(int $batchSize): void $this->teams = new Teams($this->client); $lastDocument = null; - // Root Level Resource - // if (!empty($this->rootResourceId)) { - // $this->callback([$this->teams->get($this->rootResourceId)]); - // return; - // } - - // Export Teams while (true) { $teams = []; @@ -436,6 +423,7 @@ private function exportTeams(int $batchSize): void if (!empty($this->rootResourceId)) { $queries[] = Query::equal('$id', $this->rootResourceId); + $queries[] = Query::limit(1); } if ($lastDocument) { @@ -859,18 +847,12 @@ private function exportDatabases(int $batchSize): void $lastDatabase = null; - // Root Level Resource - // if (!empty($this->rootResourceId)) { - // $this->callback([$this->database->get($this->rootResourceId)]); - // return; - // } - - // Transfer Databases while (true) { $queries = [Query::limit($batchSize)]; if (!empty($this->rootResourceId)) { $queries[] = Query::equal('$id', $this->rootResourceId); + $queries[] = Query::limit(1); } $databases = []; @@ -1107,16 +1089,11 @@ protected function exportGroupStorage(int $batchSize, array $resources): void */ private function exportBuckets(int $batchSize): void { - // Root Level Resource - // if (!empty($this->rootResourceId)) { - // $this->callback([$this->storage->getBucket($this->rootResourceId)]); - // return; - // } - - $queries = []; + $queries = []; if (!empty($this->rootResourceId)) { $queries[] = Query::equal('$id', $this->rootResourceId); + $queries[] = Query::limit(1); } $buckets = $this->storage->listBuckets($queries); @@ -1270,16 +1247,11 @@ private function exportFunctions(int $batchSize): void { $this->functions = new Functions($this->client); - // Root Level Resource - // if (!empty($this->rootResourceId)) { - // $this->callback([$this->functions->get($this->rootResourceId)]); - // return; - // } - $queries = []; if (!empty($this->rootResourceId)) { $queries[] = Query::equal('$id', $this->rootResourceId); + $queries[] = Query::limit(1); } $functions = $this->functions->list($queries); From a8e143ff5eef7ac418b863904c1b8feb766f52d6 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 24 Jul 2024 23:45:52 +1200 Subject: [PATCH 113/185] Update Appwrite destination database resources to use database library instead of API --- .github/workflows/tests.yml | 4 +- composer.json | 14 +- composer.lock | 644 +++++++------ docker-compose.yml | 2 - pint.json | 4 +- src/Migration/Destinations/Appwrite.php | 903 ++++++++++++------ src/Migration/Exception.php | 6 +- src/Migration/Resources/Auth/User.php | 4 +- .../Resources/Database/Attribute.php | 93 +- .../Resources/Database/Attributes/Boolean.php | 50 +- .../Database/Attributes/DateTime.php | 71 +- .../Resources/Database/Attributes/Decimal.php | 72 +- .../Resources/Database/Attributes/Email.php | 2 +- .../Resources/Database/Attributes/Enum.php | 65 +- .../Resources/Database/Attributes/IP.php | 2 +- .../Resources/Database/Attributes/Integer.php | 72 +- .../Database/Attributes/Relationship.php | 111 +-- .../Resources/Database/Attributes/Text.php | 57 +- .../Resources/Database/Attributes/URL.php | 2 +- .../Resources/Database/Collection.php | 24 +- src/Migration/Resources/Database/Database.php | 7 +- src/Migration/Resources/Database/Document.php | 20 +- src/Migration/Resources/Database/Index.php | 21 +- src/Migration/Sources/Appwrite.php | 222 +++-- src/Migration/Sources/Firebase.php | 119 ++- src/Migration/Sources/NHost.php | 127 ++- src/Migration/Sources/Supabase.php | 18 +- src/Migration/Target.php | 9 +- src/Migration/Transfer.php | 65 +- tests/Migration/E2E/Sources/NHostTest.php | 50 +- tests/Migration/E2E/Sources/SupabaseTest.php | 61 +- .../{unit => Unit}/General/TransferTest.php | 2 +- 32 files changed, 1753 insertions(+), 1170 deletions(-) rename tests/Migration/{unit => Unit}/General/TransferTest.php (96%) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b51d5e7..2bc1aa5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,4 +16,6 @@ jobs: - name: Run Tests run: | - docker-compose up -d --build && sleep 5 && docker compose exec tests php ./vendor/bin/phpunit \ No newline at end of file + docker-compose up -d --build + sleep 5 + docker compose exec tests php vendor/bin/phpunit \ No newline at end of file diff --git a/composer.json b/composer.json index fa3a6d9..c7618ef 100644 --- a/composer.json +++ b/composer.json @@ -17,25 +17,25 @@ "test": "./vendor/bin/phpunit", "lint": "./vendor/bin/pint --test", "format": "./vendor/bin/pint", - "check": "./vendor/bin/phpstan analyse --level=8 src/Migration" + "check": "./vendor/bin/phpstan analyse --level=8" }, "require": { "php": "8.3", "ext-curl": "*", "ext-openssl": "*", "appwrite/appwrite": "11.1.*", - "utopia-php/cli": "0.15.*", - "utopia-php/database": "0.49.*", + "utopia-php/database": "0.50.*", "utopia-php/storage": "0.18.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*" }, "require-dev": { - "phpunit/phpunit": "10.5.*", + "ext-pdo": "*", + "phpunit/phpunit": "11.2.*", "vlucas/phpdotenv": "5.6.*", - "laravel/pint": "1.12.*", - "phpstan/phpstan": "^1.11", - "utopia-php/cli": "^0.18.0" + "laravel/pint": "1.17.*", + "phpstan/phpstan": "1.11.*", + "utopia-php/cli": "0.16.*" }, "platform": { "php": "8.3" diff --git a/composer.lock b/composer.lock index d7a0f37..25ccade 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": "aa00134eaea94873c4f096e0ea2df767", + "content-hash": "35a8ee5847ea127c2281dc5a1ee285d5", "packages": [ { "name": "appwrite/appwrite", @@ -306,67 +306,18 @@ }, "time": "2024-06-25T20:36:35+00:00" }, - { - "name": "utopia-php/cli", - "version": "0.15.0", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/cli.git", - "reference": "ccb7c8125ffe0254fef8f25744bfa376eb7bd0ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cli/zipball/ccb7c8125ffe0254fef8f25744bfa376eb7bd0ea", - "reference": "ccb7c8125ffe0254fef8f25744bfa376eb7bd0ea", - "shasum": "" - }, - "require": { - "php": ">=7.4", - "utopia-php/framework": "0.*.*" - }, - "require-dev": { - "laravel/pint": "1.2.*", - "phpunit/phpunit": "^9.3", - "squizlabs/php_codesniffer": "^3.6", - "vimeo/psalm": "4.0.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\CLI\\": "src/CLI" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A simple CLI library to manage command line applications", - "keywords": [ - "cli", - "command line", - "framework", - "php", - "upf", - "utopia" - ], - "support": { - "issues": "https://github.com/utopia-php/cli/issues", - "source": "https://github.com/utopia-php/cli/tree/0.15.0" - }, - "time": "2023-03-01T05:55:14+00:00" - }, { "name": "utopia-php/database", - "version": "0.49.14", + "version": "0.50.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "415588c0b98edee9d72cdfe269ff79b14cd8f56d" + "reference": "ce3eaccb2f3bbd34b2b97419836fec633b26b8f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/415588c0b98edee9d72cdfe269ff79b14cd8f56d", - "reference": "415588c0b98edee9d72cdfe269ff79b14cd8f56d", + "url": "https://api.github.com/repos/utopia-php/database/zipball/ce3eaccb2f3bbd34b2b97419836fec633b26b8f7", + "reference": "ce3eaccb2f3bbd34b2b97419836fec633b26b8f7", "shasum": "" }, "require": { @@ -407,9 +358,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.49.14" + "source": "https://github.com/utopia-php/database/tree/0.50.0" }, - "time": "2024-06-20T02:39:23+00:00" + "time": "2024-06-21T03:21:42+00:00" }, { "name": "utopia-php/dsn", @@ -678,24 +629,24 @@ "packages-dev": [ { "name": "graham-campbell/result-type", - "version": "v1.1.2", + "version": "v1.1.3", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862" + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/fbd48bce38f73f8a4ec8583362e732e4095e5862", - "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.2" + "phpoption/phpoption": "^1.9.3" }, "require-dev": { - "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" }, "type": "library", "autoload": { @@ -724,7 +675,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.2" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" }, "funding": [ { @@ -736,20 +687,20 @@ "type": "tidelift" } ], - "time": "2023-11-12T22:16:48+00:00" + "time": "2024-07-20T21:45:45+00:00" }, { "name": "laravel/pint", - "version": "v1.12.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "08bcf51e520a5e5aea458fc600ac4869f6934a66" + "reference": "4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/08bcf51e520a5e5aea458fc600ac4869f6934a66", - "reference": "08bcf51e520a5e5aea458fc600ac4869f6934a66", + "url": "https://api.github.com/repos/laravel/pint/zipball/4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5", + "reference": "4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5", "shasum": "" }, "require": { @@ -760,13 +711,13 @@ "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.21.1", - "illuminate/view": "^10.5.1", - "laravel-zero/framework": "^10.1.2", - "mockery/mockery": "^1.5.1", - "nunomaduro/larastan": "^2.5.1", + "friendsofphp/php-cs-fixer": "^3.59.3", + "illuminate/view": "^10.48.12", + "larastan/larastan": "^2.9.7", + "laravel-zero/framework": "^10.4.0", + "mockery/mockery": "^1.6.12", "nunomaduro/termwind": "^1.15.1", - "pestphp/pest": "^2.4.0" + "pestphp/pest": "^2.34.8" }, "bin": [ "builds/pint" @@ -802,7 +753,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2023-08-30T07:53:32+00:00" + "time": "2024-07-23T16:40:20+00:00" }, { "name": "myclabs/deep-copy", @@ -1042,16 +993,16 @@ }, { "name": "phpoption/phpoption", - "version": "1.9.2", + "version": "1.9.3", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820" + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/80735db690fe4fc5c76dfa7f9b770634285fa820", - "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", "shasum": "" }, "require": { @@ -1059,13 +1010,13 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" }, "type": "library", "extra": { "bamarni-bin": { "bin-links": true, - "forward-command": true + "forward-command": false }, "branch-alias": { "dev-master": "1.9-dev" @@ -1101,7 +1052,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.2" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" }, "funding": [ { @@ -1113,20 +1064,20 @@ "type": "tidelift" } ], - "time": "2023-11-12T21:59:55+00:00" + "time": "2024-07-20T21:41:07+00:00" }, { "name": "phpstan/phpstan", - "version": "1.11.7", + "version": "1.11.8", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d" + "reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/52d2bbfdcae7f895915629e4694e9497d0f8e28d", - "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec", + "reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec", "shasum": "" }, "require": { @@ -1171,39 +1122,39 @@ "type": "github" } ], - "time": "2024-07-06T11:17:41+00:00" + "time": "2024-07-24T07:01:22+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "10.1.15", + "version": "11.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae" + "reference": "19b6365ab8b59a64438c0c3f4241feeb480c9861" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae", - "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/19b6365ab8b59a64438c0c3f4241feeb480c9861", + "reference": "19b6365ab8b59a64438c0c3f4241feeb480c9861", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1", - "phpunit/php-file-iterator": "^4.0", - "phpunit/php-text-template": "^3.0", - "sebastian/code-unit-reverse-lookup": "^3.0", - "sebastian/complexity": "^3.0", - "sebastian/environment": "^6.0", - "sebastian/lines-of-code": "^2.0", - "sebastian/version": "^4.0", + "nikic/php-parser": "^5.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.0", + "phpunit/php-text-template": "^4.0", + "sebastian/code-unit-reverse-lookup": "^4.0", + "sebastian/complexity": "^4.0", + "sebastian/environment": "^7.0", + "sebastian/lines-of-code": "^3.0", + "sebastian/version": "^5.0", "theseer/tokenizer": "^1.2.0" }, "require-dev": { - "phpunit/phpunit": "^10.1" + "phpunit/phpunit": "^11.0" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -1212,7 +1163,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.1-dev" + "dev-main": "11.0-dev" } }, "autoload": { @@ -1241,7 +1192,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.15" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.5" }, "funding": [ { @@ -1249,32 +1200,32 @@ "type": "github" } ], - "time": "2024-06-29T08:25:15+00:00" + "time": "2024-07-03T05:05:37+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "4.1.0", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" + "reference": "6ed896bf50bbbfe4d504a33ed5886278c78e4a26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6ed896bf50bbbfe4d504a33ed5886278c78e4a26", + "reference": "6ed896bf50bbbfe4d504a33ed5886278c78e4a26", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -1302,7 +1253,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.0.1" }, "funding": [ { @@ -1310,28 +1261,28 @@ "type": "github" } ], - "time": "2023-08-31T06:24:48+00:00" + "time": "2024-07-03T05:06:37+00:00" }, { "name": "phpunit/php-invoker", - "version": "4.0.0", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "suggest": { "ext-pcntl": "*" @@ -1339,7 +1290,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -1365,7 +1316,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" }, "funding": [ { @@ -1373,32 +1325,32 @@ "type": "github" } ], - "time": "2023-02-03T06:56:09+00:00" + "time": "2024-07-03T05:07:44+00:00" }, { "name": "phpunit/php-text-template", - "version": "3.0.1", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -1425,7 +1377,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" }, "funding": [ { @@ -1433,32 +1385,32 @@ "type": "github" } ], - "time": "2023-08-31T14:07:24+00:00" + "time": "2024-07-03T05:08:43+00:00" }, { "name": "phpunit/php-timer", - "version": "6.0.0", + "version": "7.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -1484,7 +1436,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" }, "funding": [ { @@ -1492,20 +1445,20 @@ "type": "github" } ], - "time": "2023-02-03T06:57:52+00:00" + "time": "2024-07-03T05:09:35+00:00" }, { "name": "phpunit/phpunit", - "version": "10.5.27", + "version": "11.2.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "2425f713b2a5350568ccb1a2d3984841a23e83c5" + "reference": "a7a29e8d3113806f18f99d670f580a30e8ffff39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2425f713b2a5350568ccb1a2d3984841a23e83c5", - "reference": "2425f713b2a5350568ccb1a2d3984841a23e83c5", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a7a29e8d3113806f18f99d670f580a30e8ffff39", + "reference": "a7a29e8d3113806f18f99d670f580a30e8ffff39", "shasum": "" }, "require": { @@ -1518,23 +1471,22 @@ "myclabs/deep-copy": "^1.12.0", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", - "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.15", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-invoker": "^4.0.0", - "phpunit/php-text-template": "^3.0.1", - "phpunit/php-timer": "^6.0.0", - "sebastian/cli-parser": "^2.0.1", - "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.1", - "sebastian/diff": "^5.1.1", - "sebastian/environment": "^6.1.0", - "sebastian/exporter": "^5.1.2", - "sebastian/global-state": "^6.0.2", - "sebastian/object-enumerator": "^5.0.0", - "sebastian/recursion-context": "^5.0.0", - "sebastian/type": "^4.0.0", - "sebastian/version": "^4.0.1" + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.5", + "phpunit/php-file-iterator": "^5.0.1", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.1", + "sebastian/comparator": "^6.0.1", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.0", + "sebastian/exporter": "^6.1.3", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/type": "^5.0.1", + "sebastian/version": "^5.0.1" }, "suggest": { "ext-soap": "To be able to generate mocks based on WSDL files" @@ -1545,7 +1497,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.5-dev" + "dev-main": "11.2-dev" } }, "autoload": { @@ -1577,7 +1529,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.27" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.2.8" }, "funding": [ { @@ -1593,32 +1545,32 @@ "type": "tidelift" } ], - "time": "2024-07-10T11:48:06+00:00" + "time": "2024-07-18T14:56:37+00:00" }, { "name": "sebastian/cli-parser", - "version": "2.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -1642,7 +1594,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" }, "funding": [ { @@ -1650,32 +1602,32 @@ "type": "github" } ], - "time": "2024-03-02T07:12:49+00:00" + "time": "2024-07-03T04:41:36+00:00" }, { "name": "sebastian/code-unit", - "version": "2.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + "reference": "6bb7d09d6623567178cf54126afa9c2310114268" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/6bb7d09d6623567178cf54126afa9c2310114268", + "reference": "6bb7d09d6623567178cf54126afa9c2310114268", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -1698,7 +1650,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.1" }, "funding": [ { @@ -1706,32 +1659,32 @@ "type": "github" } ], - "time": "2023-02-03T06:58:43+00:00" + "time": "2024-07-03T04:44:28+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "3.0.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -1753,7 +1706,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" }, "funding": [ { @@ -1761,36 +1715,36 @@ "type": "github" } ], - "time": "2023-02-03T06:59:15+00:00" + "time": "2024-07-03T04:45:54+00:00" }, { "name": "sebastian/comparator", - "version": "5.0.1", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2db5010a484d53ebf536087a70b4a5423c102372" + "reference": "131942b86d3587291067a94f295498ab6ac79c20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2db5010a484d53ebf536087a70b4a5423c102372", - "reference": "2db5010a484d53ebf536087a70b4a5423c102372", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/131942b86d3587291067a94f295498ab6ac79c20", + "reference": "131942b86d3587291067a94f295498ab6ac79c20", "shasum": "" }, "require": { "ext-dom": "*", "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/diff": "^5.0", - "sebastian/exporter": "^5.0" + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^10.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1830,7 +1784,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.0.1" }, "funding": [ { @@ -1838,33 +1792,33 @@ "type": "github" } ], - "time": "2023-08-14T13:18:12+00:00" + "time": "2024-07-03T04:48:07+00:00" }, { "name": "sebastian/complexity", - "version": "3.2.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "68ff824baeae169ec9f2137158ee529584553799" + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", - "reference": "68ff824baeae169ec9f2137158ee529584553799", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -1888,7 +1842,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" }, "funding": [ { @@ -1896,33 +1850,33 @@ "type": "github" } ], - "time": "2023-12-21T08:37:17+00:00" + "time": "2024-07-03T04:49:50+00:00" }, { "name": "sebastian/diff", - "version": "5.1.1", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0", - "symfony/process": "^6.4" + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1955,7 +1909,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" }, "funding": [ { @@ -1963,27 +1917,27 @@ "type": "github" } ], - "time": "2024-03-02T07:15:17+00:00" + "time": "2024-07-03T04:53:05+00:00" }, { "name": "sebastian/environment", - "version": "6.1.0", + "version": "7.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", + "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "suggest": { "ext-posix": "*" @@ -1991,7 +1945,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "6.1-dev" + "dev-main": "7.2-dev" } }, "autoload": { @@ -2019,7 +1973,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.0" }, "funding": [ { @@ -2027,34 +1981,34 @@ "type": "github" } ], - "time": "2024-03-23T08:47:14+00:00" + "time": "2024-07-03T04:54:44+00:00" }, { "name": "sebastian/exporter", - "version": "5.1.2", + "version": "6.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "955288482d97c19a372d3f31006ab3f37da47adf" + "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", - "reference": "955288482d97c19a372d3f31006ab3f37da47adf", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e", + "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/recursion-context": "^5.0" + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.2" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "6.1-dev" } }, "autoload": { @@ -2097,7 +2051,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2" + "source": "https://github.com/sebastianbergmann/exporter/tree/6.1.3" }, "funding": [ { @@ -2105,35 +2059,35 @@ "type": "github" } ], - "time": "2024-03-02T07:17:12+00:00" + "time": "2024-07-03T04:56:19+00:00" }, { "name": "sebastian/global-state", - "version": "6.0.2", + "version": "7.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", "shasum": "" }, "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -2159,7 +2113,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" }, "funding": [ { @@ -2167,33 +2121,33 @@ "type": "github" } ], - "time": "2024-03-02T07:19:19+00:00" + "time": "2024-07-03T04:57:36+00:00" }, { "name": "sebastian/lines-of-code", - "version": "2.0.2", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -2217,7 +2171,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" }, "funding": [ { @@ -2225,34 +2179,34 @@ "type": "github" } ], - "time": "2023-12-21T08:38:20+00:00" + "time": "2024-07-03T04:58:38+00:00" }, { "name": "sebastian/object-enumerator", - "version": "5.0.0", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", "shasum": "" }, "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -2274,7 +2228,8 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" }, "funding": [ { @@ -2282,32 +2237,32 @@ "type": "github" } ], - "time": "2023-02-03T07:08:32+00:00" + "time": "2024-07-03T05:00:13+00:00" }, { "name": "sebastian/object-reflector", - "version": "3.0.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -2329,7 +2284,8 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" }, "funding": [ { @@ -2337,32 +2293,32 @@ "type": "github" } ], - "time": "2023-02-03T07:06:18+00:00" + "time": "2024-07-03T05:01:32+00:00" }, { "name": "sebastian/recursion-context", - "version": "5.0.0", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712" + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16", + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -2392,7 +2348,8 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2" }, "funding": [ { @@ -2400,32 +2357,32 @@ "type": "github" } ], - "time": "2023-02-03T07:05:40+00:00" + "time": "2024-07-03T05:10:34+00:00" }, { "name": "sebastian/type", - "version": "4.0.0", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + "reference": "fb6a6566f9589e86661291d13eba708cce5eb4aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb6a6566f9589e86661291d13eba708cce5eb4aa", + "reference": "fb6a6566f9589e86661291d13eba708cce5eb4aa", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -2448,7 +2405,8 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.0.1" }, "funding": [ { @@ -2456,29 +2414,29 @@ "type": "github" } ], - "time": "2023-02-03T07:10:45+00:00" + "time": "2024-07-03T05:11:49+00:00" }, { "name": "sebastian/version", - "version": "4.0.1", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + "reference": "45c9debb7d039ce9b97de2f749c2cf5832a06ac4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/45c9debb7d039ce9b97de2f749c2cf5832a06ac4", + "reference": "45c9debb7d039ce9b97de2f749c2cf5832a06ac4", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -2501,7 +2459,8 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.1" }, "funding": [ { @@ -2509,7 +2468,7 @@ "type": "github" } ], - "time": "2023-02-07T11:34:05+00:00" + "time": "2024-07-03T05:13:08+00:00" }, { "name": "symfony/polyfill-ctype", @@ -2720,25 +2679,74 @@ ], "time": "2024-03-03T12:36:25+00:00" }, + { + "name": "utopia-php/cli", + "version": "0.16.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/cli.git", + "reference": "5b936638c90c86d1bae83d0dbe81fe14d12ff8ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/cli/zipball/5b936638c90c86d1bae83d0dbe81fe14d12ff8ff", + "reference": "5b936638c90c86d1bae83d0dbe81fe14d12ff8ff", + "shasum": "" + }, + "require": { + "php": ">=7.4", + "utopia-php/framework": "0.*.*" + }, + "require-dev": { + "laravel/pint": "1.2.*", + "phpunit/phpunit": "^9.3", + "squizlabs/php_codesniffer": "^3.6", + "vimeo/psalm": "4.0.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\CLI\\": "src/CLI" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A simple CLI library to manage command line applications", + "keywords": [ + "cli", + "command line", + "framework", + "php", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/cli/issues", + "source": "https://github.com/utopia-php/cli/tree/0.16.0" + }, + "time": "2023-08-05T13:13:08+00:00" + }, { "name": "vlucas/phpdotenv", - "version": "v5.6.0", + "version": "v5.6.1", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4" + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", - "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", "shasum": "" }, "require": { "ext-pcre": "*", - "graham-campbell/result-type": "^1.1.2", + "graham-campbell/result-type": "^1.1.3", "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.2", + "phpoption/phpoption": "^1.9.3", "symfony/polyfill-ctype": "^1.24", "symfony/polyfill-mbstring": "^1.24", "symfony/polyfill-php80": "^1.24" @@ -2755,7 +2763,7 @@ "extra": { "bamarni-bin": { "bin-links": true, - "forward-command": true + "forward-command": false }, "branch-alias": { "dev-master": "5.6-dev" @@ -2790,7 +2798,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.0" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" }, "funding": [ { @@ -2802,7 +2810,7 @@ "type": "tidelift" } ], - "time": "2023-11-12T22:43:29+00:00" + "time": "2024-07-20T21:52:34+00:00" } ], "aliases": [], diff --git a/docker-compose.yml b/docker-compose.yml index ddb4b2e..2d7c30d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3' - services: supabase-db: build: diff --git a/pint.json b/pint.json index 41457b3..288cbb8 100644 --- a/pint.json +++ b/pint.json @@ -1,8 +1,6 @@ { "preset": "psr12", - "exclude": [ - "./tests/resources" - ], + "exclude": [], "rules": { "array_indentation": true, "single_import_per_statement": true, diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index c8e4e97..3d4a71f 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -5,18 +5,26 @@ use Appwrite\AppwriteException; use Appwrite\Client; use Appwrite\Enums\Compression; -use Appwrite\Enums\IndexType; -use Appwrite\Enums\RelationMutate; -use Appwrite\Enums\RelationshipType; +use Appwrite\Enums\PasswordHash; use Appwrite\Enums\Runtime; use Appwrite\InputFile; -use Appwrite\Services\Databases; use Appwrite\Services\Functions; use Appwrite\Services\Storage; use Appwrite\Services\Teams; use Appwrite\Services\Users; use Override; -use Utopia\Database\Database as DatabaseLibrary; +use Utopia\Database\Database as UtopiaDatabase; +use Utopia\Database\Document as UtopiaDocument; +use Utopia\Database\Exception as DatabaseException; +use Utopia\Database\Exception\Authorization as AuthorizationException; +use Utopia\Database\Exception\Duplicate as DuplicateException; +use Utopia\Database\Exception\Limit as LimitException; +use Utopia\Database\Exception\Structure as StructureException; +use Utopia\Database\Helpers\ID; +use Utopia\Database\Helpers\Permission; +use Utopia\Database\Query; +use Utopia\Database\Validator\Index as IndexValidator; +use Utopia\Database\Validator\Structure; use Utopia\Migration\Destination; use Utopia\Migration\Exception; use Utopia\Migration\Resource; @@ -25,15 +33,6 @@ use Utopia\Migration\Resources\Auth\Team; use Utopia\Migration\Resources\Auth\User; use Utopia\Migration\Resources\Database\Attribute; -use Utopia\Migration\Resources\Database\Attributes\Boolean; -use Utopia\Migration\Resources\Database\Attributes\DateTime; -use Utopia\Migration\Resources\Database\Attributes\Decimal; -use Utopia\Migration\Resources\Database\Attributes\Email; -use Utopia\Migration\Resources\Database\Attributes\Enum; -use Utopia\Migration\Resources\Database\Attributes\IP; -use Utopia\Migration\Resources\Database\Attributes\Relationship; -use Utopia\Migration\Resources\Database\Attributes\Text; -use Utopia\Migration\Resources\Database\Attributes\URL; use Utopia\Migration\Resources\Database\Collection; use Utopia\Migration\Resources\Database\Database; use Utopia\Migration\Resources\Database\Document; @@ -52,15 +51,31 @@ class Appwrite extends Destination protected string $key; - private Databases $databases; + private Functions $functions; private Storage $storage; private Teams $teams; private Users $users; + /** + * @var array + */ + private array $documentBuffer = []; - public function __construct(string $project, string $endpoint, string $key) - { + /** + * @param string $project + * @param string $endpoint + * @param string $key + * @param UtopiaDatabase $database + * @param array>> $collectionConfig + */ + public function __construct( + string $project, + string $endpoint, + string $key, + protected UtopiaDatabase $database, + protected array $collectionConfig + ) { $this->project = $project; $this->endpoint = $endpoint; $this->key = $key; @@ -71,7 +86,6 @@ public function __construct(string $project, string $endpoint, string $key) ->setKey($key) ->addHeader('x-appwrite-preserve-dates', 'true'); - $this->databases = new Databases($this->client); $this->functions = new Functions($this->client); $this->storage = new Storage($this->client); $this->teams = new Teams($this->client); @@ -114,8 +128,8 @@ public static function getSupportedResources(): array /** * @param array $resources + * @return array * @throws AppwriteException - * @throws \Exception */ #[Override] public function report(array $resources = []): array @@ -154,47 +168,6 @@ public function report(array $resources = []): array $this->teams->createMembership('', [], ''); } - // Database - if (\in_array(Resource::TYPE_DATABASE, $resources)) { - $scope = 'database.read'; - $this->databases->list(); - - $scope = 'database.write'; - $this->databases->create('', ''); - } - - if (\in_array(Resource::TYPE_COLLECTION, $resources)) { - $scope = 'collections.read'; - $this->databases->listCollections(''); - - $scope = 'collections.write'; - $this->databases->createCollection('', '', ''); - } - - if (\in_array(Resource::TYPE_ATTRIBUTE, $resources)) { - $scope = 'attributes.read'; - $this->databases->listAttributes('', ''); - - $scope = 'attributes.write'; - $this->databases->createStringAttribute('', '', '', 0, false); - } - - if (\in_array(Resource::TYPE_INDEX, $resources)) { - $scope = 'indexes.read'; - $this->databases->listIndexes('', ''); - - $scope = 'indexes.write'; - $this->databases->createIndex('', '', '', '', []); - } - - if (\in_array(Resource::TYPE_DOCUMENT, $resources)) { - $scope = 'documents.read'; - $this->databases->listDocuments('', ''); - - $scope = 'documents.write'; - $this->databases->createDocument('', '', '', []); - } - // Storage if (\in_array(Resource::TYPE_BUCKET, $resources)) { $scope = 'storage.read'; @@ -218,7 +191,7 @@ public function report(array $resources = []): array $this->functions->list(); $scope = 'functions.write'; - $this->functions->create('', '', ''); + $this->functions->create('', '', Runtime::NODE180()); } } catch (AppwriteException $e) { @@ -245,14 +218,18 @@ protected function import(array $resources, callable $callback): void return; } - foreach ($resources as $resource) { + $total = \count($resources); + + foreach ($resources as $index => $resource) { var_dump("Importing......." . $resource->getGroup() . ' - ' . $resource->getName()); $resource->setStatus(Resource::STATUS_PROCESSING); + $isLast = $index === $total - 1; + try { $responseResource = match ($resource->getGroup()) { - Transfer::GROUP_DATABASES => $this->importDatabaseResource($resource), + Transfer::GROUP_DATABASES => $this->importDatabaseResource($resource, $isLast), Transfer::GROUP_STORAGE => $this->importFileResource($resource), Transfer::GROUP_AUTH => $this->importAuthResource($resource), Transfer::GROUP_FUNCTIONS => $this->importFunctionResource($resource), @@ -266,7 +243,7 @@ protected function import(array $resources, callable $callback): void var_dump("getMessage ==== "); var_dump($e->getMessage()); - if ($e->getCode() === 409) { + if ($e->getCode() === 409) { // DATABASE_ALREADY_EXISTS why SKIP? not termination $resource->setStatus(Resource::STATUS_SKIPPED, $e->getMessage()); } else { @@ -294,263 +271,627 @@ protected function import(array $resources, callable $callback): void /** * @throws AppwriteException * @throws \Exception + * @throws \Throwable */ - public function importDatabaseResource(Resource $resource): Resource + public function importDatabaseResource(Resource $resource, bool $isLast): Resource { - var_dump("Destination Appwrite::importDatabaseResource === " . $resource->getName()); - - $this->databases = new Databases($this->client); - switch ($resource->getName()) { case Resource::TYPE_DATABASE: /** @var Database $resource */ - $this->databases->create( - $resource->getId(), - $resource->getDBName() - ); + $success = $this->createDatabase($resource); break; case Resource::TYPE_COLLECTION: /** @var Collection $resource */ - $newCollection = $this->databases->createCollection( - $resource->getDatabase()->getId(), - $resource->getId(), - $resource->getCollectionName(), - $resource->getPermissions(), - $resource->getDocumentSecurity() - ); - $resource->setId($newCollection['$id']); - break; - case Resource::TYPE_INDEX: - /** @var Index $resource */ - - $type = match ($resource->getType()) { - Index::TYPE_KEY => IndexType::KEY(), - Index::TYPE_UNIQUE => IndexType::UNIQUE(), - Index::TYPE_FULLTEXT => IndexType::FULLTEXT(), - default => throw new \Exception('Invalid IndexType: ' . $resource->getType()), - }; - - $this->databases->createIndex( - $resource->getCollection()->getDatabase()->getId(), - $resource->getCollection()->getId(), - $resource->getKey(), - $type, - $resource->getAttributes(), - $resource->getOrders() - ); + $success = $this->createCollection($resource); break; case Resource::TYPE_ATTRIBUTE: /** @var Attribute $resource */ - $this->createAttribute($resource); + $success = $this->createAttribute($resource); + break; + case Resource::TYPE_INDEX: + /** @var Index $resource */ + $success = $this->createIndex($resource); break; case Resource::TYPE_DOCUMENT: /** @var Document $resource */ - // Check if document has already been created - $exists = \array_key_exists( - $resource->getId(), - $this->cache->get(Resource::TYPE_DOCUMENT) - ); + $success = $this->createDocument($resource, $isLast); + break; + default: + $success = false; + break; + } - if ($exists) { - $resource->setStatus( - Resource::STATUS_SKIPPED, - 'Document has been already created by relationship' - ); + if ($success) { + $resource->setStatus(Resource::STATUS_SUCCESS); + } - return $resource; - } + return $resource; + } - $this->databases->createDocument( - $resource->getCollection()->getDatabase()->getId(), - $resource->getCollection()->getId(), - $resource->getId(), - $resource->getData(), - $resource->getPermissions() - ); - break; + /** + * @throws AuthorizationException + * @throws StructureException + * @throws DatabaseException + */ + protected function createDatabase(Database $resource): bool + { + $resourceId = $resource->getId() == 'unique()' + ? ID::unique() + : $resource->getId(); + + $resource->setId($resourceId); + + $database = $this->database->createDocument('databases', new UtopiaDocument([ + '$id' => $resource->getId(), + 'name' => $resource->getDatabaseName(), + 'enabled' => true, + 'search' => implode(' ', [$resource->getId(), $resource->getDatabaseName()]), + ])); + + $resource->setInternalId($database->getInternalId()); + + /** + * @var array{ + * "$collection": string, + * "$id": string, + * name: string, + * attributes: array>, + * indexes: array> + * } $collections + */ + $collections = $this->collectionConfig['databases']['collections']; + + $attributes = \array_map( + fn ($attr) => new UtopiaDocument($attr), + $collections['attributes'] + ); + $indexes = \array_map( + fn ($index) => new UtopiaDocument($index), + $collections['indexes'] + ); + + $this->database->createCollection( + 'database_' . $database->getInternalId(), + $attributes, + $indexes + ); + + return true; + } + + /** + * @throws AuthorizationException + * @throws DatabaseException + * @throws StructureException + * @throws Exception + */ + protected function createCollection(Collection $resource): bool + { + $resourceId = $resource->getId() == 'unique()' + ? ID::unique() + : $resource->getId(); + + $resource->setId($resourceId); + + $database = $this->database->getDocument( + 'databases', + $resource->getDatabase()->getId() + ); + + if ($database->isEmpty()) { + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Database not found', + ); } - $resource->setStatus(Resource::STATUS_SUCCESS); + $collection = $this->database->createDocument('database_' . $resource->getDatabase()->getInternalId(), new UtopiaDocument([ + '$id' => $resource->getId(), + 'databaseInternalId' => $resource->getDatabase()->getInternalId(), + 'databaseId' => $resource->getDatabase()->getId(), + '$permissions' => Permission::aggregate($resource->getPermissions()), + 'documentSecurity' => $resource->getDocumentSecurity(), + 'enabled' => true, + 'name' => $resource->getCollectionName(), + 'search' => implode(' ', [$resource->getId(), $resource->getCollectionName()]), + ])); + + $resource->setInternalId($collection->getInternalId()); + + $this->database->createCollection( + 'database_' . $resource->getDatabase()->getInternalId() . '_collection_' . $resource->getInternalId(), + permissions: $resource->getPermissions(), + documentSecurity: $resource->getDocumentSecurity() + ); - return $resource; + return true; } /** * @throws AppwriteException * @throws \Exception + * @throws \Throwable */ - public function createAttribute(Attribute $attribute): void + protected function createAttribute(Attribute $resource): bool { - var_dump('createAttribute ' . $attribute->getTypeName()); - var_dump('createAttribute key ' . $attribute->getKey()); - - switch ($attribute->getTypeName()) { - case Attribute::TYPE_STRING: - /** @var Text $attribute */ - $this->databases->createStringAttribute( - $attribute->getCollection()->getDatabase()->getId(), - $attribute->getCollection()->getId(), - $attribute->getKey(), - $attribute->getSize(), - $attribute->getRequired(), - $attribute->getDefault(), - $attribute->getArray() - ); - break; - case Attribute::TYPE_INTEGER: - /** @var \Utopia\Migration\Resources\Database\Attributes\Integer $attribute */ - $this->databases->createIntegerAttribute( - $attribute->getCollection()->getDatabase()->getId(), - $attribute->getCollection()->getId(), - $attribute->getKey(), - $attribute->getRequired(), - $attribute->getMin(), - $attribute->getMax(), - $attribute->getDefault(), - $attribute->getArray() - ); - break; - case Attribute::TYPE_FLOAT: - /** @var Decimal $attribute */ - // todo: Change createFloatAttribute min/max to accept float!!! - - $this->databases->createFloatAttribute( - $attribute->getCollection()->getDatabase()->getId(), - $attribute->getCollection()->getId(), - $attribute->getKey(), - $attribute->getRequired(), - $attribute->getMin(), - $attribute->getMax(), - $attribute->getDefault(), - $attribute->getArray() + $database = $this->database->getDocument( + 'databases', + $resource->getCollection()->getDatabase()->getId(), + ); + if ($database->isEmpty()) { + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Database not found', + ); + } + + $collection = $this->database->getDocument( + 'database_' . $database->getInternalId(), + $resource->getCollection()->getId(), + ); + if ($collection->isEmpty()) { + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Collection not found', + ); + } + + if (!empty($resource->getFormat())) { + if (!Structure::hasFormat($resource->getFormat(), $resource->getType())) { + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: "Format {$resource->getFormat()} not available for attribute type {$resource->getType()}", ); - break; - case Attribute::TYPE_BOOLEAN: - /** @var Boolean $attribute */ - $this->databases->createBooleanAttribute( - $attribute->getCollection()->getDatabase()->getId(), - $attribute->getCollection()->getId(), - $attribute->getKey(), - $attribute->getRequired(), - $attribute->getDefault(), - $attribute->getArray() + } + } + if ($resource->isRequired() && $resource->getDefault() !== null) { + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Cannot set default value for required attribute', + ); + } + if ($resource->isArray() && $resource->getDefault() !== null) { + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Cannot set default value for array attribute', + ); + } + if ($resource->getType() === UtopiaDatabase::VAR_RELATIONSHIP) { + $relatedCollection = $this->database->getDocument( + 'database_' . $database->getInternalId(), + $resource->getOptions()['relatedCollection'] + ); + if ($relatedCollection->isEmpty()) { + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Related collection not found', ); - break; - case Attribute::TYPE_DATETIME: - /** @var DateTime $attribute */ - - var_dump('createDatetimeAttribute key: ' . $attribute->getKey()); - - $this->databases->createDatetimeAttribute( - $attribute->getCollection()->getDatabase()->getId(), - $attribute->getCollection()->getId(), - $attribute->getKey(), - $attribute->getRequired(), - $attribute->getDefault(), - $attribute->getArray() + } + } + + try { + $attribute = new UtopiaDocument([ + '$id' => ID::custom($database->getInternalId() . '_' . $collection->getInternalId() . '_' . $resource->getKey()), + 'key' => $resource->getKey(), + 'databaseInternalId' => $database->getInternalId(), + 'databaseId' => $database->getId(), + 'collectionInternalId' => $collection->getInternalId(), + 'collectionId' => $collection->getId(), + 'type' => $resource->getType(), + 'status' => 'available', // processing, available, failed, deleting, stuck + 'size' => $resource->getSize(), + 'required' => $resource->isRequired(), + 'signed' => $resource->isSigned(), + 'default' => $resource->getDefault(), + 'array' => $resource->isArray(), + 'format' => $resource->getFormat(), + 'formatOptions' => $resource->getFormatOptions(), + 'filters' => $resource->getFilters(), + 'options' => $resource->getOptions(), + ]); + + $this->database->checkAttribute($collection, $attribute); + + $attribute = $this->database->createDocument('attributes', $attribute); + } catch (DuplicateException) { + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Attribute already exists', + ); + } catch (LimitException) { + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Attribute limit exceeded', + ); + } catch (\Throwable $e) { + $this->database->purgeCachedDocument('database_' . $database->getInternalId(), $collection->getId()); + $this->database->purgeCachedCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId()); + throw $e; + } + + $options = $resource->getOptions(); + + if ($resource->getType() === UtopiaDatabase::VAR_RELATIONSHIP && isset($relatedCollection) && $options['twoWay']) { + $twoWayKey = $options['twoWayKey']; + $options['relatedCollection'] = $collection->getId(); + $options['twoWayKey'] = $resource->getKey(); + $options['side'] = UtopiaDatabase::RELATION_SIDE_CHILD; + + try { + $twoWayAttribute = new UtopiaDocument([ + '$id' => ID::custom($database->getInternalId() . '_' . $relatedCollection->getInternalId() . '_' . $twoWayKey), + 'key' => $twoWayKey, + 'databaseInternalId' => $database->getInternalId(), + 'databaseId' => $database->getId(), + 'collectionInternalId' => $relatedCollection->getInternalId(), + 'collectionId' => $relatedCollection->getId(), + 'type' => $resource->getType(), + 'status' => 'processing', // processing, available, failed, deleting, stuck + 'size' => $resource->getSize(), + 'required' => $resource->isRequired(), + 'signed' => $resource->isSigned(), + 'default' => $resource->getDefault(), + 'array' => $resource->isArray(), + 'format' => $resource->getFormat(), + 'formatOptions' => $resource->getFormatOptions(), + 'filters' => $resource->getFilters(), + 'options' => $options, + ]); + + $this->database->createDocument('attributes', $twoWayAttribute); + } catch (DuplicateException) { + $this->database->deleteDocument('attributes', $attribute->getId()); + + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Attribute already exists', ); - break; - case Attribute::TYPE_EMAIL: - /** @var Email $attribute */ - $this->databases->createEmailAttribute( - $attribute->getCollection()->getDatabase()->getId(), - $attribute->getCollection()->getId(), - $attribute->getKey(), - $attribute->getRequired(), - $attribute->getDefault(), - $attribute->getArray() + } catch (LimitException) { + $this->database->deleteDocument('attributes', $attribute->getId()); + + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Attribute limit exceeded', ); - break; - case Attribute::TYPE_IP: - /** @var IP $attribute */ - $this->databases->createIpAttribute( - $attribute->getCollection()->getDatabase()->getId(), - $attribute->getCollection()->getId(), - $attribute->getKey(), - $attribute->getRequired(), - $attribute->getDefault(), - $attribute->getArray() + } catch (\Throwable $e) { + $this->database->purgeCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId()); + $this->database->purgeCachedCollection('database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId()); + throw $e; + } + } + + try { + switch ($resource->getType()) { + case UtopiaDatabase::VAR_RELATIONSHIP: + if ( + isset($relatedCollection) + && !$this->database->createRelationship( + collection: 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), + relatedCollection: 'database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId(), + type: $options['relationType'], + twoWay: $options['twoWay'], + id: $resource->getKey(), + twoWayKey: $options['twoWayKey'], + onDelete: $options['onDelete'], + ) + ) { + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Failed to create relationship', + ); + } + + if (isset($relatedCollection) && $options['twoWay']) { + $relatedAttribute = $this->database->getDocument('attributes', $database->getInternalId() . '_' . $relatedCollection->getInternalId() . '_' . $options['twoWayKey']); + $this->database->updateDocument('attributes', $relatedAttribute->getId(), $relatedAttribute->setAttribute('status', 'available')); + } + break; + default: + if (!$this->database->createAttribute( + 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), + $resource->getKey(), + $resource->getType(), + $resource->getSize(), + $resource->isRequired(), + $resource->getDefault(), + $resource->isSigned(), + $resource->isArray(), + $resource->getFormat(), + $resource->getFormatOptions(), + $resource->getFilters(), + )) { + throw new \Exception('Failed to create Attribute'); + } + } + } catch (\Throwable) { + $this->database->deleteDocument('attributes', $attribute->getId()); + + if (isset($relatedAttribute)) { + $this->database->deleteDocument('attributes', $relatedAttribute->getId()); + } + } + + if ($resource->getType() === UtopiaDatabase::VAR_RELATIONSHIP && isset($relatedCollection) && $options['twoWay']) { + $this->database->purgeCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId()); + } + + $this->database->purgeCachedDocument('database_' . $database->getInternalId(), $collection->getId()); + $this->database->purgeCachedCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId()); + + return true; + } + + /** + * @throws Exception + * @throws \Throwable + */ + protected function createIndex(Index $resource): bool + { + $database = $this->database->getDocument( + 'databases', + $resource->getCollection()->getDatabase()->getId(), + ); + if ($database->isEmpty()) { + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Database not found', + ); + } + + $collection = $this->database->getDocument( + 'database_' . $database->getInternalId(), + $resource->getCollection()->getId(), + ); + if ($collection->isEmpty()) { + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Collection not found', + ); + } + + $count = $this->database->count('indexes', [ + Query::equal('collectionInternalId', [$collection->getInternalId()]), + Query::equal('databaseInternalId', [$database->getInternalId()]) + ], $this->database->getLimitForIndexes()); + + if ($count >= $this->database->getLimitForIndexes()) { + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Index limit reached for collection', + ); + } + + /** + * @var array $collectionAttributes + */ + $collectionAttributes = $collection->getAttribute('attributes'); + + $oldAttributes = \array_map( + fn ($attr) => $attr->getArrayCopy(), + $collectionAttributes + ); + + $oldAttributes[] = [ + 'key' => '$id', + 'type' => UtopiaDatabase::VAR_STRING, + 'status' => 'available', + 'required' => true, + 'array' => false, + 'default' => null, + 'size' => UtopiaDatabase::LENGTH_KEY + ]; + $oldAttributes[] = [ + 'key' => '$createdAt', + 'type' => UtopiaDatabase::VAR_DATETIME, + 'status' => 'available', + 'signed' => false, + 'required' => false, + 'array' => false, + 'default' => null, + 'size' => 0 + ]; + $oldAttributes[] = [ + 'key' => '$updatedAt', + 'type' => UtopiaDatabase::VAR_DATETIME, + 'status' => 'available', + 'signed' => false, + 'required' => false, + 'array' => false, + 'default' => null, + 'size' => 0 + ]; + + // Lengths hidden by default + $lengths = []; + + foreach ($resource->getAttributes() as $i => $attribute) { + // find attribute metadata in collection document + $attributeIndex = \array_search( + $attribute, + \array_column($oldAttributes, 'key') + ); + + if ($attributeIndex === false) { + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Attribute not found in collection: ' . $attribute, ); - break; - case Attribute::TYPE_URL: - /** @var URL $attribute */ - $this->databases->createUrlAttribute( - $attribute->getCollection()->getDatabase()->getId(), - $attribute->getCollection()->getId(), - $attribute->getKey(), - $attribute->getRequired(), - $attribute->getDefault(), - $attribute->getArray() + } + + $attributeStatus = $oldAttributes[$attributeIndex]['status']; + $attributeType = $oldAttributes[$attributeIndex]['type']; + $attributeSize = $oldAttributes[$attributeIndex]['size']; + $attributeArray = $oldAttributes[$attributeIndex]['array'] ?? false; + + if ($attributeType === UtopiaDatabase::VAR_RELATIONSHIP) { + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Relationship attributes are not supported in indexes', ); - break; - case Attribute::TYPE_ENUM: - /** @var Enum $attribute */ - $this->databases->createEnumAttribute( - $attribute->getCollection()->getDatabase()->getId(), - $attribute->getCollection()->getId(), - $attribute->getKey(), - $attribute->getElements(), - $attribute->getRequired(), - $attribute->getDefault(), - $attribute->getArray() + } + + // Ensure attribute is available + if ($attributeStatus !== 'available') { + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Attribute not available: ' . $attribute, ); - break; - case Attribute::TYPE_RELATIONSHIP: - /** @var Relationship $attribute */ - - $type = match ($attribute->getRelationType()) { - DatabaseLibrary::RELATION_MANY_TO_MANY => RelationshipType::MANYTOMANY(), - DatabaseLibrary::RELATION_MANY_TO_ONE => RelationshipType::MANYTOONE(), - DatabaseLibrary::RELATION_ONE_TO_MANY => RelationshipType::ONETOMANY(), - DatabaseLibrary::RELATION_ONE_TO_ONE => RelationshipType::ONETOONE(), - default => throw new \Exception('Invalid RelationshipType: ' . $attribute->getRelationType()), - }; + } - $mutation = match ($attribute->getRelationType()) { - DatabaseLibrary::RELATION_MUTATE_CASCADE => RelationMutate::CASCADE(), - DatabaseLibrary::RELATION_MUTATE_RESTRICT => RelationMutate::RESTRICT(), - DatabaseLibrary::RELATION_MUTATE_SET_NULL => RelationMutate::SETNULL(), - default => null, - }; + $lengths[$i] = null; + + if ($attributeType === UtopiaDatabase::VAR_STRING) { + $lengths[$i] = $attributeSize; // set attribute size as index length only for strings + } + + if ($attributeArray === true) { + $lengths[$i] = UtopiaDatabase::ARRAY_INDEX_LENGTH; + $orders[$i] = null; + } + } - $this->databases->createRelationshipAttribute( - $attribute->getCollection()->getDatabase()->getId(), - $attribute->getCollection()->getId(), - $attribute->getRelatedCollection(), - $type, - $attribute->getTwoWay(), - $attribute->getKey(), - $attribute->getTwoWayKey(), - $mutation + $index = new UtopiaDocument([ + '$id' => ID::custom($database->getInternalId() . '_' . $collection->getInternalId() . '_' . $resource->getKey()), + 'key' => $resource->getKey(), + 'status' => 'available', // processing, available, failed, deleting, stuck + 'databaseInternalId' => $database->getInternalId(), + 'databaseId' => $database->getId(), + 'collectionInternalId' => $collection->getInternalId(), + 'collectionId' => $collection->getId(), + 'type' => $resource->getType(), + 'attributes' => $resource->getAttributes(), + 'lengths' => $lengths, + 'orders' => $resource->getOrders(), + ]); + + $validator = new IndexValidator( + $collectionAttributes, + $this->database->getAdapter()->getMaxIndexLength() + ); + + if (!$validator->isValid($index)) { + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Invalid index: ' . $validator->getDescription(), + ); + } + + $index = $this->database->createDocument('indexes', $index); + + try { + $result = $this->database->createIndex( + 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), + $resource->getKey(), + $resource->getType(), + $resource->getAttributes(), + $lengths, + $resource->getOrders() + ); + + if (!$result) { + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Failed to create index', ); - break; - default: - throw new \Exception('Invalid attribute type'); + } + } catch (\Throwable $th) { + $this->database->deleteDocument('indexes', $index->getId()); + + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Failed to create index', + ); } - // Wait for attribute to be created - $this->awaitAttributeCreation($attribute, 5); + $this->database->purgeCachedDocument( + 'database_' . $database->getInternalId(), + $collection->getId() + ); + + return true; } /** - * Await Attribute Creation - * @throws \Exception + * @throws AuthorizationException + * @throws DatabaseException + * @throws StructureException */ - public function awaitAttributeCreation(Attribute $attribute, int $timeout): bool + protected function createDocument(Document $resource, bool $isLast): bool { - $start = \time(); + // Check if document has already been created + $exists = \array_key_exists( + $resource->getId(), + $this->cache->get(Resource::TYPE_DOCUMENT) + ); - while (\time() - $start < $timeout) { - $response = $this->databases->getAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey()); + if ($exists) { + $resource->setStatus( + Resource::STATUS_SKIPPED, + 'Document has been already created by relationship' + ); + return false; + } - if ($response['status'] === 'available') { - return true; - } + if (!$isLast) { + $this->documentBuffer[] = new UtopiaDocument(\array_merge([ + '$id' => $resource->getId(), + '$permissions' => $resource->getPermissions(), + ], $resource->getData())); + return true; + } - \usleep(500000); + try { + $this->database->createDocuments( + $resource->getCollection()->getId(), + $this->documentBuffer + ); + } finally { + $this->documentBuffer = []; } - throw new \Exception('Attribute creation timeout'); + return true; } /** @@ -617,7 +958,7 @@ public function importFile(File $file): File [ 'bucketId' => $bucketId, 'fileId' => $file->getId(), - 'file' => new \CurlFile('data://'.$file->getMimeType().';base64,'.base64_encode($file->getData()), $file->getMimeType(), $file->getFileName()), + 'file' => new \CurlFile('data://' . $file->getMimeType() . ';base64,' . base64_encode($file->getData()), $file->getMimeType(), $file->getFileName()), 'permissions' => $file->getPermissions(), ] ); @@ -633,14 +974,14 @@ public function importFile(File $file): File "/storage/buckets/{$bucketId}/files", [ 'content-type' => 'multipart/form-data', - 'content-range' => 'bytes '.($file->getStart()) . '-' . ($file->getEnd() == ($file->getSize() - 1) ? $file->getSize() : $file->getEnd()) . '/' . $file->getSize(), + 'content-range' => 'bytes ' . ($file->getStart()) . '-' . ($file->getEnd() == ($file->getSize() - 1) ? $file->getSize() : $file->getEnd()) . '/' . $file->getSize(), 'x-appwrite-project' => $this->project, 'x-appwrite-key' => $this->key, ], [ 'bucketId' => $bucketId, 'fileId' => $file->getId(), - 'file' => new \CurlFile('data://'.$file->getMimeType().';base64,'.base64_encode($file->getData()), $file->getMimeType(), $file->getFileName()), + 'file' => new \CurlFile('data://' . $file->getMimeType() . ';base64,' . base64_encode($file->getData()), $file->getMimeType(), $file->getFileName()), 'permissions' => $file->getPermissions(), ] ); @@ -669,7 +1010,7 @@ public function importAuthResource(Resource $resource): Resource switch ($resource->getName()) { case Resource::TYPE_USER: /** @var User $resource */ - if (! empty($resource->getPasswordHash())) { + if (!empty($resource->getPasswordHash())) { $this->importPasswordUser($resource); } else { $this->users->create( @@ -783,7 +1124,7 @@ public function importPasswordUser(User $user): ?array $user->getId(), $user->getEmail(), $hash->getHash(), - 'sha256', + PasswordHash::SHA256(), empty($user->getUsername()) ? null : $user->getUsername() ); break; @@ -892,7 +1233,7 @@ public function importFunctionResource(Resource $resource): Resource $resource->getSchedule(), $resource->getTimeout(), $resource->getEnabled(), - entrypoint:$resource->getEntrypoint(), + entrypoint: $resource->getEntrypoint(), ); break; case Resource::TYPE_ENVIRONMENT_VARIABLE: @@ -932,7 +1273,7 @@ private function importDeployment(Deployment $deployment): Resource ], [ 'functionId' => $functionId, - 'code' => new \CurlFile('data://application/gzip;base64,'.base64_encode($deployment->getData()), 'application/gzip', 'deployment.tar.gz'), + 'code' => new \CurlFile('data://application/gzip;base64,' . base64_encode($deployment->getData()), 'application/gzip', 'deployment.tar.gz'), 'activate' => $deployment->getActivated() ? 'true' : 'false', 'entrypoint' => $deployment->getEntrypoint(), ] @@ -948,12 +1289,12 @@ private function importDeployment(Deployment $deployment): Resource "/v1/functions/{$functionId}/deployments", [ 'content-type' => 'multipart/form-data', - 'content-range' => 'bytes '.($deployment->getStart()).'-'.($deployment->getEnd() == ($deployment->getSize() - 1) ? $deployment->getSize() : $deployment->getEnd()).'/'.$deployment->getSize(), + 'content-range' => 'bytes ' . ($deployment->getStart()) . '-' . ($deployment->getEnd() == ($deployment->getSize() - 1) ? $deployment->getSize() : $deployment->getEnd()) . '/' . $deployment->getSize(), 'x-appwrite-id' => $deployment->getId(), ], [ 'functionId' => $functionId, - 'code' => new \CurlFile('data://application/gzip;base64,'.base64_encode($deployment->getData()), 'application/gzip', 'deployment.tar.gz'), + 'code' => new \CurlFile('data://application/gzip;base64,' . base64_encode($deployment->getData()), 'application/gzip', 'deployment.tar.gz'), 'activate' => $deployment->getActivated(), 'entrypoint' => $deployment->getEntrypoint(), ] diff --git a/src/Migration/Exception.php b/src/Migration/Exception.php index 22bac9b..e42f890 100644 --- a/src/Migration/Exception.php +++ b/src/Migration/Exception.php @@ -8,15 +8,15 @@ class Exception extends \Exception public string $resourceGroup; - public string $resourceId; + public ?string $resourceId; public function __construct( string $resourceName, string $resourceGroup, - string $message, + ?string $resourceId = null, + string $message = '', int $code = 0, ?\Throwable $previous = null, - string $resourceId = '' ) { $this->resourceName = $resourceName; $this->resourceId = $resourceId; diff --git a/src/Migration/Resources/Auth/User.php b/src/Migration/Resources/Auth/User.php index 7c68932..f1f6a68 100644 --- a/src/Migration/Resources/Auth/User.php +++ b/src/Migration/Resources/Auth/User.php @@ -88,14 +88,14 @@ public static function getName(): string /** * Get Email */ - public function getEmail(): ?string + public function getEmail(): string { return $this->email; } /** * Get Username */ - public function getUsername(): ?string + public function getUsername(): string { return $this->username; } diff --git a/src/Migration/Resources/Database/Attribute.php b/src/Migration/Resources/Database/Attribute.php index 98edfb0..7c6afee 100644 --- a/src/Migration/Resources/Database/Attribute.php +++ b/src/Migration/Resources/Database/Attribute.php @@ -8,32 +8,41 @@ abstract class Attribute extends Resource { public const string TYPE_STRING = 'string'; - - public const string TYPE_INTEGER = 'int'; - public const string TYPE_FLOAT = 'float'; - public const string TYPE_BOOLEAN = 'bool'; - public const string TYPE_DATETIME = 'dateTime'; - public const string TYPE_EMAIL = 'email'; - public const string TYPE_ENUM = 'enum'; - public const string TYPE_IP = 'IP'; - public const string TYPE_URL = 'URL'; - public const string TYPE_RELATIONSHIP = 'relationship'; - + /** + * @param string $key + * @param Collection $collection + * @param int $size + * @param bool $required + * @param mixed|null $default + * @param bool $array + * @param bool $signed + * @param string $format + * @param array $formatOptions + * @param array $filters + * @param array $options + */ public function __construct( protected readonly string $key, protected readonly Collection $collection, + protected readonly int $size = 0, protected readonly bool $required = false, - protected readonly bool $array = false + protected readonly mixed $default = null, + protected readonly bool $array = false, + protected readonly bool $signed = false, + protected readonly string $format = '', + protected readonly array $formatOptions = [], + protected readonly array $filters = [], + protected readonly array $options = [], ) { } @@ -44,11 +53,17 @@ public function jsonSerialize(): array { return [ 'key' => $this->key, - 'type' => $this->getTypeName(), 'collection' => $this->collection, - //'collectionId' => $this->collection->getId(), + 'type' => $this->getType(), + 'size' => $this->size, 'required' => $this->required, + 'default' => $this->default, 'array' => $this->array, + 'signed' => $this->signed, + 'format' => $this->format, + 'formatOptions' => $this->formatOptions, + 'filters' => $this->filters, + 'options' => $this->options, ]; } @@ -57,7 +72,7 @@ public static function getName(): string return Resource::TYPE_ATTRIBUTE; } - abstract public function getTypeName(): string; + abstract public function getType(): string; public function getGroup(): string { @@ -74,13 +89,57 @@ public function getCollection(): Collection return $this->collection; } - public function getRequired(): bool + public function getSize(): int + { + return $this->size; + } + + public function isRequired(): bool { return $this->required; } - public function getArray(): bool + public function getDefault(): mixed + { + return $this->default; + } + + public function isArray(): bool { return $this->array; } + + public function isSigned(): bool + { + return $this->signed; + } + + public function getFormat(): string + { + return $this->format; + } + + /** + * @return array + */ + public function getFormatOptions(): array + { + return $this->formatOptions; + } + + /** + * @return array + */ + public function getFilters(): array + { + return $this->filters; + } + + /** + * @return array + */ + public function getOptions(): array + { + return $this->options; + } } diff --git a/src/Migration/Resources/Database/Attributes/Boolean.php b/src/Migration/Resources/Database/Attributes/Boolean.php index 5ac539d..73a2927 100644 --- a/src/Migration/Resources/Database/Attributes/Boolean.php +++ b/src/Migration/Resources/Database/Attributes/Boolean.php @@ -11,14 +11,35 @@ public function __construct( string $key, Collection $collection, bool $required = false, + ?bool $default = null, bool $array = false, - private readonly ?bool $default = null ) { - parent::__construct($key, $collection, $required, $array); + parent::__construct( + $key, + $collection, + required: $required, + default: $default, + array: $array + ); } /** - * @param array $array + * @param array{ + * key: string, + * collection: array{ + * database: array{ + * id: string, + * name: string, + * }, + * name: string, + * id: string, + * documentSecurity: bool, + * permissions: ?array + * }, + * required: bool, + * array: bool, + * default: ?bool, + * } $array * @return self */ public static function fromArray(array $array): self @@ -26,29 +47,14 @@ public static function fromArray(array $array): self return new self( $array['key'], Collection::fromArray($array['collection']), - $array['required'] ?? false, - $array['array'] ?? false, - $array['default'] ?? null + required: $array['required'], + default: $array['default'], + array: $array['array'], ); } - /** - * @return array - */ - public function jsonSerialize(): array - { - return array_merge(parent::jsonSerialize(), [ - 'default' => $this->default, - ]); - } - - public function getTypeName(): string + public function getType(): string { return Attribute::TYPE_BOOLEAN; } - - public function getDefault(): ?bool - { - return $this->default; - } } diff --git a/src/Migration/Resources/Database/Attributes/DateTime.php b/src/Migration/Resources/Database/Attributes/DateTime.php index 4fd09a7..5ad52c6 100644 --- a/src/Migration/Resources/Database/Attributes/DateTime.php +++ b/src/Migration/Resources/Database/Attributes/DateTime.php @@ -7,50 +7,55 @@ class DateTime extends Attribute { - protected ?string $default; - - /** - * @param ?string $default - */ - public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, string $default = null) - { - parent::__construct($key, $collection, $required, $array); - $this->default = $default; + public function __construct( + string $key, + Collection $collection, + bool $required = false, + ?string $default = null, + bool $array = false, + ) { + parent::__construct( + $key, + $collection, + required: $required, + default: $default, + array: $array, + filters: ['datetime'], + ); } - public function getTypeName(): string + public function getType(): string { return Attribute::TYPE_DATETIME; } + /** + * @param array{ + * key: string, + * collection: array{ + * database: array{ + * id: string, + * name: string, + * }, + * name: string, + * id: string, + * documentSecurity: bool, + * permissions: ?array + * }, + * required: bool, + * array: bool, + * default: ?string, + * } $array + * @return self + */ public static function fromArray(array $array): self { return new self( $array['key'], Collection::fromArray($array['collection']), - $array['required'] ?? false, - $array['array'] ?? false, - $array['default'] ?? null, + required: $array['required'], + default: $array['default'], + array: $array['array'], ); } - - /** - * @return array - */ - public function jsonSerialize(): array - { - return array_merge(parent::jsonSerialize(), [ - 'default' => $this->default, - ]); - } - - public function getDefault(): ?string - { - return $this->default; - } - - public function setDefault(string $default): void - { - $this->default = $default; - } } diff --git a/src/Migration/Resources/Database/Attributes/Decimal.php b/src/Migration/Resources/Database/Attributes/Decimal.php index ab89000..3d894ba 100644 --- a/src/Migration/Resources/Database/Attributes/Decimal.php +++ b/src/Migration/Resources/Database/Attributes/Decimal.php @@ -11,16 +11,45 @@ public function __construct( string $key, Collection $collection, bool $required = false, + ?float $default = null, bool $array = false, - private readonly ?float $default = null, - private readonly ?float $min = null, - private readonly ?float $max = null + ?float $min = null, + ?float $max = null ) { - parent::__construct($key, $collection, $required, $array); + parent::__construct( + $key, + $collection, + required: $required, + default: $default, + array: $array, + formatOptions: [ + 'min' => $min, + 'max' => $max, + ] + ); } /** - * @param array $array + * @param array{ + * key: string, + * collection: array{ + * database: array{ + * id: string, + * name: string, + * }, + * name: string, + * id: string, + * documentSecurity: bool, + * permissions: ?array + * }, + * required: bool, + * array: bool, + * default: ?float, + * formatOptions: array{ + * min: ?float, + * max: ?float + * } + * } $array * @return self */ public static function fromArray(array $array): self @@ -28,43 +57,26 @@ public static function fromArray(array $array): self return new self( $array['key'], Collection::fromArray($array['collection']), - $array['required'] ?? false, - $array['array'] ?? false, - $array['default'] ?? null, - $array['min'] ?? null, - $array['max'] ?? null + required: $array['required'], + default: $array['default'], + array: $array['array'], + min: $array['formatOptions']['min'], + max: $array['formatOptions']['max'], ); } - /** - * @return array - */ - public function jsonSerialize(): array - { - return array_merge(parent::jsonSerialize(), [ - 'default' => $this->default, - 'min' => $this->min, - 'max' => $this->max, - ]); - } - - public function getTypeName(): string + public function getType(): string { return Attribute::TYPE_FLOAT; } public function getMin(): ?float { - return $this->min; + return (float)$this->formatOptions['min']; } public function getMax(): ?float { - return $this->max; - } - - public function getDefault(): ?float - { - return $this->default; + return (float)$this->formatOptions['max']; } } diff --git a/src/Migration/Resources/Database/Attributes/Email.php b/src/Migration/Resources/Database/Attributes/Email.php index d815236..630e350 100644 --- a/src/Migration/Resources/Database/Attributes/Email.php +++ b/src/Migration/Resources/Database/Attributes/Email.php @@ -6,7 +6,7 @@ class Email extends Text { - public function getTypeName(): string + public function getType(): string { return Attribute::TYPE_EMAIL; } diff --git a/src/Migration/Resources/Database/Attributes/Enum.php b/src/Migration/Resources/Database/Attributes/Enum.php index b7f58f3..06150dc 100644 --- a/src/Migration/Resources/Database/Attributes/Enum.php +++ b/src/Migration/Resources/Database/Attributes/Enum.php @@ -8,21 +8,48 @@ class Enum extends Attribute { /** - * @param array $elements + * @param array $elements */ public function __construct( string $key, Collection $collection, - private readonly array $elements, + array $elements, bool $required = false, + ?string $default = null, bool $array = false, - private readonly ?string $default = null ) { - parent::__construct($key, $collection, $required, $array); + parent::__construct( + $key, + $collection, + required: $required, + default: $default, + array: $array, + formatOptions: [ + 'elements' => $elements, + ] + ); } /** - * @param array $array + * @param array{ + * key: string, + * collection: array{ + * database: array{ + * id: string, + * name: string, + * }, + * name: string, + * id: string, + * documentSecurity: bool, + * permissions: ?array + * }, + * required: bool, + * default: ?string, + * array: bool, + * formatOptions: array{ + * elements: array + * } + * } $array * @return self */ public static function fromArray(array $array): self @@ -30,25 +57,14 @@ public static function fromArray(array $array): self return new self( $array['key'], Collection::fromArray($array['collection']), - $array['elements'], - $array['required'] ?? false, - $array['array'] ?? false, - $array['default'] ?? null + elements: $array['formatOptions']['elements'], + required: $array['required'], + default: $array['default'], + array: $array['array'], ); } - /** - * @return array - */ - public function jsonSerialize(): array - { - return array_merge(parent::jsonSerialize(), [ - 'elements' => $this->elements, - 'default' => $this->default, - ]); - } - - public function getTypeName(): string + public function getType(): string { return Attribute::TYPE_ENUM; } @@ -58,11 +74,6 @@ public function getTypeName(): string */ public function getElements(): array { - return $this->elements; - } - - public function getDefault(): ?string - { - return $this->default; + return (array)$this->formatOptions['elements']; } } diff --git a/src/Migration/Resources/Database/Attributes/IP.php b/src/Migration/Resources/Database/Attributes/IP.php index b930c4a..08fbc38 100644 --- a/src/Migration/Resources/Database/Attributes/IP.php +++ b/src/Migration/Resources/Database/Attributes/IP.php @@ -6,7 +6,7 @@ class IP extends Text { - public function getTypeName(): string + public function getType(): string { return Attribute::TYPE_IP; } diff --git a/src/Migration/Resources/Database/Attributes/Integer.php b/src/Migration/Resources/Database/Attributes/Integer.php index f2c5b2a..52d9761 100644 --- a/src/Migration/Resources/Database/Attributes/Integer.php +++ b/src/Migration/Resources/Database/Attributes/Integer.php @@ -11,16 +11,45 @@ public function __construct( string $key, Collection $collection, bool $required = false, + ?int $default = null, bool $array = false, - private readonly ?int $default = null, - private readonly ?int $min = null, - private readonly ?int $max = null + ?int $min = null, + ?int $max = null ) { - parent::__construct($key, $collection, $required, $array); + parent::__construct( + $key, + $collection, + required: $required, + default: $default, + array: $array, + formatOptions: [ + 'min' => $min, + 'max' => $max, + ] + ); } /** - * @param array $array + * @param array{ + * key: string, + * collection: array{ + * database: array{ + * id: string, + * name: string, + * }, + * name: string, + * id: string, + * documentSecurity: bool, + * permissions: ?array + * }, + * required: bool, + * array: bool, + * default: ?int, + * formatOptions: array{ + * min: ?int, + * max: ?int + * } + * } $array * @return self */ public static function fromArray(array $array): self @@ -28,43 +57,26 @@ public static function fromArray(array $array): self return new self( $array['key'], Collection::fromArray($array['collection']), - $array['required'] ?? false, - $array['array'] ?? false, - $array['default'] ?? null, - $array['min'] ?? null, - $array['max'] ?? null + required: $array['required'], + default: $array['default'], + array: $array['array'], + min: $array['formatOptions']['min'] ?? null, + max: $array['formatOptions']['max'] ?? null ); } - /** - * @return array - */ - public function jsonSerialize(): array - { - return array_merge(parent::jsonSerialize(), [ - 'default' => $this->default, - 'min' => $this->min, - 'max' => $this->max, - ]); - } - - public function getTypeName(): string + public function getType(): string { return Attribute::TYPE_FLOAT; } public function getMin(): ?int { - return $this->min; + return (int)$this->formatOptions['min']; } public function getMax(): ?int { - return $this->max; - } - - public function getDefault(): ?int - { - return $this->default; + return (int)$this->formatOptions['max']; } } diff --git a/src/Migration/Resources/Database/Attributes/Relationship.php b/src/Migration/Resources/Database/Attributes/Relationship.php index ed41c35..b332d65 100644 --- a/src/Migration/Resources/Database/Attributes/Relationship.php +++ b/src/Migration/Resources/Database/Attributes/Relationship.php @@ -2,46 +2,58 @@ namespace Utopia\Migration\Resources\Database\Attributes; +use Utopia\Database\Database; use Utopia\Migration\Resources\Database\Attribute; use Utopia\Migration\Resources\Database\Collection; class Relationship extends Attribute { - protected string $relatedCollection; - - protected string $relationType; - - protected bool $twoWay; - - protected string $twoWayKey; - - protected string $onDelete; - - protected string $side; - public function __construct( string $key, Collection $collection, - bool $required = false, - bool $array = false, - string $relatedCollection = '', - string $relationType = '', + string $relatedCollection, + string $relationType, bool $twoWay = false, - string $twoWayKey = '', - string $onDelete = '', - string $side = '' + ?string $twoWayKey = null, + string $onDelete = Database::RELATION_MUTATE_RESTRICT, + string $side = Database::RELATION_SIDE_PARENT ) { - parent::__construct($key, $collection, $required, $array); - $this->relatedCollection = $relatedCollection; - $this->relationType = $relationType; - $this->twoWay = $twoWay; - $this->twoWayKey = $twoWayKey; - $this->onDelete = $onDelete; - $this->side = $side; + parent::__construct( + $key, + $collection, + options: [ + 'relatedCollection' => $relatedCollection, + 'relationType' => $relationType, + 'twoWay' => $twoWay, + 'twoWayKey' => $twoWayKey, + 'onDelete' => $onDelete, + 'side' => $side, + ] + ); } /** - * @param array $array + * @param array{ + * key: string, + * collection: array{ + * database: array{ + * id: string, + * name: string, + * }, + * name: string, + * id: string, + * documentSecurity: bool, + * permissions: ?array + * }, + * options: array{ + * relatedCollection: string, + * relationType: string, + * twoWay: bool, + * twoWayKey: ?string, + * onDelete: string, + * side: string, + * } + * } $array * @return self */ public static function fromArray(array $array): self @@ -49,64 +61,47 @@ public static function fromArray(array $array): self return new self( $array['key'], Collection::fromArray($array['collection']), - $array['required'] ?? false, - $array['array'] ?? false, - $array['relatedCollection'] ?? '', - $array['relationType'] ?? '', - $array['twoWay'] ?? false, - $array['twoWayKey'] ?? '', - $array['onDelete'] ?? '', - $array['side'] ?? '' + relatedCollection: $array['options']['relatedCollection'], + relationType: $array['options']['relationType'], + twoWay: $array['options']['twoWay'], + twoWayKey: $array['options']['twoWayKey'], + onDelete: $array['options']['onDelete'], + side: $array['options']['side'], ); } - /** - * @return array - */ - public function jsonSerialize(): array - { - return array_merge(parent::jsonSerialize(), [ - 'relatedCollection' => $this->relatedCollection, - 'relationType' => $this->relationType, - 'twoWay' => $this->twoWay, - 'twoWayKey' => $this->twoWayKey, - 'onDelete' => $this->onDelete, - 'side' => $this->side, - ]); - } - - public function getTypeName(): string + public function getType(): string { return Attribute::TYPE_RELATIONSHIP; } public function getRelatedCollection(): string { - return $this->relatedCollection; + return $this->options['relatedCollection']; } public function getRelationType(): string { - return $this->relationType; + return $this->options['relationType']; } public function getTwoWay(): bool { - return $this->twoWay; + return $this->options['twoWay']; } - public function getTwoWayKey(): string + public function getTwoWayKey(): ?string { - return $this->twoWayKey; + return $this->options['twoWayKey']; } public function getOnDelete(): string { - return $this->onDelete; + return $this->options['onDelete']; } public function getSide(): string { - return $this->side; + return $this->options['side']; } } diff --git a/src/Migration/Resources/Database/Attributes/Text.php b/src/Migration/Resources/Database/Attributes/Text.php index 2bc0ed3..435c0e3 100644 --- a/src/Migration/Resources/Database/Attributes/Text.php +++ b/src/Migration/Resources/Database/Attributes/Text.php @@ -11,15 +11,38 @@ public function __construct( string $key, Collection $collection, bool $required = false, + ?string $default = null, bool $array = false, - private readonly ?string $default = null, - private readonly int $size = 256 + int $size = 256 ) { - parent::__construct($key, $collection, $required, $array); + parent::__construct( + $key, + $collection, + size: $size, + required: $required, + default: $default, + array: $array + ); } /** - * @param array $array + * @param array{ + * key: string, + * collection: array{ + * database: array{ + * id: string, + * name: string, + * }, + * name: string, + * id: string, + * documentSecurity: bool, + * permissions: ?array + * }, + * required: bool, + * default: ?string, + * array: bool, + * size: int + * } $array * @return self */ public static function fromArray(array $array): self @@ -27,25 +50,14 @@ public static function fromArray(array $array): self return new self( $array['key'], Collection::fromArray($array['collection']), - $array['required'] ?? false, - $array['array'] ?? false, - $array['default'] ?? null, - $array['size'] ?? 256 + required: $array['required'], + default: $array['default'] ?? null, + array: $array['array'], + size: $array['size'] ); } - /** - * @return array - */ - public function jsonSerialize(): array - { - return array_merge(parent::jsonSerialize(), [ - 'default' => $this->default, - 'size' => $this->size, - ]); - } - - public function getTypeName(): string + public function getType(): string { return Attribute::TYPE_STRING; } @@ -54,9 +66,4 @@ public function getSize(): int { return $this->size; } - - public function getDefault(): ?string - { - return $this->default; - } } diff --git a/src/Migration/Resources/Database/Attributes/URL.php b/src/Migration/Resources/Database/Attributes/URL.php index 8410800..e67d690 100644 --- a/src/Migration/Resources/Database/Attributes/URL.php +++ b/src/Migration/Resources/Database/Attributes/URL.php @@ -6,7 +6,7 @@ class URL extends Text { - public function getTypeName(): string + public function getType(): string { return Attribute::TYPE_URL; } diff --git a/src/Migration/Resources/Database/Collection.php b/src/Migration/Resources/Database/Collection.php index ee5fb57..1911603 100644 --- a/src/Migration/Resources/Database/Collection.php +++ b/src/Migration/Resources/Database/Collection.php @@ -16,26 +16,35 @@ class Collection extends Resource */ public function __construct( private readonly Database $database, - private readonly string $name, string $id, + private readonly string $name, private readonly bool $documentSecurity = false, - array $permissions = [] + array $permissions = [], ) { $this->id = $id; $this->permissions = $permissions; } /** - * @param array $array + * @param array{ + * database: array{ + * id: string, + * name: string, + * }, + * name: string, + * id: string, + * documentSecurity: bool, + * permissions: ?array + * } $array */ public static function fromArray(array $array): self { return new self( Database::fromArray($array['database']), - $array['name'], - $array['id'], - $array['documentSecurity'] ?? false, - $array['permissions'] ?? [] + id: $array['id'], + name: $array['name'], + documentSecurity: $array['documentSecurity'], + permissions: $array['permissions'] ?? [] ); } @@ -46,7 +55,6 @@ public function jsonSerialize(): array { return array_merge([ 'database' => $this->database, - //'databaseId' => $this->database->getId(), 'id' => $this->id, 'name' => $this->name, 'documentSecurity' => $this->documentSecurity, diff --git a/src/Migration/Resources/Database/Database.php b/src/Migration/Resources/Database/Database.php index 05dcc38..39d8879 100644 --- a/src/Migration/Resources/Database/Database.php +++ b/src/Migration/Resources/Database/Database.php @@ -24,7 +24,10 @@ public function __construct( } /** - * @param array $array + * @param array{ + * id: string, + * name: string, + * } $array */ public static function fromArray(array $array): self { @@ -55,7 +58,7 @@ public function getGroup(): string return Transfer::GROUP_DATABASES; } - public function getDBName(): string + public function getDatabaseName(): string { return $this->name; } diff --git a/src/Migration/Resources/Database/Document.php b/src/Migration/Resources/Database/Document.php index 230f696..db3d41a 100644 --- a/src/Migration/Resources/Database/Document.php +++ b/src/Migration/Resources/Database/Document.php @@ -24,14 +24,28 @@ public function __construct( } /** - * @param array $array + * @param array{ + * id: string, + * collection: array{ + * database: array{ + * id: string, + * name: string, + * }, + * name: string, + * id: string, + * documentSecurity: bool, + * permissions: ?array + * }, + * data: array, + * permissions: ?array + * } $array */ public static function fromArray(array $array): self { return new self( $array['id'], Collection::fromArray($array['collection']), - $array['attributes'], + $array['data'], $array['permissions'] ?? [] ); } @@ -44,7 +58,7 @@ public function jsonSerialize(): array return [ 'id' => $this->id, 'collection' => $this->collection, - 'attributes' => $this->data, + 'data' => $this->data, 'permissions' => $this->permissions, ]; } diff --git a/src/Migration/Resources/Database/Index.php b/src/Migration/Resources/Database/Index.php index 8c8ee7c..59b4c80 100644 --- a/src/Migration/Resources/Database/Index.php +++ b/src/Migration/Resources/Database/Index.php @@ -35,7 +35,24 @@ public function __construct( } /** - * @param array $array + * @param array{ + * id: string, + * key: string, + * collection: array{ + * database: array{ + * id: string, + * name: string, + * }, + * name: string, + * id: string, + * documentSecurity: bool, + * permissions: ?array + * }, + * type: string, + * attributes: array, + * lengths: ?array, + * orders: ?array + * } $array */ public static function fromArray(array $array): self { @@ -43,7 +60,7 @@ public static function fromArray(array $array): self $array['id'], $array['key'], Collection::fromArray($array['collection']), - $array['type'] ?? '', + $array['type'], $array['attributes'], $array['lengths'] ?? [], $array['orders'] ?? [] diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index ad00747..e2d6cbd 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -109,7 +109,7 @@ public static function getSupportedResources(): array } /** - * @param array $resources + * @param array $resources * @return array * * @throws \Exception @@ -225,8 +225,8 @@ public function report(array $resources = []): array while (true) { $currentBuckets = $this->storage->listBuckets( $lastBucket - ? [Query::cursorAfter($lastBucket)] - : [Query::limit(20)] + ? [Query::cursorAfter($lastBucket)] + : [Query::limit(20)] )['buckets']; $buckets = array_merge($buckets, $currentBuckets); @@ -245,8 +245,8 @@ public function report(array $resources = []): array $currentFiles = $this->storage->listFiles( $bucket['$id'], $lastFile - ? [Query::cursorAfter($lastFile)] - : [Query::limit(20)] + ? [Query::cursorAfter($lastFile)] + : [Query::limit(20)] )['files']; $files = array_merge($files, $currentFiles); @@ -278,7 +278,7 @@ public function report(array $resources = []): array $report[Resource::TYPE_DEPLOYMENT] = 0; $functions = $this->functions->list()['functions']; foreach ($functions as $function) { - if (! empty($function['deployment'])) { + if (!empty($function['deployment'])) { $report[Resource::TYPE_DEPLOYMENT] += 1; } } @@ -316,8 +316,8 @@ public function report(array $resources = []): array /** * Export Auth Resources * - * @param int $batchSize Max 100 - * @param array $resources + * @param int $batchSize Max 100 + * @param array $resources */ protected function exportGroupAuth(int $batchSize, array $resources): void { @@ -329,9 +329,9 @@ protected function exportGroupAuth(int $batchSize, array $resources): void $this->addError(new Exception( Resource::TYPE_USER, Transfer::GROUP_AUTH, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e )); } @@ -343,9 +343,9 @@ protected function exportGroupAuth(int $batchSize, array $resources): void $this->addError(new Exception( Resource::TYPE_TEAM, Transfer::GROUP_AUTH, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e )); } @@ -357,9 +357,9 @@ protected function exportGroupAuth(int $batchSize, array $resources): void $this->addError(new Exception( Resource::TYPE_MEMBERSHIP, Transfer::GROUP_AUTH, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e )); } } @@ -401,7 +401,7 @@ private function exportUsers(int $batchSize): void '', $user['emailVerification'] ?? false, $user['phoneVerification'] ?? false, - ! $user['status'], + !$user['status'], $user['prefs'] ?? [], ); @@ -532,9 +532,9 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void new Exception( Resource::TYPE_DATABASE, Transfer::GROUP_DATABASES, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e ) ); } @@ -548,9 +548,9 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void new Exception( Resource::TYPE_COLLECTION, Transfer::GROUP_DATABASES, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e ) ); } @@ -564,9 +564,9 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void new Exception( Resource::TYPE_ATTRIBUTE, Transfer::GROUP_DATABASES, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e ) ); } @@ -580,9 +580,9 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void new Exception( Resource::TYPE_INDEX, Transfer::GROUP_DATABASES, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e ) ); } @@ -596,9 +596,9 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void new Exception( Resource::TYPE_DOCUMENT, Transfer::GROUP_DATABASES, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e ) ); } @@ -632,7 +632,7 @@ private function exportDocuments(int $batchSize): void /** @var Attribute|Relationship $attribute */ if ( $attribute->getCollection()->getId() === $collection->getId() && - $attribute->getTypeName() === Attribute::TYPE_RELATIONSHIP && + $attribute->getType() === Attribute::TYPE_RELATIONSHIP && $attribute->getSide() === 'parent' && $attribute->getRelationType() == 'manyToMany' ) { @@ -700,8 +700,8 @@ private function exportDocuments(int $batchSize): void continue; } - if ($attribute->getRequired() && ! isset($document[$attribute->getKey()])) { - switch ($attribute->getTypeName()) { + if ($attribute->isRequired() && !isset($document[$attribute->getKey()])) { + switch ($attribute->getType()) { case Attribute::TYPE_BOOLEAN: $document[$attribute->getKey()] = false; break; @@ -750,14 +750,14 @@ private function convertAttribute(array $value, Collection $collection): Attribu { switch ($value['type']) { case 'string': - if (! isset($value['format'])) { + if (!isset($value['format'])) { return new Text( $value['key'], $collection, - $value['required'], - $value['array'], - $value['default'], - $value['size'] ?? 0 + required: $value['required'], + default: $value['default'], + array: $value['array'], + size: $value['size'] ?? 0 ); } @@ -765,100 +765,91 @@ private function convertAttribute(array $value, Collection $collection): Attribu 'email' => new Email( $value['key'], $collection, - $value['required'], - $value['array'], - $value['default'] + required: $value['required'], + default: $value['default'], + array: $value['array'], ), 'enum' => new Enum( $value['key'], $collection, - $value['elements'], - $value['required'], - $value['array'], - $value['default'] + elements: $value['elements'], + required: $value['required'], + default: $value['default'], + array: $value['array'], ), 'url' => new URL( $value['key'], $collection, - $value['required'], - $value['array'], - $value['default'] + required: $value['required'], + default: $value['default'], + array: $value['array'], ), 'ip' => new IP( $value['key'], $collection, - $value['required'], - $value['array'], - $value['default'] + required: $value['required'], + default: $value['default'], + array: $value['array'], ), - // 'datetime' => new DateTime( - // $value['key'], - // $collection, - // $value['required'], - // $value['array'], - // $value['default'] - // ), default => new Text( $value['key'], $collection, - $value['required'], - $value['array'], - $value['default'], - $value['size'] ?? 0 + required: $value['required'], + default: $value['default'], + array: $value['array'], + size: $value['size'] ?? 0, ), }; case 'boolean': return new Boolean( $value['key'], $collection, - $value['required'], - $value['array'], - $value['default'] + required: $value['required'], + default: $value['default'], + array: $value['array'] ); case 'integer': return new Integer( $value['key'], $collection, - $value['required'], - $value['array'], - $value['default'], - $value['min'] ?? null, - $value['max'] ?? null + required: $value['required'], + default: $value['default'], + array: $value['array'], + min: $value['min'] ?? null, + max: $value['max'] ?? null, ); case 'double': return new Decimal( $value['key'], $collection, - $value['required'], - $value['array'], - $value['default'], - $value['min'] ?? null, - $value['max'] ?? null + required: $value['required'], + default: $value['default'], + array: $value['array'], + min: $value['min'] ?? null, + max: $value['max'] ?? null, ); case 'relationship': return new Relationship( $value['key'], $collection, - $value['required'], - $value['array'], - $value['relatedCollection'], - $value['relationType'], - $value['twoWay'], - $value['twoWayKey'], - $value['onDelete'], - $value['side'] + relatedCollection: $value['relatedCollection'], + relationType: $value['relationType'], + twoWay: $value['twoWay'], + twoWayKey: $value['twoWayKey'], + onDelete: $value['onDelete'], + side: $value['side'], ); case 'datetime': return new DateTime( $value['key'], $collection, - $value['required'], - $value['array'], - $value['default'] + required: $value['required'], + default: $value['default'], + array: $value['array'], ); } - throw new \Exception('Unknown attribute type: '.$value['type']); + throw new \Exception('Unknown attribute type: ' . $value['type']); } /** @@ -945,7 +936,7 @@ private function exportCollections(int $batchSize): void $collections[] = $newCollection; } - $lastCollection = ! empty($collection) + $lastCollection = !empty($collection) ? $collections[count($collections) - 1]->getId() : null; @@ -1059,7 +1050,7 @@ private function exportIndexes(int $batchSize): void } } - protected function exportGroupStorage(int $batchSize, array $resources) + protected function exportGroupStorage(int $batchSize, array $resources): void { try { if (\in_array(Resource::TYPE_BUCKET, $resources)) { @@ -1070,9 +1061,9 @@ protected function exportGroupStorage(int $batchSize, array $resources) new Exception( Resource::TYPE_BUCKET, Transfer::GROUP_STORAGE, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e ) ); } @@ -1086,9 +1077,9 @@ protected function exportGroupStorage(int $batchSize, array $resources) new Exception( Resource::TYPE_FILE, Transfer::GROUP_STORAGE, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e ) ); } @@ -1102,9 +1093,9 @@ protected function exportGroupStorage(int $batchSize, array $resources) new Exception( Resource::TYPE_BUCKET, Transfer::GROUP_STORAGE, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e ) ); } @@ -1187,9 +1178,9 @@ private function exportFiles(int $batchSize): void $this->addError(new Exception( resourceName: Resource::TYPE_FILE, resourceGroup: Transfer::GROUP_STORAGE, + resourceId: $file['$id'], message: $e->getMessage(), - code: $e->getCode(), - resourceId: $file['$id'] + code: $e->getCode() )); } @@ -1203,6 +1194,9 @@ private function exportFiles(int $batchSize): void } } + /** + * @throws \Exception + */ private function exportFileData(File $file): void { // Set the chunk size (5MB) @@ -1252,9 +1246,9 @@ protected function exportGroupFunctions(int $batchSize, array $resources): void $this->addError(new Exception( Resource::TYPE_FUNCTION, Transfer::GROUP_FUNCTIONS, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e )); } @@ -1266,9 +1260,9 @@ protected function exportGroupFunctions(int $batchSize, array $resources): void $this->addError(new Exception( Resource::TYPE_DEPLOYMENT, Transfer::GROUP_FUNCTIONS, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e )); } } @@ -1377,7 +1371,10 @@ private function exportDeployments(int $batchSize, bool $exportOnlyActive = fals } } - private function exportDeploymentData(Func $func, array $deployment) + /** + * @throws \Exception + */ + private function exportDeploymentData(Func $func, array $deployment): void { // Set the chunk size (5MB) $start = 0; @@ -1395,7 +1392,7 @@ private function exportDeploymentData(Func $func, array $deployment) ); // Content-Length header was missing, file is less than max buffer size. - if (! array_key_exists('Content-Length', $responseHeaders)) { + if (!array_key_exists('Content-Length', $responseHeaders)) { $file = $this->call( 'GET', "/functions/{$func->getId()}/deployments/{$deployment['$id']}/download", @@ -1422,7 +1419,8 @@ private function exportDeploymentData(Func $func, array $deployment) ); $deployment->setInternalId($deployment->getId()); - return $this->callback([$deployment]); + $this->callback([$deployment]); + return; } $fileSize = $responseHeaders['Content-Length']; diff --git a/src/Migration/Sources/Firebase.php b/src/Migration/Sources/Firebase.php index 03950a4..b23afbc 100644 --- a/src/Migration/Sources/Firebase.php +++ b/src/Migration/Sources/Firebase.php @@ -186,9 +186,9 @@ protected function exportGroupAuth(int $batchSize, array $resources): void new Exception( Resource::TYPE_USER, Transfer::GROUP_AUTH, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e ) ); } @@ -283,9 +283,9 @@ protected function exportGroupDatabases(int $batchSize, array $resources) new Exception( Resource::TYPE_DATABASE, Transfer::GROUP_DATABASES, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e ) ); } @@ -299,9 +299,9 @@ protected function exportGroupDatabases(int $batchSize, array $resources) new Exception( Resource::TYPE_COLLECTION, Transfer::GROUP_DATABASES, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e ) ); } @@ -365,28 +365,97 @@ private function exportDB(int $batchSize, bool $pushDocuments, Database $databas } } + /** + * @throws \Exception + */ private function convertAttribute(Collection $collection, string $key, array $field): Attribute { if (array_key_exists('booleanValue', $field)) { - return new Boolean($key, $collection, false, false, null); + return new Boolean( + $key, + $collection, + required:false, + default: null, + array: false, + ); } elseif (array_key_exists('bytesValue', $field)) { - return new Text($key, $collection, false, false, null, 1000000); + return new Text( + $key, + $collection, + required: false, + default: null, + array: false, + size: 1000000, + ); } elseif (array_key_exists('doubleValue', $field)) { - return new Decimal($key, $collection, false, false, null); + return new Decimal( + $key, + $collection, + required: false, + default: null, + array: false, + ); } elseif (array_key_exists('integerValue', $field)) { - return new Integer($key, $collection, false, false, null); + return new Integer( + $key, + $collection, + required: false, + default: null, + array: false, + ); } elseif (array_key_exists('mapValue', $field)) { - return new Text($key, $collection, false, false, null, 1000000); + return new Text( + $key, + $collection, + required: false, + default: null, + array: false, + size: 1000000, + ); } elseif (array_key_exists('nullValue', $field)) { - return new Text($key, $collection, false, false, null, 1000000); + return new Text( + $key, + $collection, + required: false, + default: null, + array: false, + size: 1000000, + ); } elseif (array_key_exists('referenceValue', $field)) { - return new Text($key, $collection, false, false, null, 1000000); //TODO: This should be a reference attribute + return new Text( + $key, + $collection, + required: false, + default: null, + array: false, + size: 1000000, + ); //TODO: This should be a reference attribute } elseif (array_key_exists('stringValue', $field)) { - return new Text($key, $collection, false, false, null, 1000000); + return new Text( + $key, + $collection, + required: false, + default: null, + array: false, + size: 1000000, + ); } elseif (array_key_exists('timestampValue', $field)) { - return new DateTime($key, $collection, false, false, null); + return new DateTime( + $key, + $collection, + required: false, + default: null, + array: false, + ); } elseif (array_key_exists('geoPointValue', $field)) { - return new Text($key, $collection, false, false, null, 1000000); + return new Text( + $key, + $collection, + required: false, + default: null, + array: false, + size: 1000000, + ); } elseif (array_key_exists('arrayValue', $field)) { return $this->calculateArrayType($collection, $key, $field['arrayValue']); } else { @@ -560,9 +629,9 @@ protected function exportGroupStorage(int $batchSize, array $resources): void $this->addError(new Exception( Resource::TYPE_BUCKET, Transfer::GROUP_STORAGE, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e )); } @@ -574,9 +643,9 @@ protected function exportGroupStorage(int $batchSize, array $resources): void $this->addError(new Exception( Resource::TYPE_FILE, Transfer::GROUP_STORAGE, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e )); } diff --git a/src/Migration/Sources/NHost.php b/src/Migration/Sources/NHost.php index 62020c6..a87a1e1 100644 --- a/src/Migration/Sources/NHost.php +++ b/src/Migration/Sources/NHost.php @@ -221,9 +221,9 @@ protected function exportGroupAuth(int $batchSize, array $resources): void $this->addError(new Exception( Resource::TYPE_USER, Transfer::GROUP_AUTH, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e )); } } @@ -285,9 +285,9 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void new Exception( Resource::TYPE_DATABASE, Transfer::GROUP_DATABASES, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e ) ); } @@ -301,9 +301,9 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void new Exception( Resource::TYPE_COLLECTION, Transfer::GROUP_DATABASES, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e ) ); } @@ -317,9 +317,9 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void new Exception( Resource::TYPE_ATTRIBUTE, Transfer::GROUP_DATABASES, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e ) ); } @@ -333,9 +333,9 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void new Exception( Resource::TYPE_DOCUMENT, Transfer::GROUP_DATABASES, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e ) ); } @@ -349,9 +349,9 @@ protected function exportGroupDatabases(int $batchSize, array $resources): void new Exception( Resource::TYPE_INDEX, Transfer::GROUP_DATABASES, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e ) ); } @@ -458,12 +458,12 @@ private function exportDocuments(int $batchSize): void foreach ($collections as $collection) { /** @var Collection $collection */ - $total = $db->query('SELECT COUNT(*) FROM '.$collection->getDatabase()->getDBName().'."'.$collection->getCollectionName().'"')->fetchColumn(); + $total = $db->query('SELECT COUNT(*) FROM '.$collection->getDatabase()->getDatabaseName().'."'.$collection->getCollectionName().'"')->fetchColumn(); $offset = 0; while ($offset < $total) { - $statement = $db->prepare('SELECT row_to_json(t) FROM (SELECT * FROM '.$collection->getDatabase()->getDBName().'."'.$collection->getCollectionName().'" LIMIT :limit OFFSET :offset) t;'); + $statement = $db->prepare('SELECT row_to_json(t) FROM (SELECT * FROM '.$collection->getDatabase()->getDatabaseName().'."'.$collection->getCollectionName().'" LIMIT :limit OFFSET :offset) t;'); $statement->bindValue(':limit', $batchSize, \PDO::PARAM_INT); $statement->bindValue(':offset', $offset, \PDO::PARAM_INT); $statement->execute(); @@ -509,7 +509,13 @@ private function convertAttribute(array $column, Collection $collection): Attrib // Numbers case 'boolean': case 'bool': - return new Boolean($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default']); + return new Boolean( + $column['column_name'], + $collection, + required: $column['is_nullable'] === 'NO', + default: $column['column_default'], + array: $isArray, + ); case 'smallint': case 'int2': if (! is_numeric($column['column_default']) && ! is_null($column['column_default'])) { @@ -525,7 +531,15 @@ private function convertAttribute(array $column, Collection $collection): Attrib $column['column_default'] = null; } - return new Integer($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default'], -32768, 32767); + return new Integer( + $column['column_name'], + $collection, + required: $column['is_nullable'] === 'NO', + default:$column['column_default'], + array: $isArray, + min: -32768, + max: 32767, + ); case 'integer': case 'int4': if (! is_numeric($column['column_default']) && ! is_null($column['column_default'])) { @@ -541,7 +555,15 @@ private function convertAttribute(array $column, Collection $collection): Attrib $column['column_default'] = null; } - return new Integer($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default'], -2147483648, 2147483647); + return new Integer( + $column['column_name'], + $collection, + required: $column['is_nullable'] === 'NO', + default: $column['column_default'], + array: $isArray, + min: -2147483648, + max: 2147483647, + ); case 'bigint': case 'int8': case 'numeric': @@ -557,7 +579,13 @@ private function convertAttribute(array $column, Collection $collection): Attrib $column['column_default'] = null; } - return new Integer($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default']); + return new Integer( + $column['column_name'], + $collection, + required: $column['is_nullable'] === 'NO', + default: $column['column_default'], + array: $isArray, + ); case 'decimal': case 'real': case 'double precision': @@ -577,7 +605,13 @@ private function convertAttribute(array $column, Collection $collection): Attrib $column['column_default'] = null; } - return new Decimal($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default']); + return new Decimal( + $column['column_name'], + $collection, + required: $column['is_nullable'] === 'NO', + default: $column['column_default'], + array: $isArray, + ); // Time (Conversion happens with documents) case 'timestamp with time zone': case 'date': @@ -588,36 +622,23 @@ private function convertAttribute(array $column, Collection $collection): Attrib case 'time': case 'timetz': case 'interval': - return new DateTime($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, null); - break; - // Strings and Objects - case 'uuid': - case 'character varying': - case 'text': - case 'character': - case 'json': - case 'jsonb': - case 'varchar': - case 'bytea': - return new Text( + return new DateTime( $column['column_name'], $collection, - $column['is_nullable'] === 'NO', - $isArray, - $column['column_default'], - $column['character_maximum_length'] ?? $column['character_octet_length'] ?? 10485760 + required: $column['is_nullable'] === 'NO', + default: null, + array: $isArray, ); - break; default: + // Strings and Objects return new Text( $column['column_name'], $collection, - $column['is_nullable'] === 'NO', - $isArray, - $column['column_default'], - $column['character_maximum_length'] ?? $column['character_octet_length'] ?? 10485760 + required: $column['is_nullable'] === 'NO', + default: $column['column_default'], + array: $isArray, + size: $column['character_maximum_length'] ?? $column['character_octet_length'] ?? 10485760, ); - break; } } @@ -676,9 +697,9 @@ protected function exportGroupStorage(int $batchSize, array $resources) new Exception( Resource::TYPE_BUCKET, Transfer::GROUP_STORAGE, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e ) ); } @@ -692,9 +713,9 @@ protected function exportGroupStorage(int $batchSize, array $resources) new Exception( Resource::TYPE_FILE, Transfer::GROUP_STORAGE, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e ) ); } diff --git a/src/Migration/Sources/Supabase.php b/src/Migration/Sources/Supabase.php index ebc7833..d67f573 100644 --- a/src/Migration/Sources/Supabase.php +++ b/src/Migration/Sources/Supabase.php @@ -356,9 +356,9 @@ protected function exportGroupAuth(int $batchSize, array $resources): void $this->addError(new Exception( Resource::TYPE_BUCKET, Transfer::GROUP_STORAGE, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e )); } } @@ -428,9 +428,9 @@ protected function exportGroupStorage(int $batchSize, array $resources) $this->addError(new Exception( Resource::TYPE_BUCKET, Transfer::GROUP_STORAGE, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e )); } @@ -442,9 +442,9 @@ protected function exportGroupStorage(int $batchSize, array $resources) $this->addError(new Exception( Resource::TYPE_BUCKET, Transfer::GROUP_STORAGE, - $e->getMessage(), - $e->getCode(), - $e + message: $e->getMessage(), + code: $e->getCode(), + previous: $e )); } } diff --git a/src/Migration/Target.php b/src/Migration/Target.php index f432f92..a8165c2 100644 --- a/src/Migration/Target.php +++ b/src/Migration/Target.php @@ -20,18 +20,18 @@ abstract class Target * * @var array */ - public $errors = []; + public array $errors = []; /** * Warnings * * @var array */ - public $warnings = []; + public array $warnings = []; - protected $endpoint = ''; + protected string $endpoint = ''; - protected $rootResourceId = ''; + protected string $rootResourceId = ''; abstract public static function getName(): string; @@ -224,6 +224,5 @@ public function addWarning(Warning $warning): void */ public function shutdown(): void { - } } diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index 9140406..1fa5ca3 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -4,29 +4,47 @@ class Transfer { - public const GROUP_GENERAL = 'general'; + public const string GROUP_GENERAL = 'general'; - public const GROUP_AUTH = 'auth'; + public const string GROUP_AUTH = 'auth'; - public const GROUP_STORAGE = 'storage'; + public const string GROUP_STORAGE = 'storage'; - public const GROUP_FUNCTIONS = 'functions'; + public const string GROUP_FUNCTIONS = 'functions'; - public const GROUP_DATABASES = 'databases'; + public const string GROUP_DATABASES = 'databases'; - public const GROUP_SETTINGS = 'settings'; + public const string GROUP_SETTINGS = 'settings'; - public const GROUP_AUTH_RESOURCES = [Resource::TYPE_USER, Resource::TYPE_TEAM, Resource::TYPE_MEMBERSHIP, Resource::TYPE_HASH]; + public const array GROUP_AUTH_RESOURCES = [ + Resource::TYPE_USER, + Resource::TYPE_TEAM, + Resource::TYPE_MEMBERSHIP, + Resource::TYPE_HASH + ]; - public const GROUP_STORAGE_RESOURCES = [Resource::TYPE_FILE, Resource::TYPE_BUCKET]; + public const array GROUP_STORAGE_RESOURCES = [ + Resource::TYPE_FILE, + Resource::TYPE_BUCKET + ]; - public const GROUP_FUNCTIONS_RESOURCES = [Resource::TYPE_FUNCTION, Resource::TYPE_ENVIRONMENT_VARIABLE, Resource::TYPE_DEPLOYMENT]; + public const array GROUP_FUNCTIONS_RESOURCES = [ + Resource::TYPE_FUNCTION, + Resource::TYPE_ENVIRONMENT_VARIABLE, + Resource::TYPE_DEPLOYMENT + ]; - public const GROUP_DATABASES_RESOURCES = [Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_INDEX, Resource::TYPE_ATTRIBUTE, Resource::TYPE_DOCUMENT]; + public const array GROUP_DATABASES_RESOURCES = [ + Resource::TYPE_DATABASE, + Resource::TYPE_COLLECTION, + Resource::TYPE_INDEX, + Resource::TYPE_ATTRIBUTE, + Resource::TYPE_DOCUMENT + ]; - public const GROUP_SETTINGS_RESOURCES = []; + public const array GROUP_SETTINGS_RESOURCES = []; - public const ALL_PUBLIC_RESOURCES = [ + public const array ALL_PUBLIC_RESOURCES = [ Resource::TYPE_USER, Resource::TYPE_TEAM, Resource::TYPE_MEMBERSHIP, @@ -42,7 +60,7 @@ class Transfer Resource::TYPE_DOCUMENT, ]; - public const ROOT_RESOURCES = [ + public const array ROOT_RESOURCES = [ Resource::TYPE_BUCKET, Resource::TYPE_DATABASE, Resource::TYPE_FUNCTION, @@ -50,7 +68,7 @@ class Transfer Resource::TYPE_TEAM, ]; - public const STORAGE_MAX_CHUNK_SIZE = 1024 * 1024 * 5; // 5MB + public const int STORAGE_MAX_CHUNK_SIZE = 1024 * 1024 * 5; // 5MB protected Source $source; @@ -91,14 +109,7 @@ public function __construct(Source $source, Destination $destination) return $this; } - /** - * @return array> - */ - protected Cache $cache; - - protected array $resources = []; - - public function getStatusCounters() + public function getStatusCounters(): array { $status = []; @@ -123,7 +134,6 @@ public function getStatusCounters() foreach ($this->cache->getAll() as $resources) { foreach ($resources as $resource) { - /** @var resource $resource */ if (isset($status[$resource->getName()])) { $status[$resource->getName()][$resource->getStatus()]++; if ($status[$resource->getName()]['pending'] > 0) { @@ -135,7 +145,6 @@ public function getStatusCounters() // Process Destination Errors foreach ($this->destination->getErrors() as $error) { - /** @var Exception $error */ if (isset($status[$error->getResourceGroup()])) { $status[$error->getResourceGroup()][Resource::STATUS_ERROR]++; } @@ -191,19 +200,15 @@ public function run(array $resources, callable $callback, string $rootResourceId $computedResources = array_map('strtolower', $computedResources); // Check we don't have multiple root resources if rootResourceId is set - - $rootResourceId = $rootResourceId ?? ''; // Convert null to empty string - + $rootResourceId = $rootResourceId ?? ''; if ($rootResourceId) { - $rootResourceCount = count(array_intersect($computedResources, self::ROOT_RESOURCES)); - + $rootResourceCount = \count(\array_intersect($computedResources, self::ROOT_RESOURCES)); if ($rootResourceCount > 1) { throw new \Exception('Multiple root resources found. Only one root resource can be transferred at a time if using $rootResourceId.'); } } $this->resources = $computedResources; - $this->destination->run($computedResources, $callback, $rootResourceId); } diff --git a/tests/Migration/E2E/Sources/NHostTest.php b/tests/Migration/E2E/Sources/NHostTest.php index 003a1b1..83a51a4 100644 --- a/tests/Migration/E2E/Sources/NHostTest.php +++ b/tests/Migration/E2E/Sources/NHostTest.php @@ -4,6 +4,11 @@ use Utopia\Migration\Destination; use Utopia\Migration\Resource; +use Utopia\Migration\Resources\Auth\User; +use Utopia\Migration\Resources\Database\Collection; +use Utopia\Migration\Resources\Database\Database; +use Utopia\Migration\Resources\Storage\Bucket; +use Utopia\Migration\Resources\Storage\File; use Utopia\Migration\Source; use Utopia\Migration\Sources\NHost; use Utopia\Migration\Transfer; @@ -17,6 +22,9 @@ class NHostTest extends Base protected ?Destination $destination = null; + /** + * @throws \Exception + */ protected function setUp(): void { // Check DB is online and ready @@ -28,7 +36,7 @@ protected function setUp(): void $pdo = new \PDO('pgsql:host=nhost-db'.';port=5432;dbname=postgres', 'postgres', 'postgres'); $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); - if ($pdo && $pdo->query('SELECT 1')->fetchColumn() === 1) { + if ($pdo->query('SELECT 1')->fetchColumn() === 1) { break; } else { var_dump('DB was offline, waiting 1s then retrying.'); @@ -51,7 +59,7 @@ protected function setUp(): void $this->call('GET', 'http://nhost-storage/', ['Content-Type' => 'text/plain']); break; - } catch (\Exception $e) { + } catch (\Exception) { } sleep(5); @@ -77,6 +85,9 @@ protected function setUp(): void $this->transfer = new Transfer($this->source, $this->destination); } + /** + * @throws \Exception + */ public function testSourceReport() { // Test report all @@ -91,6 +102,7 @@ public function testSourceReport() /** * @depends testSourceReport + * @throws \Exception */ public function testRunTransfer($state) { @@ -99,7 +111,7 @@ public function testRunTransfer($state) function () {} ); - $this->assertEquals(0, count($this->transfer->getReport('error'))); + $this->assertCount(0, $this->transfer->getReport('error')); return array_merge($state, [ 'transfer' => $this->transfer, @@ -120,8 +132,6 @@ public function testValidateTransfer($state) if ($counters[Resource::STATUS_ERROR] > 0) { $this->fail('Resource '.$resource.' has '.$counters[Resource::STATUS_ERROR].' errors'); - - return; } } @@ -138,7 +148,7 @@ public function testValidateUserTransfer($state): void $foundUser = null; foreach ($users as $user) { - /** @var \Utopia\Migration\Resources\Auth\User $user */ + /** @var User $user */ if ($user->getEmail() === 'test@test.com') { $foundUser = $user; } @@ -148,8 +158,6 @@ public function testValidateUserTransfer($state): void if (! $foundUser) { $this->fail('User "test@test.com" not found'); - - return; } $this->assertEquals('success', $foundUser->getStatus()); @@ -168,8 +176,8 @@ public function testValidateDatabaseTransfer($state) $foundDatabase = null; foreach ($databases as $database) { - /** @var \Utopia\Migration\Resources\Database $database */ - if ($database->getDBName() === 'public') { + /** @var Database $database */ + if ($database->getDatabaseName() === 'public') { $foundDatabase = $database; } @@ -178,12 +186,10 @@ public function testValidateDatabaseTransfer($state) if (! $foundDatabase) { $this->fail('Database "public" not found'); - - return; } $this->assertEquals('success', $foundDatabase->getStatus()); - $this->assertEquals('public', $foundDatabase->getDBName()); + $this->assertEquals('public', $foundDatabase->getDatabaseName()); $this->assertEquals('public', $foundDatabase->getId()); // Find known collection @@ -191,7 +197,7 @@ public function testValidateDatabaseTransfer($state) $foundCollection = null; foreach ($collections as $collection) { - /** @var \Utopia\Migration\Resources\Database\Collection $collection */ + /** @var Collection $collection */ if ($collection->getCollectionName() === 'TestTable') { $foundCollection = $collection; @@ -201,8 +207,6 @@ public function testValidateDatabaseTransfer($state) if (! $foundCollection) { $this->fail('Collection "TestTable" not found'); - - return; } $this->assertEquals('success', $foundCollection->getStatus()); @@ -223,7 +227,7 @@ public function testDatabaseFunctionalDefaultsWarn($state): void $foundCollection = null; foreach ($collections as $collection) { - /** @var \Utopia\Migration\Resources\Database\Collection $collection */ + /** @var Collection $collection */ if ($collection->getCollectionName() === 'FunctionalDefaultTestTable') { $foundCollection = $collection; } @@ -233,8 +237,6 @@ public function testDatabaseFunctionalDefaultsWarn($state): void if (! $foundCollection) { $this->fail('Collection "FunctionalDefaultTestTable" not found'); - - return; } $this->assertEquals('warning', $foundCollection->getStatus()); @@ -253,7 +255,7 @@ public function testValidateStorageTransfer($state): void $foundBucket = null; foreach ($buckets as $bucket) { - /** @var \Utopia\Migration\Resources\Bucket $bucket */ + /** @var Bucket $bucket */ if ($bucket->getId() === 'default') { $foundBucket = $bucket; } @@ -263,8 +265,6 @@ public function testValidateStorageTransfer($state): void if (! $foundBucket) { $this->fail('Bucket "default" not found'); - - return; } $this->assertEquals('success', $foundBucket->getStatus()); @@ -275,7 +275,7 @@ public function testValidateStorageTransfer($state): void $foundFile = null; foreach ($files as $file) { - /** @var \Utopia\Migration\Resources\File $file */ + /** @var File $file */ if ($file->getFileName() === 'tulips.png') { $foundFile = $file; } @@ -285,10 +285,8 @@ public function testValidateStorageTransfer($state): void if (! $foundFile) { $this->fail('File "tulips.png" not found'); - - return; } - /** @var \Utopia\Migration\Resources\Storage\File $foundFile */ + /** @var File $foundFile */ $this->assertEquals('success', $foundFile->getStatus()); $this->assertEquals('tulips.png', $foundFile->getFileName()); $this->assertEquals('default', $foundFile->getBucket()->getId()); diff --git a/tests/Migration/E2E/Sources/SupabaseTest.php b/tests/Migration/E2E/Sources/SupabaseTest.php index e640c5d..b397f5c 100644 --- a/tests/Migration/E2E/Sources/SupabaseTest.php +++ b/tests/Migration/E2E/Sources/SupabaseTest.php @@ -4,6 +4,12 @@ use Utopia\Migration\Destination; use Utopia\Migration\Resource; +use Utopia\Migration\Resources\Auth\User; +use Utopia\Migration\Resources\Database\Collection; +use Utopia\Migration\Resources\Database\Database; +use Utopia\Migration\Resources\Database\Document; +use Utopia\Migration\Resources\Storage\Bucket; +use Utopia\Migration\Resources\Storage\File; use Utopia\Migration\Source; use Utopia\Migration\Sources\Supabase; use Utopia\Migration\Transfer; @@ -17,6 +23,9 @@ class SupabaseTest extends Base protected ?Destination $destination = null; + /** + * @throws \Exception + */ protected function setUp(): void { // Check DB is online and ready @@ -28,12 +37,12 @@ protected function setUp(): void $pdo = new \PDO('pgsql:host=supabase-db'.';port=5432;dbname=postgres', 'postgres', 'postgres'); $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); - if ($pdo && $pdo->query('SELECT 1')->fetchColumn() === 1) { + if ($pdo->query('SELECT 1')->fetchColumn() === 1) { break; } else { var_dump('DB was offline, waiting 1s then retrying.'); } - } catch (\PDOException $e) { + } catch (\PDOException) { } sleep(1); @@ -57,6 +66,9 @@ protected function setUp(): void $this->transfer = new Transfer($this->source, $this->destination); } + /** + * @throws \Exception + */ public function testSourceReport() { // Test report all @@ -71,6 +83,7 @@ public function testSourceReport() /** * @depends testSourceReport + * @throws \Exception */ public function testRunTransfer($state) { @@ -79,7 +92,7 @@ public function testRunTransfer($state) function () {} ); - $this->assertEquals(0, count($this->transfer->getReport('error'))); + $this->assertCount(0, $this->transfer->getReport('error')); return array_merge($state, [ 'transfer' => $this->transfer, @@ -100,8 +113,6 @@ public function testValidateTransfer($state) if ($counters[Resource::STATUS_ERROR] > 0) { $this->fail('Resource '.$resource.' has '.$counters[Resource::STATUS_ERROR].' errors'); - - return; } } @@ -119,7 +130,7 @@ public function testValidateUserTransfer($state): void $foundUser = null; foreach ($users as $user) { - /** @var \Utopia\Migration\Resources\Auth\User $user */ + /** @var User $user */ if ($user->getEmail() == 'albert.kihn95@yahoo.com') { $foundUser = $user; @@ -129,8 +140,6 @@ public function testValidateUserTransfer($state): void if (! $foundUser) { $this->fail('User "albert.kihn95@yahoo.com" not found'); - - return; } $this->assertEquals('success', $foundUser->getStatus()); @@ -149,8 +158,8 @@ public function testValidateDatabaseTransfer($state) $foundDatabase = null; foreach ($databases as $database) { - /** @var \Utopia\Migration\Resources\Database $database */ - if ($database->getDBName() === 'public') { + /** @var Database $database */ + if ($database->getDatabaseName() === 'public') { $foundDatabase = $database; break; @@ -159,12 +168,10 @@ public function testValidateDatabaseTransfer($state) if (! $foundDatabase) { $this->fail('Database "public" not found'); - - return; } $this->assertEquals('success', $foundDatabase->getStatus()); - $this->assertEquals('public', $foundDatabase->getDBName()); + $this->assertEquals('public', $foundDatabase->getDatabaseName()); $this->assertEquals('public', $foundDatabase->getId()); // Find Known Collections @@ -174,8 +181,8 @@ public function testValidateDatabaseTransfer($state) $foundCollection = null; foreach ($collections as $collection) { - /** @var \Utopia\Migration\Resources\Database\Collection $collection */ - if ($collection->getDatabase()->getDBName() === 'public' && $collection->getCollectionName() === 'test') { + /** @var Collection $collection */ + if ($collection->getDatabase()->getDatabaseName() === 'public' && $collection->getCollectionName() === 'test') { $foundCollection = $collection; break; @@ -184,13 +191,11 @@ public function testValidateDatabaseTransfer($state) if (! $foundCollection) { $this->fail('Collection "test" not found'); - - return; } $this->assertEquals('success', $foundCollection->getStatus()); $this->assertEquals('test', $foundCollection->getCollectionName()); - $this->assertEquals('public', $foundCollection->getDatabase()->getDBName()); + $this->assertEquals('public', $foundCollection->getDatabase()->getDatabaseName()); $this->assertEquals('public', $foundCollection->getDatabase()->getId()); // Find Known Documents @@ -200,8 +205,8 @@ public function testValidateDatabaseTransfer($state) $foundDocument = null; foreach ($documents as $document) { - /** @var \Utopia\Migration\Resources\Database\Document $document */ - if ($document->getCollection()->getDatabase()->getDBName() === 'public' && $document->getCollection()->getCollectionName() === 'test') { + /** @var Document $document */ + if ($document->getCollection()->getDatabase()->getDatabaseName() === 'public' && $document->getCollection()->getCollectionName() === 'test') { $foundDocument = $document; } @@ -210,8 +215,6 @@ public function testValidateDatabaseTransfer($state) if (! $foundDocument) { $this->fail('Document "1" not found'); - - return; } $this->assertEquals('success', $foundDocument->getStatus()); @@ -229,7 +232,7 @@ public function testDatabaseFunctionalDefaultsWarn($state): void $foundCollection = null; foreach ($collections as $collection) { - /** @var \Utopia\Migration\Resources\Database\Collection $collection */ + /** @var Collection $collection */ if ($collection->getCollectionName() === 'FunctionalDefaultTestTable') { $foundCollection = $collection; } @@ -239,8 +242,6 @@ public function testDatabaseFunctionalDefaultsWarn($state): void if (! $foundCollection) { $this->fail('Collection "FunctionalDefaultTestTable" not found'); - - return; } $this->assertEquals('warning', $foundCollection->getStatus()); @@ -261,7 +262,7 @@ public function testValidateStorageTransfer($state): void $foundBucket = null; foreach ($buckets as $bucket) { - /** @var \Utopia\Migration\Resources\Storage\Bucket $bucket */ + /** @var Bucket $bucket */ if ($bucket->getBucketName() === 'Test Bucket 1') { $foundBucket = $bucket; } @@ -271,8 +272,6 @@ public function testValidateStorageTransfer($state): void if (! $foundBucket) { $this->fail('Bucket "Test Bucket 1" not found'); - - return; } $this->assertEquals('success', $foundBucket->getStatus()); @@ -284,7 +283,7 @@ public function testValidateStorageTransfer($state): void $foundFile = null; foreach ($files as $file) { - /** @var \Utopia\Migration\Resources\File $file */ + /** @var File $file */ if ($file->getFileName() === 'tulips.png') { $foundFile = $file; } @@ -294,10 +293,8 @@ public function testValidateStorageTransfer($state): void if (! $foundFile) { $this->fail('File "tulips.png" not found'); - - return; } - /** @var \Utopia\Migration\Resources\Storage\File $foundFile */ + /** @var File $foundFile */ $this->assertEquals('success', $foundFile->getStatus()); $this->assertEquals('tulips.png', $foundFile->getFileName()); $this->assertEquals('image/png', $foundFile->getMimeType()); diff --git a/tests/Migration/unit/General/TransferTest.php b/tests/Migration/Unit/General/TransferTest.php similarity index 96% rename from tests/Migration/unit/General/TransferTest.php rename to tests/Migration/Unit/General/TransferTest.php index 26baf71..54d2f00 100644 --- a/tests/Migration/unit/General/TransferTest.php +++ b/tests/Migration/Unit/General/TransferTest.php @@ -51,7 +51,7 @@ public function testRootResourceId(): void $database = $this->destination->getResourceById(Transfer::GROUP_DATABASES, Resource::TYPE_DATABASE, 'test'); /** @var Database $database */ $this->assertNotNull($database); - $this->assertEquals('test', $database->getDBName()); + $this->assertEquals('test', $database->getDatabaseName()); $this->assertEquals('test', $database->getId()); } } From 1a3eaa563572a07ac8b799f1b4cb5e8bbfe08bad Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 25 Jul 2024 14:49:42 +1200 Subject: [PATCH 114/185] Fix array check --- src/Migration/Sources/NHost.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Migration/Sources/NHost.php b/src/Migration/Sources/NHost.php index 6aa4723..49d4d4a 100644 --- a/src/Migration/Sources/NHost.php +++ b/src/Migration/Sources/NHost.php @@ -487,7 +487,7 @@ private function exportDocuments(int $batchSize): void $processedData = []; foreach ($collectionAttributes as $attribute) { /** @var Attribute $attribute */ - if (! $attribute->getArray() && \is_array($data[$attribute->getKey()])) { + if (! $attribute->isArray() && \is_array($data[$attribute->getKey()])) { $processedData[$attribute->getKey()] = json_encode($data[$attribute->getKey()]); } else { $processedData[$attribute->getKey()] = $data[$attribute->getKey()]; From c47a3e7958d5aa0c098a711ec2a12127360f6e3f Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 25 Jul 2024 14:49:55 +1200 Subject: [PATCH 115/185] Pretty print errors --- src/Migration/Exception.php | 14 +++++++++++++- tests/Migration/E2E/Sources/NHostTest.php | 4 ++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Migration/Exception.php b/src/Migration/Exception.php index e42f890..67b02c1 100644 --- a/src/Migration/Exception.php +++ b/src/Migration/Exception.php @@ -2,7 +2,7 @@ namespace Utopia\Migration; -class Exception extends \Exception +class Exception extends \Exception implements \JsonSerializable { public string $resourceName; @@ -39,4 +39,16 @@ public function getResourceId(): string { return $this->resourceId; } + public function jsonSerialize(): array + { + return [ + 'code' => $this->getCode(), + 'message' => $this->getMessage(), + 'resourceName' => $this->resourceName, + 'resourceGroup' => $this->resourceGroup, + 'resourceId' => $this->resourceId, + 'trace' => $this->getTrace() + ]; + } + } diff --git a/tests/Migration/E2E/Sources/NHostTest.php b/tests/Migration/E2E/Sources/NHostTest.php index 7f5bba8..b0d153b 100644 --- a/tests/Migration/E2E/Sources/NHostTest.php +++ b/tests/Migration/E2E/Sources/NHostTest.php @@ -136,7 +136,7 @@ public function testValidateSourceErrors($state) $errors = $source->getErrors(); if (!empty($errors)) { - $this->fail('[Source] Failed: ' . \json_encode($errors)); + $this->fail('[Source] Failed: ' . \json_encode($errors, JSON_PRETTY_PRINT)); } return $state; @@ -157,7 +157,7 @@ public function testValidateDestinationErrors($state) $errors = $destination->getErrors(); if (!empty($errors)) { - $this->fail('[Destination] Failed: ' . \json_encode($errors)); + $this->fail('[Destination] Failed: ' . \json_encode($errors, JSON_PRETTY_PRINT)); } return $state; From 3668d07abf45d1356f9dacc43a19283c055f0c57 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 25 Jul 2024 14:50:15 +1200 Subject: [PATCH 116/185] Fix phpunit deprecation warnings --- tests/Migration/E2E/Sources/SupabaseTest.php | 31 +++++++------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/tests/Migration/E2E/Sources/SupabaseTest.php b/tests/Migration/E2E/Sources/SupabaseTest.php index 472d623..53096a5 100644 --- a/tests/Migration/E2E/Sources/SupabaseTest.php +++ b/tests/Migration/E2E/Sources/SupabaseTest.php @@ -2,6 +2,7 @@ namespace Utopia\Tests\E2E\Sources; +use PHPUnit\Framework\Attributes\Depends; use Utopia\Migration\Destination; use Utopia\Migration\Resource; use Utopia\Migration\Resources\Auth\User; @@ -82,9 +83,9 @@ public function testSourceReport() } /** - * @depends testSourceReport * @throws \Exception */ + #[Depends('testSourceReport')] public function testRunTransfer($state) { $this->transfer->run( @@ -101,9 +102,7 @@ function () {} ]); } - /** - * @depends testRunTransfer - */ + #[Depends('testRunTransfer')] public function testValidateSourceErrors($state) { /** @var Transfer $transfer */ @@ -118,15 +117,13 @@ public function testValidateSourceErrors($state) $errors = $source->getErrors(); if (!empty($errors)) { - $this->fail('[Source] Failed: ' . \json_encode($errors)); + $this->fail('[Source] Failed: ' . \json_encode($errors, JSON_PRETTY_PRINT)); } return $state; } - /** - * @depends testValidateSourceErrors - */ + #[Depends('testValidateSourceErrors')] public function testValidateDestinationErrors($state) { /** @var Transfer $transfer */ @@ -141,15 +138,13 @@ public function testValidateDestinationErrors($state) $errors = $destination->getErrors(); if (!empty($errors)) { - $this->fail('[Destination] Failed: ' . \json_encode($errors)); + $this->fail('[Destination] Failed: ' . \json_encode($errors, JSON_PRETTY_PRINT)); } return $state; } - /** - * @depends testValidateDestinationErrors - */ + #[Depends('testValidateDestinationErrors')] public function testValidateUserTransfer($state): void { // Find known user @@ -175,9 +170,7 @@ public function testValidateUserTransfer($state): void $this->assertEquals('bcrypt', $foundUser->getPasswordHash()->getAlgorithm()); } - /** - * @depends testValidateDestinationErrors - */ + #[Depends('testValidateDestinationErrors')] public function testValidateDatabaseTransfer($state) { // Find known database @@ -250,9 +243,7 @@ public function testValidateDatabaseTransfer($state) return $state; } - /** - * @depends testValidateDatabaseTransfer - */ + #[Depends('testValidateDatabaseTransfer')] public function testDatabaseFunctionalDefaultsWarn($state): void { // Find known collection @@ -278,9 +269,7 @@ public function testDatabaseFunctionalDefaultsWarn($state): void $this->assertEquals('public', $foundCollection->getDatabase()->getId()); } - /** - * @depends testValidateDestinationErrors - */ + #[Depends('testValidateDestinationErrors')] public function testValidateStorageTransfer($state): void { // Find known bucket From 2b26610232a2d2f6b251d5906d585286498c2f51 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 25 Jul 2024 17:25:50 +1200 Subject: [PATCH 117/185] Pass collection structure only instead of all --- src/Migration/Destinations/Appwrite.php | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 7cad27c..b032a0a 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -68,14 +68,14 @@ class Appwrite extends Destination * @param string $endpoint * @param string $key * @param UtopiaDatabase $database - * @param array>> $collectionConfig + * @param array> $collectionStructure */ public function __construct( string $project, string $endpoint, string $key, protected UtopiaDatabase $database, - protected array $collectionConfig + protected array $collectionStructure ) { $this->project = $project; $this->endpoint = $endpoint; @@ -331,24 +331,13 @@ protected function createDatabase(Database $resource): bool $resource->setInternalId($database->getInternalId()); - /** - * @var array{ - * "$collection": string, - * "$id": string, - * name: string, - * attributes: array>, - * indexes: array> - * } $collections - */ - $collections = $this->collectionConfig['databases']['collections']; - $attributes = \array_map( fn ($attr) => new UtopiaDocument($attr), - $collections['attributes'] + $this->collectionStructure['attributes'] ); $indexes = \array_map( fn ($index) => new UtopiaDocument($index), - $collections['indexes'] + $this->collectionStructure['indexes'] ); $this->database->createCollection( From 27459208393d4b7fdda41904fb4301004ae90c53 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 25 Jul 2024 17:25:57 +1200 Subject: [PATCH 118/185] Fix imports --- src/Migration/Destinations/Appwrite.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index b032a0a..04cbc98 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -6,7 +6,6 @@ use Appwrite\Client; use Appwrite\Enums\Compression; use Appwrite\Enums\PasswordHash; -use Appwrite\Enums\PasswordHash; use Appwrite\Enums\Runtime; use Appwrite\InputFile; use Appwrite\Services\Functions; From 8841ae3dfcc8aa12dfceee21c597a514fdccf433 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 25 Jul 2024 17:27:04 +1200 Subject: [PATCH 119/185] Update test CLI to pass db + structure to Appwrite destination --- bin/MigrationCLI.php | 127 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 115 insertions(+), 12 deletions(-) diff --git a/bin/MigrationCLI.php b/bin/MigrationCLI.php index 56bd0be..11a29f0 100644 --- a/bin/MigrationCLI.php +++ b/bin/MigrationCLI.php @@ -1,8 +1,12 @@ 'databases', + '$id' => 'collections', + 'name' => 'Collections', + 'attributes' => [ + [ + '$id' => 'databaseInternalId', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'databaseId', + 'type' => Database::VAR_STRING, + 'signed' => true, + 'size' => Database::LENGTH_KEY, + 'format' => '', + 'filters' => [], + 'required' => true, + 'default' => null, + 'array' => false, + ], + [ + '$id' => 'name', + 'type' => Database::VAR_STRING, + 'size' => 256, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'enabled', + 'type' => Database::VAR_BOOLEAN, + 'signed' => true, + 'size' => 0, + 'format' => '', + 'filters' => [], + 'required' => true, + 'default' => null, + 'array' => false, + ], + [ + '$id' => 'documentSecurity', + 'type' => Database::VAR_BOOLEAN, + 'signed' => true, + 'size' => 0, + 'format' => '', + 'filters' => [], + 'required' => true, + 'default' => null, + 'array' => false, + ], + [ + '$id' => 'attributes', + 'type' => Database::VAR_STRING, + 'size' => 1000000, + 'required' => false, + 'signed' => true, + 'array' => false, + 'filters' => ['subQueryAttributes'], + ], + [ + '$id' => 'indexes', + 'type' => Database::VAR_STRING, + 'size' => 1000000, + 'required' => false, + 'signed' => true, + 'array' => false, + 'filters' => ['subQueryIndexes'], + ], + [ + '$id' => 'search', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + ] + ]; + /** * Prints the current status of migrations as a table after wiping the screen */ public function drawFrame(): void { - echo chr(27).chr(91).'H'.chr(27).chr(91).'J'; + echo chr(27) . chr(91) . 'H' . chr(27) . chr(91) . 'J'; $statusCounters = $this->transfer->getStatusCounters(); @@ -42,39 +136,39 @@ public function drawFrame(): void // Render Errors $destErrors = $this->destination->getErrors(); - if (! empty($destErrors)) { + if (!empty($destErrors)) { echo "\n\nDestination Errors:\n"; foreach ($destErrors as $error) { /** @var Utopia\Migration\Exception $error */ - echo $error->getResourceName().'['.$error->getResourceId().'] - '.$error->getMessage()."\n"; + echo $error->getResourceName() . '[' . $error->getResourceId() . '] - ' . $error->getMessage() . "\n"; } } $sourceErrors = $this->source->getErrors(); - if (! empty($sourceErrors)) { + if (!empty($sourceErrors)) { echo "\n\nSource Errors:\n"; foreach ($sourceErrors as $error) { /** @var Utopia\Migration\Exception $error */ - echo $error->getResourceGroup().'['.$error->getResourceId().'] - '.$error->getMessage()."\n"; + echo $error->getResourceGroup() . '[' . $error->getResourceId() . '] - ' . $error->getMessage() . "\n"; } } // Render Warnings $sourceWarnings = $this->source->getWarnings(); - if (! empty($sourceWarnings)) { + if (!empty($sourceWarnings)) { echo "\n\nSource Warnings:\n"; foreach ($sourceWarnings as $warning) { /** @var Utopia\Migration\Warning $warning */ - echo $warning->getResourceName().'['.$warning->getResourceId().'] - '.$warning->getMessage()."\n"; + echo $warning->getResourceName() . '[' . $warning->getResourceId() . '] - ' . $warning->getMessage() . "\n"; } } $destWarnings = $this->destination->getWarnings(); - if (! empty($destWarnings)) { + if (!empty($destWarnings)) { echo "\n\nDestination Warnings:\n"; foreach ($destWarnings as $warning) { /** @var Utopia\Migration\Warning $warning */ - echo $warning->getResourceName().'['.$warning->getResourceId().'] - '.$warning->getMessage()."\n"; + echo $warning->getResourceName() . '[' . $warning->getResourceId() . '] - ' . $warning->getMessage() . "\n"; } } } @@ -99,7 +193,7 @@ public function getSource(): Source ); case 'firebase': return new Firebase( - json_decode(file_get_contents(__DIR__.'/serviceAccount.json'), true) + json_decode(file_get_contents(__DIR__ . '/serviceAccount.json'), true) ); case 'nhost': return new NHost( @@ -122,7 +216,16 @@ public function getDestination(): Destination return new DestinationsAppwrite( $_ENV['DESTINATION_APPWRITE_TEST_PROJECT'], $_ENV['DESTINATION_APPWRITE_TEST_ENDPOINT'], - $_ENV['DESTINATION_APPWRITE_TEST_KEY'] + $_ENV['DESTINATION_APPWRITE_TEST_KEY'], + new Database( + new MariaDB(new PDO( + $_ENV['DESTINATION_APPWRITE_TEST_DSN'], + $_ENV['DESTINATION_APPWRITE_TEST_USER'], + $_ENV['DESTINATION_APPWRITE_TEST_PASSWORD'] + )), + new Cache(new None()) + ), + $this->structure ); case 'local': return new Local('./localBackup'); From 589cdf11d635de4dc9cd297827724ea5f7ef91de Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 25 Jul 2024 23:25:27 +1200 Subject: [PATCH 120/185] Remove redundant file --- phpcs.xml | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 phpcs.xml diff --git a/phpcs.xml b/phpcs.xml deleted file mode 100644 index 89c508c..0000000 --- a/phpcs.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - ./src - ./tests - - - - * - - - - * - - \ No newline at end of file From c87c834590df4672805dd3306a34c3fccfda6456 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 25 Jul 2024 23:25:41 +1200 Subject: [PATCH 121/185] Remove var dumps --- src/Migration/Destinations/Appwrite.php | 11 ----------- src/Migration/Source.php | 1 - src/Migration/Transfer.php | 1 - 3 files changed, 13 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 04cbc98..79533ad 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -212,8 +212,6 @@ public function report(array $resources = []): array #[Override] protected function import(array $resources, callable $callback): void { - var_dump("Destinations/Appwrite import...."); - if (empty($resources)) { return; } @@ -221,8 +219,6 @@ protected function import(array $resources, callable $callback): void $total = \count($resources); foreach ($resources as $index => $resource) { - var_dump("Importing......." . $resource->getGroup() . ' - ' . $resource->getName()); - $resource->setStatus(Resource::STATUS_PROCESSING); $isLast = $index === $total - 1; @@ -236,13 +232,6 @@ protected function import(array $resources, callable $callback): void default => throw new \Exception('Invalid resource group'), }; } catch (\Throwable $e) { - - var_dump("Appwrite import Throwable ==== "); - var_dump("getCode ==== "); - var_dump($e->getCode()); - var_dump("getMessage ==== "); - var_dump($e->getMessage()); - if ($e->getCode() === 409) { // DATABASE_ALREADY_EXISTS why SKIP? not termination $resource->setStatus(Resource::STATUS_SKIPPED, $e->getMessage()); diff --git a/src/Migration/Source.php b/src/Migration/Source.php index 6a8f529..8d072cf 100644 --- a/src/Migration/Source.php +++ b/src/Migration/Source.php @@ -36,7 +36,6 @@ public function run(array $resources, callable $callback, string $rootResourceId foreach ($returnedResources as $resource) { /** @var Resource $resource */ if (! in_array($resource->getName(), $resources)) { - var_dump('setStatus === ' . Resource::STATUS_SKIPPED); $resource->setStatus(Resource::STATUS_SKIPPED); } else { $prunedResources[] = $resource; diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index a55dea0..3b4ed0c 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -265,7 +265,6 @@ public static function extractServices(array $services): array { $resources = []; foreach ($services as $service) { - var_dump('converting resource === '.$service); $resources = match ($service) { self::GROUP_FUNCTIONS => array_merge($resources, self::GROUP_FUNCTIONS_RESOURCES), self::GROUP_STORAGE => array_merge($resources, self::GROUP_STORAGE_RESOURCES), From da27599b50448186ab9c1b1be4e12f4aac824b3b Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 25 Jul 2024 23:25:52 +1200 Subject: [PATCH 122/185] Print with previous as trace --- src/Migration/Exception.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Migration/Exception.php b/src/Migration/Exception.php index 67b02c1..2dc2817 100644 --- a/src/Migration/Exception.php +++ b/src/Migration/Exception.php @@ -47,7 +47,7 @@ public function jsonSerialize(): array 'resourceName' => $this->resourceName, 'resourceGroup' => $this->resourceGroup, 'resourceId' => $this->resourceId, - 'trace' => $this->getTrace() + 'trace' => $this->getPrevious()->getTrace(), ]; } From b55d93836c5bb4c672a462be3fd9aee25daafcb7 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Jul 2024 00:01:43 +1200 Subject: [PATCH 123/185] Add indexes to structure --- bin/MigrationCLI.php | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/bin/MigrationCLI.php b/bin/MigrationCLI.php index 11a29f0..690a5b9 100644 --- a/bin/MigrationCLI.php +++ b/bin/MigrationCLI.php @@ -115,7 +115,37 @@ class MigrationCLI 'array' => false, 'filters' => [], ], - ] + ], + 'indexes' => [ + [ + '$id' => '_fulltext_search', + 'type' => Database::INDEX_FULLTEXT, + 'attributes' => ['search'], + 'lengths' => [], + 'orders' => [], + ], + [ + '$id' => '_key_name', + 'type' => Database::INDEX_KEY, + 'attributes' => ['name'], + 'lengths' => [256], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => '_key_enabled', + 'type' => Database::INDEX_KEY, + 'attributes' => ['enabled'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => '_key_documentSecurity', + 'type' => Database::INDEX_KEY, + 'attributes' => ['documentSecurity'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + ], ]; /** From 320d8a571d1a22790ae883a127530e8dd3804f1e Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Jul 2024 00:01:59 +1200 Subject: [PATCH 124/185] Use const array for structure --- bin/MigrationCLI.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/MigrationCLI.php b/bin/MigrationCLI.php index 690a5b9..9d5976b 100644 --- a/bin/MigrationCLI.php +++ b/bin/MigrationCLI.php @@ -28,7 +28,7 @@ class MigrationCLI protected mixed $destination; - protected array $structure = [ + protected const array STRUCTURE = [ '$collection' => 'databases', '$id' => 'collections', 'name' => 'Collections', @@ -255,7 +255,7 @@ public function getDestination(): Destination )), new Cache(new None()) ), - $this->structure + self::STRUCTURE ); case 'local': return new Local('./localBackup'); From 325af1932597cdeb1564156aaad9d25c0bbda576 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Jul 2024 00:02:28 +1200 Subject: [PATCH 125/185] Init db method --- bin/MigrationCLI.php | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/bin/MigrationCLI.php b/bin/MigrationCLI.php index 9d5976b..ef57050 100644 --- a/bin/MigrationCLI.php +++ b/bin/MigrationCLI.php @@ -247,14 +247,7 @@ public function getDestination(): Destination $_ENV['DESTINATION_APPWRITE_TEST_PROJECT'], $_ENV['DESTINATION_APPWRITE_TEST_ENDPOINT'], $_ENV['DESTINATION_APPWRITE_TEST_KEY'], - new Database( - new MariaDB(new PDO( - $_ENV['DESTINATION_APPWRITE_TEST_DSN'], - $_ENV['DESTINATION_APPWRITE_TEST_USER'], - $_ENV['DESTINATION_APPWRITE_TEST_PASSWORD'] - )), - new Cache(new None()) - ), + $this->getDatabase(), self::STRUCTURE ); case 'local': @@ -264,6 +257,25 @@ public function getDestination(): Destination } } + public function getDatabase(): Database + { + + $database = new Database( + new MariaDB(new PDO( + $_ENV['DESTINATION_APPWRITE_TEST_DSN'], + $_ENV['DESTINATION_APPWRITE_TEST_USER'], + $_ENV['DESTINATION_APPWRITE_TEST_PASSWORD'] + )), + new Cache(new None()) + ); + + $database + ->setDatabase('appwrite') + ->setNamespace('_' . $_ENV['DESTINATION_APPWRITE_TEST_NAMESPACE']); + + return $database; + } + public function start(): void { $dotenv = Dotenv::createImmutable(__DIR__); From d451071527cf3652da543abf5929f221da38ad70 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Jul 2024 00:02:44 +1200 Subject: [PATCH 126/185] Add required subQueries --- bin/MigrationCLI.php | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/bin/MigrationCLI.php b/bin/MigrationCLI.php index ef57050..f94b86e 100644 --- a/bin/MigrationCLI.php +++ b/bin/MigrationCLI.php @@ -259,6 +259,46 @@ public function getDestination(): Destination public function getDatabase(): Database { + Database::addFilter( + 'subQueryAttributes', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + $attributes = $database->find('attributes', [ + Query::equal('collectionInternalId', [$document->getInternalId()]), + Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]), + Query::limit($database->getLimitForAttributes()), + ]); + + foreach ($attributes as $attribute) { + if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) { + $options = $attribute->getAttribute('options'); + foreach ($options as $key => $value) { + $attribute->setAttribute($key, $value); + } + $attribute->removeAttribute('options'); + } + } + + return $attributes; + } + ); + + Database::addFilter( + 'subQueryIndexes', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database + ->find('indexes', [ + Query::equal('collectionInternalId', [$document->getInternalId()]), + Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]), + Query::limit($database->getLimitForIndexes()), + ]); + } + ); $database = new Database( new MariaDB(new PDO( From bf80258d04d4b88b779b58c716f47b91adb35734 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Jul 2024 00:02:52 +1200 Subject: [PATCH 127/185] Add required filters --- bin/MigrationCLI.php | 58 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/bin/MigrationCLI.php b/bin/MigrationCLI.php index f94b86e..7f01e6d 100644 --- a/bin/MigrationCLI.php +++ b/bin/MigrationCLI.php @@ -300,6 +300,64 @@ function (mixed $value, Document $document, Database $database) { } ); + Database::addFilter( + 'casting', + function (mixed $value) { + return json_encode(['value' => $value], JSON_PRESERVE_ZERO_FRACTION); + }, + function (mixed $value) { + if (is_null($value)) { + return; + } + + return json_decode($value, true)['value']; + } + ); + + Database::addFilter( + 'enum', + function (mixed $value, Document $attribute) { + if ($attribute->isSet('elements')) { + $attribute->removeAttribute('elements'); + } + + return $value; + }, + function (mixed $value, Document $attribute) { + $formatOptions = \json_decode($attribute->getAttribute('formatOptions', '[]'), true); + if (isset($formatOptions['elements'])) { + $attribute->setAttribute('elements', $formatOptions['elements']); + } + + return $value; + } + ); + + Database::addFilter( + 'range', + function (mixed $value, Document $attribute) { + if ($attribute->isSet('min')) { + $attribute->removeAttribute('min'); + } + if ($attribute->isSet('max')) { + $attribute->removeAttribute('max'); + } + + return $value; + }, + function (mixed $value, Document $attribute) { + $formatOptions = json_decode($attribute->getAttribute('formatOptions', '[]'), true); + if (isset($formatOptions['min']) || isset($formatOptions['max'])) { + $attribute + ->setAttribute('min', $formatOptions['min']) + ->setAttribute('max', $formatOptions['max']) + ; + } + + return $value; + } + ); + $database = new Database( new MariaDB(new PDO( $_ENV['DESTINATION_APPWRITE_TEST_DSN'], From 5315709c53de146cee4777f85068e8a36d9c7876 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Jul 2024 00:03:38 +1200 Subject: [PATCH 128/185] Skip auth for CLI test --- bin/MigrationCLI.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/bin/MigrationCLI.php b/bin/MigrationCLI.php index 7f01e6d..a2595ab 100644 --- a/bin/MigrationCLI.php +++ b/bin/MigrationCLI.php @@ -399,12 +399,17 @@ public function start(): void /** * Run Transfer */ - $this->transfer->run( - $this->source->getSupportedResources(), - function (array $resources) { + Authorization::skip(fn() => $this->transfer->run( + [ + \Utopia\Migration\Resources\Database\Database::getName(), + \Utopia\Migration\Resources\Database\Collection::getName(), + \Utopia\Migration\Resources\Database\Attribute::getName(), +// \Utopia\Migration\Resources\Database\Index::getName(), + ], + function () { $this->drawFrame(); } - ); + )); } } From 3e82be832a11c6d988761dbb823693852228a275 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 25 Jul 2024 16:20:42 +0300 Subject: [PATCH 129/185] getResourceId fix for null --- src/Migration/Exception.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Migration/Exception.php b/src/Migration/Exception.php index 67b02c1..7ae244e 100644 --- a/src/Migration/Exception.php +++ b/src/Migration/Exception.php @@ -13,10 +13,10 @@ class Exception extends \Exception implements \JsonSerializable public function __construct( string $resourceName, string $resourceGroup, - ?string $resourceId = null, + string $resourceId = null, string $message = '', int $code = 0, - ?\Throwable $previous = null, + \Throwable $previous = null, ) { $this->resourceName = $resourceName; $this->resourceId = $resourceId; @@ -37,8 +37,9 @@ public function getResourceGroup(): string public function getResourceId(): string { - return $this->resourceId; + return $this->resourceId ?? ''; } + public function jsonSerialize(): array { return [ @@ -47,8 +48,7 @@ public function jsonSerialize(): array 'resourceName' => $this->resourceName, 'resourceGroup' => $this->resourceGroup, 'resourceId' => $this->resourceId, - 'trace' => $this->getTrace() + 'trace' => $this->getTrace(), ]; } - } From ab46162d2b1ac90111490f01a07da25bd2325cac Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Jul 2024 01:31:51 +1200 Subject: [PATCH 130/185] Fix property order --- bin/MigrationCLI.php | 2 +- src/Migration/Resources/Database/Collection.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/MigrationCLI.php b/bin/MigrationCLI.php index a2595ab..80ae278 100644 --- a/bin/MigrationCLI.php +++ b/bin/MigrationCLI.php @@ -399,7 +399,7 @@ public function start(): void /** * Run Transfer */ - Authorization::skip(fn() => $this->transfer->run( + Authorization::skip(fn () => $this->transfer->run( [ \Utopia\Migration\Resources\Database\Database::getName(), \Utopia\Migration\Resources\Database\Collection::getName(), diff --git a/src/Migration/Resources/Database/Collection.php b/src/Migration/Resources/Database/Collection.php index 1911603..fcf79e2 100644 --- a/src/Migration/Resources/Database/Collection.php +++ b/src/Migration/Resources/Database/Collection.php @@ -16,8 +16,8 @@ class Collection extends Resource */ public function __construct( private readonly Database $database, - string $id, private readonly string $name, + string $id, private readonly bool $documentSecurity = false, array $permissions = [], ) { From ff6f85e84ef7fba594275004588af9b65644bf23 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Jul 2024 01:32:19 +1200 Subject: [PATCH 131/185] Fix two way attribute default status --- src/Migration/Destinations/Appwrite.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 79533ad..4b13ad0 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -470,7 +470,7 @@ protected function createAttribute(Attribute $resource): bool 'collectionInternalId' => $collection->getInternalId(), 'collectionId' => $collection->getId(), 'type' => $resource->getType(), - 'status' => 'available', // processing, available, failed, deleting, stuck + 'status' => 'available', 'size' => $resource->getSize(), 'required' => $resource->isRequired(), 'signed' => $resource->isSigned(), @@ -522,7 +522,7 @@ protected function createAttribute(Attribute $resource): bool 'collectionInternalId' => $relatedCollection->getInternalId(), 'collectionId' => $relatedCollection->getId(), 'type' => $resource->getType(), - 'status' => 'processing', // processing, available, failed, deleting, stuck + 'status' => 'available', 'size' => $resource->getSize(), 'required' => $resource->isRequired(), 'signed' => $resource->isSigned(), @@ -582,11 +582,6 @@ protected function createAttribute(Attribute $resource): bool message: 'Failed to create relationship', ); } - - if (isset($relatedCollection) && $options['twoWay']) { - $relatedAttribute = $this->database->getDocument('attributes', $database->getInternalId() . '_' . $relatedCollection->getInternalId() . '_' . $options['twoWayKey']); - $this->database->updateDocument('attributes', $relatedAttribute->getId(), $relatedAttribute->setAttribute('status', 'available')); - } break; default: if (!$this->database->createAttribute( From 2f32eb7937718095e49d4a2558539acd6e5a9859 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Jul 2024 01:37:46 +1200 Subject: [PATCH 132/185] Fix import --- bin/MigrationCLI.php | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/MigrationCLI.php b/bin/MigrationCLI.php index 80ae278..379ce31 100644 --- a/bin/MigrationCLI.php +++ b/bin/MigrationCLI.php @@ -7,6 +7,7 @@ use Utopia\Cache\Cache; use Utopia\Database\Adapter\MariaDB; use Utopia\Database\Database; +use Utopia\Database\Validator\Authorization; use Utopia\Migration\Destination; use Utopia\Migration\Destinations\Appwrite as DestinationsAppwrite; use Utopia\Migration\Destinations\Local; From 3db950c6fb1416c7dde07754346aaa3d5bbf565a Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Jul 2024 18:48:45 +1200 Subject: [PATCH 133/185] Fix missing imports --- bin/MigrationCLI.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bin/MigrationCLI.php b/bin/MigrationCLI.php index 379ce31..5f8dd88 100644 --- a/bin/MigrationCLI.php +++ b/bin/MigrationCLI.php @@ -2,11 +2,13 @@ require_once __DIR__ . '/.././vendor/autoload.php'; +use Appwrite\Query; use Dotenv\Dotenv; use Utopia\Cache\Adapter\None; use Utopia\Cache\Cache; use Utopia\Database\Adapter\MariaDB; use Utopia\Database\Database; +use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; use Utopia\Migration\Destination; use Utopia\Migration\Destinations\Appwrite as DestinationsAppwrite; @@ -308,7 +310,7 @@ function (mixed $value) { }, function (mixed $value) { if (is_null($value)) { - return; + return null; } return json_decode($value, true)['value']; From cb0b1d2cc9895f07d98284c1fcd5637a71ed8ed2 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Jul 2024 18:48:58 +1200 Subject: [PATCH 134/185] Fix missing PDO options --- bin/MigrationCLI.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/bin/MigrationCLI.php b/bin/MigrationCLI.php index 5f8dd88..8836062 100644 --- a/bin/MigrationCLI.php +++ b/bin/MigrationCLI.php @@ -365,7 +365,14 @@ function (mixed $value, Document $attribute) { new MariaDB(new PDO( $_ENV['DESTINATION_APPWRITE_TEST_DSN'], $_ENV['DESTINATION_APPWRITE_TEST_USER'], - $_ENV['DESTINATION_APPWRITE_TEST_PASSWORD'] + $_ENV['DESTINATION_APPWRITE_TEST_PASSWORD'], + [ + PDO::ATTR_TIMEOUT => 3, + PDO::ATTR_PERSISTENT => true, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => true, + PDO::ATTR_STRINGIFY_FETCHES => true + ], )), new Cache(new None()) ); @@ -407,7 +414,7 @@ public function start(): void \Utopia\Migration\Resources\Database\Database::getName(), \Utopia\Migration\Resources\Database\Collection::getName(), \Utopia\Migration\Resources\Database\Attribute::getName(), -// \Utopia\Migration\Resources\Database\Index::getName(), + \Utopia\Migration\Resources\Database\Index::getName(), ], function () { $this->drawFrame(); From 0faac91a60d5b60ce3e3c1792fb725b1d3d690d4 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Jul 2024 23:58:14 +1200 Subject: [PATCH 135/185] Force side parent --- src/Migration/Destinations/Appwrite.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 4b13ad0..d928138 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -233,7 +233,6 @@ protected function import(array $resources, callable $callback): void }; } catch (\Throwable $e) { if ($e->getCode() === 409) { - // DATABASE_ALREADY_EXISTS why SKIP? not termination $resource->setStatus(Resource::STATUS_SKIPPED, $e->getMessage()); } else { $resource->setStatus(Resource::STATUS_ERROR, $e->getMessage()); @@ -447,6 +446,7 @@ protected function createAttribute(Attribute $resource): bool ); } if ($resource->getType() === UtopiaDatabase::VAR_RELATIONSHIP) { + $resource->getOptions()['side'] = UtopiaDatabase::RELATION_SIDE_PARENT; $relatedCollection = $this->database->getDocument( 'database_' . $database->getInternalId(), $resource->getOptions()['relatedCollection'] From 987efd45fd1e35178ad8db6c9eb01edd5a328064 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Jul 2024 23:58:24 +1200 Subject: [PATCH 136/185] Clear cache early --- src/Migration/Destinations/Appwrite.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index d928138..59f85ed 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -505,6 +505,8 @@ protected function createAttribute(Attribute $resource): bool throw $e; } + $this->database->purgeCachedDocument('database_' . $database->getInternalId(), $collection->getId()); + $this->database->purgeCachedCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId()); $options = $resource->getOptions(); if ($resource->getType() === UtopiaDatabase::VAR_RELATIONSHIP && isset($relatedCollection) && $options['twoWay']) { From aab670cfd53f7d68b6cf967db44f09238d2127c9 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Jul 2024 23:58:43 +1200 Subject: [PATCH 137/185] Fix duplicate key because of remapping twoWayKey --- src/Migration/Destinations/Appwrite.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 59f85ed..e201498 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -573,7 +573,7 @@ protected function createAttribute(Attribute $resource): bool type: $options['relationType'], twoWay: $options['twoWay'], id: $resource->getKey(), - twoWayKey: $options['twoWayKey'], + twoWayKey: $twoWayKey ?? null, onDelete: $options['onDelete'], ) ) { From bf5c8a8c273b719c959ab2850cd98a6d6d5a63f4 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 26 Jul 2024 23:58:56 +1200 Subject: [PATCH 138/185] Throw on attribute creation failure --- src/Migration/Destinations/Appwrite.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index e201498..7fc6479 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -608,6 +608,13 @@ protected function createAttribute(Attribute $resource): bool if (isset($relatedAttribute)) { $this->database->deleteDocument('attributes', $relatedAttribute->getId()); } + + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'Failed to create attribute', + ); } if ($resource->getType() === UtopiaDatabase::VAR_RELATIONSHIP && isset($relatedCollection) && $options['twoWay']) { From 1bdb6f0e5fcdfe2264a6e6360482ddc68412da1e Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 27 Jul 2024 00:00:53 +1200 Subject: [PATCH 139/185] Fix collection ID and buffer clear --- src/Migration/Destinations/Appwrite.php | 30 +++++++++++++------------ 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 7fc6479..593826b 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -856,21 +856,23 @@ protected function createDocument(Document $resource, bool $isLast): bool return false; } - if (!$isLast) { - $this->documentBuffer[] = new UtopiaDocument(\array_merge([ - '$id' => $resource->getId(), - '$permissions' => $resource->getPermissions(), - ], $resource->getData())); - return true; - } + $this->documentBuffer[] = new UtopiaDocument(\array_merge([ + '$id' => $resource->getId(), + '$permissions' => $resource->getPermissions(), + ], $resource->getData())); - try { - $this->database->createDocuments( - $resource->getCollection()->getId(), - $this->documentBuffer - ); - } finally { - $this->documentBuffer = []; + if ($isLast) { + try { + $databaseInternalId = $resource->getCollection()->getDatabase()->getInternalId(); + $collectionInternalId = $resource->getCollection()->getInternalId(); + + $this->database->createDocuments( + 'database_' . $databaseInternalId . '_collection_' . $collectionInternalId, + $this->documentBuffer + ); + } finally { + $this->documentBuffer = []; + } } return true; From 4f6fe2301f8096b2de88f5bc1d47c76d4fb038bd Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 27 Jul 2024 00:01:09 +1200 Subject: [PATCH 140/185] Options as reference --- src/Migration/Resources/Database/Attribute.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Migration/Resources/Database/Attribute.php b/src/Migration/Resources/Database/Attribute.php index 7c6afee..2d50d2a 100644 --- a/src/Migration/Resources/Database/Attribute.php +++ b/src/Migration/Resources/Database/Attribute.php @@ -42,7 +42,7 @@ public function __construct( protected readonly string $format = '', protected readonly array $formatOptions = [], protected readonly array $filters = [], - protected readonly array $options = [], + protected array $options = [], ) { } @@ -138,7 +138,7 @@ public function getFilters(): array /** * @return array */ - public function getOptions(): array + public function &getOptions(): array { return $this->options; } From 2c9e6677968d608f00bbca0947a976035e74d92b Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 27 Jul 2024 00:01:18 +1200 Subject: [PATCH 141/185] Update database --- composer.lock | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/composer.lock b/composer.lock index 25ccade..fa35220 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": "35a8ee5847ea127c2281dc5a1ee285d5", + "content-hash": "7162a38cd3804bfd37499285693dd65c", "packages": [ { "name": "appwrite/appwrite", @@ -308,16 +308,16 @@ }, { "name": "utopia-php/database", - "version": "0.50.0", + "version": "0.50.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "ce3eaccb2f3bbd34b2b97419836fec633b26b8f7" + "reference": "1745147bef29a9bddf5dd03fd9174ec29e2c26f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/ce3eaccb2f3bbd34b2b97419836fec633b26b8f7", - "reference": "ce3eaccb2f3bbd34b2b97419836fec633b26b8f7", + "url": "https://api.github.com/repos/utopia-php/database/zipball/1745147bef29a9bddf5dd03fd9174ec29e2c26f0", + "reference": "1745147bef29a9bddf5dd03fd9174ec29e2c26f0", "shasum": "" }, "require": { @@ -358,9 +358,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.50.0" + "source": "https://github.com/utopia-php/database/tree/0.50.1" }, - "time": "2024-06-21T03:21:42+00:00" + "time": "2024-07-26T11:56:05+00:00" }, { "name": "utopia-php/dsn", @@ -2823,6 +2823,8 @@ "ext-curl": "*", "ext-openssl": "*" }, - "platform-dev": [], + "platform-dev": { + "ext-pdo": "*" + }, "plugin-api-version": "2.6.0" } From 452f37a472708d0e92b99c6a38ba2ebe04890dc6 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 27 Jul 2024 00:04:41 +1200 Subject: [PATCH 142/185] Fix two way delete check --- bin/MigrationCLI.php | 2 +- src/Migration/Destinations/Appwrite.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/MigrationCLI.php b/bin/MigrationCLI.php index 8836062..129c8ae 100644 --- a/bin/MigrationCLI.php +++ b/bin/MigrationCLI.php @@ -310,7 +310,7 @@ function (mixed $value) { }, function (mixed $value) { if (is_null($value)) { - return null; + return; } return json_decode($value, true)['value']; diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 593826b..2076087 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -605,8 +605,8 @@ protected function createAttribute(Attribute $resource): bool } catch (\Throwable) { $this->database->deleteDocument('attributes', $attribute->getId()); - if (isset($relatedAttribute)) { - $this->database->deleteDocument('attributes', $relatedAttribute->getId()); + if (isset($twoWayAttribute)) { + $this->database->deleteDocument('attributes', $twoWayAttribute->getId()); } throw new Exception( From f8c4c19c41fe25bdb13780c3a5887dce43e3870b Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 28 Jul 2024 13:05:01 +0300 Subject: [PATCH 143/185] Change Collection resource getInternalId --- src/Migration/Destinations/Appwrite.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 2076087..a40a5b8 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -364,9 +364,9 @@ protected function createCollection(Collection $resource): bool ); } - $collection = $this->database->createDocument('database_' . $resource->getDatabase()->getInternalId(), new UtopiaDocument([ + $collection = $this->database->createDocument('database_' . $database->getInternalId(), new UtopiaDocument([ '$id' => $resource->getId(), - 'databaseInternalId' => $resource->getDatabase()->getInternalId(), + 'databaseInternalId' => $database->getInternalId(), 'databaseId' => $resource->getDatabase()->getId(), '$permissions' => Permission::aggregate($resource->getPermissions()), 'documentSecurity' => $resource->getDocumentSecurity(), @@ -378,7 +378,7 @@ protected function createCollection(Collection $resource): bool $resource->setInternalId($collection->getInternalId()); $this->database->createCollection( - 'database_' . $resource->getDatabase()->getInternalId() . '_collection_' . $resource->getInternalId(), + 'database_' . $database->getInternalId() . '_collection_' . $resource->getInternalId(), permissions: $resource->getPermissions(), documentSecurity: $resource->getDocumentSecurity() ); From 6d3d1982c67a59080e60c241f50488f4fc1a38b3 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 28 Jul 2024 14:38:15 +0300 Subject: [PATCH 144/185] Attribute source types --- src/Migration/Destinations/Appwrite.php | 32 ++++++++++++++++++------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index a40a5b8..8168381 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -393,6 +393,20 @@ protected function createCollection(Collection $resource): bool */ protected function createAttribute(Attribute $resource): bool { + $type = match ($resource->getType()) { + Attribute::TYPE_DATETIME => UtopiaDatabase::VAR_DATETIME, + Attribute::TYPE_BOOLEAN => UtopiaDatabase::VAR_BOOLEAN, + Attribute::TYPE_FLOAT => UtopiaDatabase::VAR_FLOAT, + Attribute::TYPE_STRING => UtopiaDatabase::VAR_STRING, + Attribute::TYPE_INTEGER => UtopiaDatabase::VAR_INTEGER, + Attribute::TYPE_IP => UtopiaDatabase::VAR_STRING, + Attribute::TYPE_EMAIL => UtopiaDatabase::VAR_STRING, + Attribute::TYPE_URL => UtopiaDatabase::VAR_STRING, + Attribute::TYPE_RELATIONSHIP => UtopiaDatabase::VAR_RELATIONSHIP, + Attribute::TYPE_ENUM => UtopiaDatabase::VAR_STRING, + default => throw new \Exception('Invalid resource type ' . $resource->getType()), + }; + $database = $this->database->getDocument( 'databases', $resource->getCollection()->getDatabase()->getId(), @@ -420,12 +434,12 @@ protected function createAttribute(Attribute $resource): bool } if (!empty($resource->getFormat())) { - if (!Structure::hasFormat($resource->getFormat(), $resource->getType())) { + if (!Structure::hasFormat($resource->getFormat(), $type)) { throw new Exception( resourceName: $resource->getName(), resourceGroup: $resource->getGroup(), resourceId: $resource->getId(), - message: "Format {$resource->getFormat()} not available for attribute type {$resource->getType()}", + message: "Format {$resource->getFormat()} not available for attribute type {$type}", ); } } @@ -445,7 +459,7 @@ protected function createAttribute(Attribute $resource): bool message: 'Cannot set default value for array attribute', ); } - if ($resource->getType() === UtopiaDatabase::VAR_RELATIONSHIP) { + if ($type === UtopiaDatabase::VAR_RELATIONSHIP) { $resource->getOptions()['side'] = UtopiaDatabase::RELATION_SIDE_PARENT; $relatedCollection = $this->database->getDocument( 'database_' . $database->getInternalId(), @@ -469,7 +483,7 @@ protected function createAttribute(Attribute $resource): bool 'databaseId' => $database->getId(), 'collectionInternalId' => $collection->getInternalId(), 'collectionId' => $collection->getId(), - 'type' => $resource->getType(), + 'type' => $type, 'status' => 'available', 'size' => $resource->getSize(), 'required' => $resource->isRequired(), @@ -509,7 +523,7 @@ protected function createAttribute(Attribute $resource): bool $this->database->purgeCachedCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId()); $options = $resource->getOptions(); - if ($resource->getType() === UtopiaDatabase::VAR_RELATIONSHIP && isset($relatedCollection) && $options['twoWay']) { + if ($type === UtopiaDatabase::VAR_RELATIONSHIP && isset($relatedCollection) && $options['twoWay']) { $twoWayKey = $options['twoWayKey']; $options['relatedCollection'] = $collection->getId(); $options['twoWayKey'] = $resource->getKey(); @@ -523,7 +537,7 @@ protected function createAttribute(Attribute $resource): bool 'databaseId' => $database->getId(), 'collectionInternalId' => $relatedCollection->getInternalId(), 'collectionId' => $relatedCollection->getId(), - 'type' => $resource->getType(), + 'type' => $type, 'status' => 'available', 'size' => $resource->getSize(), 'required' => $resource->isRequired(), @@ -563,7 +577,7 @@ protected function createAttribute(Attribute $resource): bool } try { - switch ($resource->getType()) { + switch ($type) { case UtopiaDatabase::VAR_RELATIONSHIP: if ( isset($relatedCollection) @@ -589,7 +603,7 @@ protected function createAttribute(Attribute $resource): bool if (!$this->database->createAttribute( 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $resource->getKey(), - $resource->getType(), + $type, $resource->getSize(), $resource->isRequired(), $resource->getDefault(), @@ -617,7 +631,7 @@ protected function createAttribute(Attribute $resource): bool ); } - if ($resource->getType() === UtopiaDatabase::VAR_RELATIONSHIP && isset($relatedCollection) && $options['twoWay']) { + if ($type === UtopiaDatabase::VAR_RELATIONSHIP && isset($relatedCollection) && $options['twoWay']) { $this->database->purgeCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId()); } From 4b67ad55b579a372529c52d7c84bd7c16b4797c3 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 28 Jul 2024 15:27:08 +0300 Subject: [PATCH 145/185] createDocument $databaseInternalId $collectionInternalId --- src/Migration/Destinations/Appwrite.php | 28 +++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 8168381..9b00b8b 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -875,11 +875,35 @@ protected function createDocument(Document $resource, bool $isLast): bool '$permissions' => $resource->getPermissions(), ], $resource->getData())); + var_dump("shmuel 1"); + var_dump($isLast); + if ($isLast) { try { - $databaseInternalId = $resource->getCollection()->getDatabase()->getInternalId(); - $collectionInternalId = $resource->getCollection()->getInternalId(); + //$databaseInternalId = $resource->getCollection()->getDatabase()->getInternalId(); + //$collectionInternalId = $resource->getCollection()->getInternalId(); + + /** + * Make this use cache! + */ + $database = $this->database->getDocument( + 'databases', + $resource->getCollection()->getDatabase()->getId(), + ); + + /** + * Make this use cache! + */ + $collection = $this->database->getDocument( + 'database_' . $database->getInternalId(), + $resource->getCollection()->getId(), + ); + + $databaseInternalId = $database->getInternalId(); + $collectionInternalId = $collection->getInternalId(); + var_dump($databaseInternalId); + var_dump($collectionInternalId); $this->database->createDocuments( 'database_' . $databaseInternalId . '_collection_' . $collectionInternalId, $this->documentBuffer From a1ce44d486f82847ac60aad5d5ce12cd4a483fac Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 28 Jul 2024 16:01:49 +0300 Subject: [PATCH 146/185] Fix getType INTEGER --- src/Migration/Destinations/Appwrite.php | 5 +---- src/Migration/Resources/Database/Attributes/Integer.php | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 9b00b8b..d26822a 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -397,11 +397,8 @@ protected function createAttribute(Attribute $resource): bool Attribute::TYPE_DATETIME => UtopiaDatabase::VAR_DATETIME, Attribute::TYPE_BOOLEAN => UtopiaDatabase::VAR_BOOLEAN, Attribute::TYPE_FLOAT => UtopiaDatabase::VAR_FLOAT, - Attribute::TYPE_STRING => UtopiaDatabase::VAR_STRING, + Attribute::TYPE_STRING, Attribute::TYPE_IP, Attribute::TYPE_EMAIL, Attribute::TYPE_URL => UtopiaDatabase::VAR_STRING, Attribute::TYPE_INTEGER => UtopiaDatabase::VAR_INTEGER, - Attribute::TYPE_IP => UtopiaDatabase::VAR_STRING, - Attribute::TYPE_EMAIL => UtopiaDatabase::VAR_STRING, - Attribute::TYPE_URL => UtopiaDatabase::VAR_STRING, Attribute::TYPE_RELATIONSHIP => UtopiaDatabase::VAR_RELATIONSHIP, Attribute::TYPE_ENUM => UtopiaDatabase::VAR_STRING, default => throw new \Exception('Invalid resource type ' . $resource->getType()), diff --git a/src/Migration/Resources/Database/Attributes/Integer.php b/src/Migration/Resources/Database/Attributes/Integer.php index 52d9761..e705cce 100644 --- a/src/Migration/Resources/Database/Attributes/Integer.php +++ b/src/Migration/Resources/Database/Attributes/Integer.php @@ -67,7 +67,7 @@ public static function fromArray(array $array): self public function getType(): string { - return Attribute::TYPE_FLOAT; + return Attribute::TYPE_INTEGER; } public function getMin(): ?int From d28841fba746e71763a67ccb81a81962cfd9396b Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 28 Jul 2024 18:06:41 +0300 Subject: [PATCH 147/185] setPreserveDates --- src/Migration/Destinations/Appwrite.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index d26822a..eea1924 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -853,6 +853,8 @@ protected function createIndex(Index $resource): bool */ protected function createDocument(Document $resource, bool $isLast): bool { + $this->database->setPreserveDates(true); + // Check if document has already been created $exists = \array_key_exists( $resource->getId(), @@ -910,6 +912,8 @@ protected function createDocument(Document $resource, bool $isLast): bool } } + $this->database->setPreserveDates(false); + return true; } From fe0e045e0699ff02718309515c2f30fa3237559d Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 28 Jul 2024 18:47:30 +0300 Subject: [PATCH 148/185] setPreserveDates --- src/Migration/Destinations/Appwrite.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index eea1924..fd4bd83 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -397,11 +397,14 @@ protected function createAttribute(Attribute $resource): bool Attribute::TYPE_DATETIME => UtopiaDatabase::VAR_DATETIME, Attribute::TYPE_BOOLEAN => UtopiaDatabase::VAR_BOOLEAN, Attribute::TYPE_FLOAT => UtopiaDatabase::VAR_FLOAT, - Attribute::TYPE_STRING, Attribute::TYPE_IP, Attribute::TYPE_EMAIL, Attribute::TYPE_URL => UtopiaDatabase::VAR_STRING, + Attribute::TYPE_STRING, + Attribute::TYPE_IP, + Attribute::TYPE_EMAIL, + Attribute::TYPE_URL, + Attribute::TYPE_ENUM => UtopiaDatabase::VAR_STRING, Attribute::TYPE_INTEGER => UtopiaDatabase::VAR_INTEGER, Attribute::TYPE_RELATIONSHIP => UtopiaDatabase::VAR_RELATIONSHIP, - Attribute::TYPE_ENUM => UtopiaDatabase::VAR_STRING, - default => throw new \Exception('Invalid resource type ' . $resource->getType()), + default => throw new \Exception('Invalid resource type '.$resource->getType()), }; $database = $this->database->getDocument( @@ -874,9 +877,6 @@ protected function createDocument(Document $resource, bool $isLast): bool '$permissions' => $resource->getPermissions(), ], $resource->getData())); - var_dump("shmuel 1"); - var_dump($isLast); - if ($isLast) { try { @@ -901,8 +901,7 @@ protected function createDocument(Document $resource, bool $isLast): bool $databaseInternalId = $database->getInternalId(); $collectionInternalId = $collection->getInternalId(); - var_dump($databaseInternalId); - var_dump($collectionInternalId); + $this->database->createDocuments( 'database_' . $databaseInternalId . '_collection_' . $collectionInternalId, $this->documentBuffer From f349e7247eafceb7e368e3f06aa699939841e450 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 29 Jul 2024 18:03:49 +1200 Subject: [PATCH 149/185] Fix missing format on extended string attributes --- bin/MigrationCLI.php | 7 +------ src/Migration/Destinations/Appwrite.php | 4 ++-- .../Resources/Database/Attributes/Email.php | 21 +++++++++++++++++++ .../Resources/Database/Attributes/Enum.php | 1 + .../Resources/Database/Attributes/IP.php | 21 +++++++++++++++++++ .../Resources/Database/Attributes/Text.php | 17 +++++++++++---- .../Resources/Database/Attributes/URL.php | 21 +++++++++++++++++++ 7 files changed, 80 insertions(+), 12 deletions(-) diff --git a/bin/MigrationCLI.php b/bin/MigrationCLI.php index 129c8ae..72c22e3 100644 --- a/bin/MigrationCLI.php +++ b/bin/MigrationCLI.php @@ -410,12 +410,7 @@ public function start(): void * Run Transfer */ Authorization::skip(fn () => $this->transfer->run( - [ - \Utopia\Migration\Resources\Database\Database::getName(), - \Utopia\Migration\Resources\Database\Collection::getName(), - \Utopia\Migration\Resources\Database\Attribute::getName(), - \Utopia\Migration\Resources\Database\Index::getName(), - ], + $this->source->getSupportedResources(), function () { $this->drawFrame(); } diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index fd4bd83..8788c94 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -396,14 +396,14 @@ protected function createAttribute(Attribute $resource): bool $type = match ($resource->getType()) { Attribute::TYPE_DATETIME => UtopiaDatabase::VAR_DATETIME, Attribute::TYPE_BOOLEAN => UtopiaDatabase::VAR_BOOLEAN, + Attribute::TYPE_INTEGER => UtopiaDatabase::VAR_INTEGER, Attribute::TYPE_FLOAT => UtopiaDatabase::VAR_FLOAT, + Attribute::TYPE_RELATIONSHIP => UtopiaDatabase::VAR_RELATIONSHIP, Attribute::TYPE_STRING, Attribute::TYPE_IP, Attribute::TYPE_EMAIL, Attribute::TYPE_URL, Attribute::TYPE_ENUM => UtopiaDatabase::VAR_STRING, - Attribute::TYPE_INTEGER => UtopiaDatabase::VAR_INTEGER, - Attribute::TYPE_RELATIONSHIP => UtopiaDatabase::VAR_RELATIONSHIP, default => throw new \Exception('Invalid resource type '.$resource->getType()), }; diff --git a/src/Migration/Resources/Database/Attributes/Email.php b/src/Migration/Resources/Database/Attributes/Email.php index 630e350..0c8fbbb 100644 --- a/src/Migration/Resources/Database/Attributes/Email.php +++ b/src/Migration/Resources/Database/Attributes/Email.php @@ -3,9 +3,30 @@ namespace Utopia\Migration\Resources\Database\Attributes; use Utopia\Migration\Resources\Database\Attribute; +use Utopia\Migration\Resources\Database\Collection; class Email extends Text { + public function __construct( + string $key, + Collection + $collection, + bool $required = false, + ?string $default = null, + bool $array = false, + int $size = 256 + ) { + parent::__construct( + $key, + $collection, + required: $required, + default: $default, + array: $array, + size: $size, + format: 'email', + ); + } + public function getType(): string { return Attribute::TYPE_EMAIL; diff --git a/src/Migration/Resources/Database/Attributes/Enum.php b/src/Migration/Resources/Database/Attributes/Enum.php index 06150dc..6bec6d3 100644 --- a/src/Migration/Resources/Database/Attributes/Enum.php +++ b/src/Migration/Resources/Database/Attributes/Enum.php @@ -24,6 +24,7 @@ public function __construct( required: $required, default: $default, array: $array, + format: 'enum', formatOptions: [ 'elements' => $elements, ] diff --git a/src/Migration/Resources/Database/Attributes/IP.php b/src/Migration/Resources/Database/Attributes/IP.php index 08fbc38..8ea44d0 100644 --- a/src/Migration/Resources/Database/Attributes/IP.php +++ b/src/Migration/Resources/Database/Attributes/IP.php @@ -3,9 +3,30 @@ namespace Utopia\Migration\Resources\Database\Attributes; use Utopia\Migration\Resources\Database\Attribute; +use Utopia\Migration\Resources\Database\Collection; class IP extends Text { + public function __construct( + string $key, + Collection + $collection, + bool $required = false, + ?string $default = null, + bool $array = false, + int $size = 256 + ) { + parent::__construct( + $key, + $collection, + required: $required, + default: $default, + array: $array, + size: $size, + format: 'ip', + ); + } + public function getType(): string { return Attribute::TYPE_IP; diff --git a/src/Migration/Resources/Database/Attributes/Text.php b/src/Migration/Resources/Database/Attributes/Text.php index 435c0e3..8274e5f 100644 --- a/src/Migration/Resources/Database/Attributes/Text.php +++ b/src/Migration/Resources/Database/Attributes/Text.php @@ -13,7 +13,8 @@ public function __construct( bool $required = false, ?string $default = null, bool $array = false, - int $size = 256 + int $size = 256, + string $format = '', ) { parent::__construct( $key, @@ -21,7 +22,8 @@ public function __construct( size: $size, required: $required, default: $default, - array: $array + array: $array, + format: $format, ); } @@ -41,7 +43,8 @@ public function __construct( * required: bool, * default: ?string, * array: bool, - * size: int + * size: int, + * format: string, * } $array * @return self */ @@ -53,7 +56,8 @@ public static function fromArray(array $array): self required: $array['required'], default: $array['default'] ?? null, array: $array['array'], - size: $array['size'] + size: $array['size'], + format: $array['format'], ); } @@ -66,4 +70,9 @@ public function getSize(): int { return $this->size; } + + public function getFormat(): string + { + return $this->format; + } } diff --git a/src/Migration/Resources/Database/Attributes/URL.php b/src/Migration/Resources/Database/Attributes/URL.php index e67d690..df04581 100644 --- a/src/Migration/Resources/Database/Attributes/URL.php +++ b/src/Migration/Resources/Database/Attributes/URL.php @@ -3,9 +3,30 @@ namespace Utopia\Migration\Resources\Database\Attributes; use Utopia\Migration\Resources\Database\Attribute; +use Utopia\Migration\Resources\Database\Collection; class URL extends Text { + public function __construct( + string $key, + Collection + $collection, + bool $required = false, + ?string $default = null, + bool $array = false, + int $size = 256 + ) { + parent::__construct( + $key, + $collection, + required: $required, + default: $default, + array: $array, + size: $size, + format: 'url', + ); + } + public function getType(): string { return Attribute::TYPE_URL; From 697d51ea55ac6309c95bfa850a0358b3e06b1e0e Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 29 Jul 2024 18:28:04 +1200 Subject: [PATCH 150/185] Fix size on extended string format --- bin/MigrationCLI.php | 4 ++-- src/Migration/Resources/Database/Attributes/Email.php | 3 ++- src/Migration/Resources/Database/Attributes/IP.php | 2 +- src/Migration/Resources/Database/Attributes/Text.php | 3 ++- src/Migration/Resources/Database/Attributes/URL.php | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/bin/MigrationCLI.php b/bin/MigrationCLI.php index 72c22e3..68779b4 100644 --- a/bin/MigrationCLI.php +++ b/bin/MigrationCLI.php @@ -61,7 +61,7 @@ class MigrationCLI [ '$id' => 'name', 'type' => Database::VAR_STRING, - 'size' => 256, + 'size' => Database::LENGTH_KEY, 'required' => true, 'signed' => true, 'array' => false, @@ -131,7 +131,7 @@ class MigrationCLI '$id' => '_key_name', 'type' => Database::INDEX_KEY, 'attributes' => ['name'], - 'lengths' => [256], + 'lengths' => [Database::LENGTH_KEY], 'orders' => [Database::ORDER_ASC], ], [ diff --git a/src/Migration/Resources/Database/Attributes/Email.php b/src/Migration/Resources/Database/Attributes/Email.php index 0c8fbbb..5f5e6a9 100644 --- a/src/Migration/Resources/Database/Attributes/Email.php +++ b/src/Migration/Resources/Database/Attributes/Email.php @@ -2,6 +2,7 @@ namespace Utopia\Migration\Resources\Database\Attributes; +use Utopia\Database\Database; use Utopia\Migration\Resources\Database\Attribute; use Utopia\Migration\Resources\Database\Collection; @@ -14,7 +15,7 @@ public function __construct( bool $required = false, ?string $default = null, bool $array = false, - int $size = 256 + int $size = Database::LENGTH_KEY ) { parent::__construct( $key, diff --git a/src/Migration/Resources/Database/Attributes/IP.php b/src/Migration/Resources/Database/Attributes/IP.php index 8ea44d0..894efe0 100644 --- a/src/Migration/Resources/Database/Attributes/IP.php +++ b/src/Migration/Resources/Database/Attributes/IP.php @@ -14,7 +14,7 @@ public function __construct( bool $required = false, ?string $default = null, bool $array = false, - int $size = 256 + int $size = 39 ) { parent::__construct( $key, diff --git a/src/Migration/Resources/Database/Attributes/Text.php b/src/Migration/Resources/Database/Attributes/Text.php index 8274e5f..6effa0e 100644 --- a/src/Migration/Resources/Database/Attributes/Text.php +++ b/src/Migration/Resources/Database/Attributes/Text.php @@ -2,6 +2,7 @@ namespace Utopia\Migration\Resources\Database\Attributes; +use Utopia\Database\Database; use Utopia\Migration\Resources\Database\Attribute; use Utopia\Migration\Resources\Database\Collection; @@ -13,7 +14,7 @@ public function __construct( bool $required = false, ?string $default = null, bool $array = false, - int $size = 256, + int $size = Database::LENGTH_KEY, string $format = '', ) { parent::__construct( diff --git a/src/Migration/Resources/Database/Attributes/URL.php b/src/Migration/Resources/Database/Attributes/URL.php index df04581..9bb2163 100644 --- a/src/Migration/Resources/Database/Attributes/URL.php +++ b/src/Migration/Resources/Database/Attributes/URL.php @@ -14,7 +14,7 @@ public function __construct( bool $required = false, ?string $default = null, bool $array = false, - int $size = 256 + int $size = 2000 ) { parent::__construct( $key, From 272485ef287e4b8e4107f490fd4aeea105513bd0 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 29 Jul 2024 10:40:28 +0300 Subject: [PATCH 151/185] Fix size --- src/Migration/Resources/Database/Attributes/Enum.php | 5 ++++- src/Migration/Resources/Database/Attributes/IP.php | 3 +-- src/Migration/Resources/Database/Attributes/URL.php | 3 +-- src/Migration/Sources/Appwrite.php | 3 +++ 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Migration/Resources/Database/Attributes/Enum.php b/src/Migration/Resources/Database/Attributes/Enum.php index 6bec6d3..f1a76cc 100644 --- a/src/Migration/Resources/Database/Attributes/Enum.php +++ b/src/Migration/Resources/Database/Attributes/Enum.php @@ -17,17 +17,19 @@ public function __construct( bool $required = false, ?string $default = null, bool $array = false, + int $size = 256 ) { parent::__construct( $key, $collection, + size: $size, required: $required, default: $default, array: $array, format: 'enum', formatOptions: [ 'elements' => $elements, - ] + ], ); } @@ -62,6 +64,7 @@ public static function fromArray(array $array): self required: $array['required'], default: $array['default'], array: $array['array'], + size: $array['size'], ); } diff --git a/src/Migration/Resources/Database/Attributes/IP.php b/src/Migration/Resources/Database/Attributes/IP.php index 894efe0..f843995 100644 --- a/src/Migration/Resources/Database/Attributes/IP.php +++ b/src/Migration/Resources/Database/Attributes/IP.php @@ -9,8 +9,7 @@ class IP extends Text { public function __construct( string $key, - Collection - $collection, + Collection $collection, bool $required = false, ?string $default = null, bool $array = false, diff --git a/src/Migration/Resources/Database/Attributes/URL.php b/src/Migration/Resources/Database/Attributes/URL.php index 9bb2163..afe80ce 100644 --- a/src/Migration/Resources/Database/Attributes/URL.php +++ b/src/Migration/Resources/Database/Attributes/URL.php @@ -9,8 +9,7 @@ class URL extends Text { public function __construct( string $key, - Collection - $collection, + Collection $collection, bool $required = false, ?string $default = null, bool $array = false, diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index e2d6cbd..d8cc5b8 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -776,6 +776,7 @@ private function convertAttribute(array $value, Collection $collection): Attribu required: $value['required'], default: $value['default'], array: $value['array'], + size: $value['size'] ?? 256, ), 'url' => new URL( $value['key'], @@ -783,6 +784,7 @@ private function convertAttribute(array $value, Collection $collection): Attribu required: $value['required'], default: $value['default'], array: $value['array'], + size: $value['size'] ?? 2000, ), 'ip' => new IP( $value['key'], @@ -790,6 +792,7 @@ private function convertAttribute(array $value, Collection $collection): Attribu required: $value['required'], default: $value['default'], array: $value['array'], + size: $value['size'] ?? 39, ), default => new Text( $value['key'], From bb2685e7a1be687600ae16d1b7a42fb18aad0b36 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 29 Jul 2024 10:46:09 +0300 Subject: [PATCH 152/185] Email size --- src/Migration/Resources/Database/Attributes/Email.php | 3 +-- src/Migration/Sources/Appwrite.php | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Migration/Resources/Database/Attributes/Email.php b/src/Migration/Resources/Database/Attributes/Email.php index 5f5e6a9..2f7b32d 100644 --- a/src/Migration/Resources/Database/Attributes/Email.php +++ b/src/Migration/Resources/Database/Attributes/Email.php @@ -10,8 +10,7 @@ class Email extends Text { public function __construct( string $key, - Collection - $collection, + Collection $collection, bool $required = false, ?string $default = null, bool $array = false, diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index d8cc5b8..e744505 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -38,6 +38,7 @@ use Utopia\Migration\Resources\Storage\File; use Utopia\Migration\Source; use Utopia\Migration\Transfer; +use Utopia\Database\Database as UtopiaDatabase; class Appwrite extends Source { @@ -768,6 +769,7 @@ private function convertAttribute(array $value, Collection $collection): Attribu required: $value['required'], default: $value['default'], array: $value['array'], + size: $value['size'] ?? UtopiaDatabase::LENGTH_KEY, ), 'enum' => new Enum( $value['key'], @@ -776,7 +778,7 @@ private function convertAttribute(array $value, Collection $collection): Attribu required: $value['required'], default: $value['default'], array: $value['array'], - size: $value['size'] ?? 256, + size: $value['size'] ?? UtopiaDatabase::LENGTH_KEY, ), 'url' => new URL( $value['key'], From ae26226960db28e06f9813424c0a1e0d1f5f798b Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 29 Jul 2024 13:07:51 +0300 Subject: [PATCH 153/185] Fix email length --- src/Migration/Resources/Database/Attributes/Email.php | 2 +- src/Migration/Sources/Appwrite.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Migration/Resources/Database/Attributes/Email.php b/src/Migration/Resources/Database/Attributes/Email.php index 2f7b32d..6c59484 100644 --- a/src/Migration/Resources/Database/Attributes/Email.php +++ b/src/Migration/Resources/Database/Attributes/Email.php @@ -14,7 +14,7 @@ public function __construct( bool $required = false, ?string $default = null, bool $array = false, - int $size = Database::LENGTH_KEY + int $size = 254 ) { parent::__construct( $key, diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index e744505..3374eb7 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -769,7 +769,7 @@ private function convertAttribute(array $value, Collection $collection): Attribu required: $value['required'], default: $value['default'], array: $value['array'], - size: $value['size'] ?? UtopiaDatabase::LENGTH_KEY, + size: $value['size'] ?? 254, ), 'enum' => new Enum( $value['key'], From 26af4f6328417d2d71c2c73ce95c9565361da836 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 29 Jul 2024 22:26:10 +1200 Subject: [PATCH 154/185] Fix float/int signed attribute --- src/Migration/Resources/Database/Attributes/Decimal.php | 7 ++++++- src/Migration/Resources/Database/Attributes/Integer.php | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Migration/Resources/Database/Attributes/Decimal.php b/src/Migration/Resources/Database/Attributes/Decimal.php index 3d894ba..2109855 100644 --- a/src/Migration/Resources/Database/Attributes/Decimal.php +++ b/src/Migration/Resources/Database/Attributes/Decimal.php @@ -14,14 +14,19 @@ public function __construct( ?float $default = null, bool $array = false, ?float $min = null, - ?float $max = null + ?float $max = null, + bool $signed = true, ) { + $min ??= PHP_FLOAT_MIN; + $max ??= PHP_FLOAT_MAX; + parent::__construct( $key, $collection, required: $required, default: $default, array: $array, + signed: $signed, formatOptions: [ 'min' => $min, 'max' => $max, diff --git a/src/Migration/Resources/Database/Attributes/Integer.php b/src/Migration/Resources/Database/Attributes/Integer.php index e705cce..b74dfc7 100644 --- a/src/Migration/Resources/Database/Attributes/Integer.php +++ b/src/Migration/Resources/Database/Attributes/Integer.php @@ -14,14 +14,18 @@ public function __construct( ?int $default = null, bool $array = false, ?int $min = null, - ?int $max = null + ?int $max = null, + bool $signed = true, ) { + $min ??= PHP_INT_MIN; + $max ??= PHP_INT_MAX; parent::__construct( $key, $collection, required: $required, default: $default, array: $array, + signed: $signed, formatOptions: [ 'min' => $min, 'max' => $max, From edbb61e14ccd2747ef0655a2559befcccc7b2136 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 29 Jul 2024 22:26:25 +1200 Subject: [PATCH 155/185] Fix integer size --- src/Migration/Resources/Database/Attributes/Integer.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Migration/Resources/Database/Attributes/Integer.php b/src/Migration/Resources/Database/Attributes/Integer.php index b74dfc7..0f107df 100644 --- a/src/Migration/Resources/Database/Attributes/Integer.php +++ b/src/Migration/Resources/Database/Attributes/Integer.php @@ -19,9 +19,12 @@ public function __construct( ) { $min ??= PHP_INT_MIN; $max ??= PHP_INT_MAX; + $size = $max > 2147483647 ? 8 : 4; + parent::__construct( $key, $collection, + size: $size, required: $required, default: $default, array: $array, From caef939fd468e946d5183f385255dd37c904d36c Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 1 Aug 2024 10:50:58 +0300 Subject: [PATCH 156/185] Fix twoWayKey --- src/Migration/Destinations/Appwrite.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 8788c94..8a474c9 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -523,6 +523,8 @@ protected function createAttribute(Attribute $resource): bool $this->database->purgeCachedCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId()); $options = $resource->getOptions(); + $twoWayKey = null; + if ($type === UtopiaDatabase::VAR_RELATIONSHIP && isset($relatedCollection) && $options['twoWay']) { $twoWayKey = $options['twoWayKey']; $options['relatedCollection'] = $collection->getId(); @@ -587,7 +589,7 @@ protected function createAttribute(Attribute $resource): bool type: $options['relationType'], twoWay: $options['twoWay'], id: $resource->getKey(), - twoWayKey: $twoWayKey ?? null, + twoWayKey: $options['twoWay'] ? $twoWayKey : $options['twoWayKey'] ?? null, onDelete: $options['onDelete'], ) ) { From 40549786ab182cf1e8ac5239844e860c7d938add Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 1 Aug 2024 11:52:06 +0300 Subject: [PATCH 157/185] Add catch createDocuments --- src/Migration/Destinations/Appwrite.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 8a474c9..8459a2c 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -855,6 +855,7 @@ protected function createIndex(Index $resource): bool * @throws AuthorizationException * @throws DatabaseException * @throws StructureException + * @throws Exception */ protected function createDocument(Document $resource, bool $isLast): bool { @@ -908,6 +909,14 @@ protected function createDocument(Document $resource, bool $isLast): bool 'database_' . $databaseInternalId . '_collection_' . $collectionInternalId, $this->documentBuffer ); + + } catch (\Throwable $e) { + throw new Exception( + resourceName: $resource->getName(), + resourceGroup: $resource->getGroup(), + resourceId: $resource->getId(), + message: 'createDocuments failed: ' . $e->getMessage(), + ); } finally { $this->documentBuffer = []; } From 2f54112b13bf57898ec8cc952cf258ed314d9ff8 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 1 Aug 2024 15:54:21 +0300 Subject: [PATCH 158/185] Revert --- src/Migration/Destinations/Appwrite.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 8459a2c..8a474c9 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -855,7 +855,6 @@ protected function createIndex(Index $resource): bool * @throws AuthorizationException * @throws DatabaseException * @throws StructureException - * @throws Exception */ protected function createDocument(Document $resource, bool $isLast): bool { @@ -909,14 +908,6 @@ protected function createDocument(Document $resource, bool $isLast): bool 'database_' . $databaseInternalId . '_collection_' . $collectionInternalId, $this->documentBuffer ); - - } catch (\Throwable $e) { - throw new Exception( - resourceName: $resource->getName(), - resourceGroup: $resource->getGroup(), - resourceId: $resource->getId(), - message: 'createDocuments failed: ' . $e->getMessage(), - ); } finally { $this->documentBuffer = []; } From 66447084933addd47cb633a559e5525a0e8797e6 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 5 Aug 2024 19:49:03 +1200 Subject: [PATCH 159/185] Parameterise batch size --- src/Migration/Destination.php | 20 ++++++++++++++----- .../Resources/Database/Attributes/Email.php | 1 - src/Migration/Source.php | 4 ++-- src/Migration/Sources/Appwrite.php | 2 +- src/Migration/Transfer.php | 18 +++++++++++++---- 5 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/Migration/Destination.php b/src/Migration/Destination.php index d5f576d..3c1146e 100644 --- a/src/Migration/Destination.php +++ b/src/Migration/Destination.php @@ -27,12 +27,22 @@ public function setSource(Source $source): self * @param array $resources Resources to transfer * @param callable $callback Callback to run after transfer * @param string $rootResourceId Root resource ID, If enabled you can only transfer a single root resource + * @param int $batchSize The number of resources to transfer in a single batch */ - public function run(array $resources, callable $callback, string $rootResourceId = ''): void - { - $this->source->run($resources, function (array $resources) use ($callback) { - $this->import($resources, $callback); - }, $rootResourceId); + public function run( + array $resources, + callable $callback, + string $rootResourceId = '', + int $batchSize = 100, + ): void { + $this->source->run( + $resources, + function (array $resources) use ($callback) { + $this->import($resources, $callback); + }, + $rootResourceId, + $batchSize + ); } /** diff --git a/src/Migration/Resources/Database/Attributes/Email.php b/src/Migration/Resources/Database/Attributes/Email.php index 6c59484..57dabc4 100644 --- a/src/Migration/Resources/Database/Attributes/Email.php +++ b/src/Migration/Resources/Database/Attributes/Email.php @@ -2,7 +2,6 @@ namespace Utopia\Migration\Resources\Database\Attributes; -use Utopia\Database\Database; use Utopia\Migration\Resources\Database\Attribute; use Utopia\Migration\Resources\Database\Collection; diff --git a/src/Migration/Source.php b/src/Migration/Source.php index 8d072cf..43548f5 100644 --- a/src/Migration/Source.php +++ b/src/Migration/Source.php @@ -27,7 +27,7 @@ public function callback(array $resources): void * @param callable $callback Callback to run after transfer * @param string $rootResourceId Root resource ID, If enabled you can only transfer a single root resource */ - public function run(array $resources, callable $callback, string $rootResourceId = ''): void + public function run(array $resources, callable $callback, string $rootResourceId = '', int $batchSize = 100): void { $this->rootResourceId = $rootResourceId; @@ -46,7 +46,7 @@ public function run(array $resources, callable $callback, string $rootResourceId $this->cache->addAll($prunedResources); }; - $this->exportResources($resources, 100); + $this->exportResources($resources, $batchSize); } /** diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 3374eb7..e3e34c0 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -10,6 +10,7 @@ use Appwrite\Services\Storage; use Appwrite\Services\Teams; use Appwrite\Services\Users; +use Utopia\Database\Database as UtopiaDatabase; use Utopia\Migration\Exception; use Utopia\Migration\Resource; use Utopia\Migration\Resources\Auth\Hash; @@ -38,7 +39,6 @@ use Utopia\Migration\Resources\Storage\File; use Utopia\Migration\Source; use Utopia\Migration\Transfer; -use Utopia\Database\Database as UtopiaDatabase; class Appwrite extends Source { diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index 3b4ed0c..6581d0f 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -180,12 +180,16 @@ public function getStatusCounters(): array * * @param array $resources Resources to transfer * @param callable $callback Callback to run after transfer - * @param string|null $rootResourceId Root resource ID, If enabled you can only transfer a single root resource + * @param string $rootResourceId Root resource ID, If enabled you can only transfer a single root resource * * @throws \Exception */ - public function run(array $resources, callable $callback, string $rootResourceId = null): void - { + public function run( + array $resources, + callable $callback, + string $rootResourceId = '', + int $batchSize = 100 + ): void { // Allows you to push entire groups if you want. $computedResources = []; foreach ($resources as $resource) { @@ -208,7 +212,13 @@ public function run(array $resources, callable $callback, string $rootResourceId } $this->resources = $computedResources; - $this->destination->run($computedResources, $callback, $rootResourceId); + + $this->destination->run( + $computedResources, + $callback, + $rootResourceId, + $batchSize + ); } /** From 9461883bcc8b11d9f815ce5d0795eb653a34b425 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 5 Aug 2024 14:17:57 +0300 Subject: [PATCH 160/185] getBatchSize --- src/Migration/Destinations/Appwrite.php | 5 +++++ src/Migration/Source.php | 2 ++ src/Migration/Target.php | 5 +++++ 3 files changed, 12 insertions(+) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 8a474c9..8c81131 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -1340,4 +1340,9 @@ private function importDeployment(Deployment $deployment): Resource return $deployment; } + + public function getBatchSize(): int + { + return 200; + } } diff --git a/src/Migration/Source.php b/src/Migration/Source.php index 43548f5..6bd38c2 100644 --- a/src/Migration/Source.php +++ b/src/Migration/Source.php @@ -46,6 +46,8 @@ public function run(array $resources, callable $callback, string $rootResourceId $this->cache->addAll($prunedResources); }; + $batchSize = $this->getBatchSize(); + $this->exportResources($resources, $batchSize); } diff --git a/src/Migration/Target.php b/src/Migration/Target.php index a8165c2..e4f0140 100644 --- a/src/Migration/Target.php +++ b/src/Migration/Target.php @@ -225,4 +225,9 @@ public function addWarning(Warning $warning): void public function shutdown(): void { } + + public function getBatchSize(): int + { + return 100; + } } From 65add6a2779b4c8c7cd9864034a2ff7a2b1b4566 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 5 Aug 2024 17:52:19 +0300 Subject: [PATCH 161/185] allow nulls --- src/Migration/Transfer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index 6581d0f..8f61876 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -187,7 +187,7 @@ public function getStatusCounters(): array public function run( array $resources, callable $callback, - string $rootResourceId = '', + string $rootResourceId = null, int $batchSize = 100 ): void { // Allows you to push entire groups if you want. From 8ccde499763daf9e1e36839a2e5996598b137b40 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 5 Aug 2024 17:55:20 +0300 Subject: [PATCH 162/185] allow nulls --- src/Migration/Transfer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index 8f61876..ce5f0ec 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -178,10 +178,10 @@ public function getStatusCounters(): array /** * Transfer Resources between adapters * - * @param array $resources Resources to transfer - * @param callable $callback Callback to run after transfer - * @param string $rootResourceId Root resource ID, If enabled you can only transfer a single root resource - * + * @param array $resources Resources to transfer + * @param callable $callback Callback to run after transfer + * @param string|null $rootResourceId Root resource ID, If enabled you can only transfer a single root resource + * @param int $batchSize * @throws \Exception */ public function run( From 44c8196ab12d07158ec5cbad9868cabc86a87eb9 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 6 Aug 2024 14:19:52 +1200 Subject: [PATCH 163/185] Stan fixes --- composer.json | 2 +- src/Migration/Destination.php | 2 +- src/Migration/Destinations/Appwrite.php | 25 ++++++++----------- src/Migration/Exception.php | 5 +++- src/Migration/Resource.php | 3 +++ src/Migration/Resources/Auth/User.php | 4 +++ .../Resources/Database/Attributes/Enum.php | 1 + src/Migration/Resources/Functions/Func.php | 9 ++++++- src/Migration/Resources/Storage/Bucket.php | 3 +++ src/Migration/Resources/Storage/File.php | 6 ++--- src/Migration/Source.php | 18 +++++++++---- src/Migration/Sources/Appwrite.php | 3 +++ 12 files changed, 55 insertions(+), 26 deletions(-) diff --git a/composer.json b/composer.json index 86f9592..99b7cb8 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "test": "./vendor/bin/phpunit", "lint": "./vendor/bin/pint --test", "format": "./vendor/bin/pint", - "check": "./vendor/bin/phpstan analyse --level=8" + "check": "./vendor/bin/phpstan analyse --level=8 --memory-limit 512M" }, "require": { "php": "8.3", diff --git a/src/Migration/Destination.php b/src/Migration/Destination.php index 3c1146e..2a3b95e 100644 --- a/src/Migration/Destination.php +++ b/src/Migration/Destination.php @@ -48,7 +48,7 @@ function (array $resources) use ($callback) { /** * Import Resources * - * @param resource[] $resources Resources to import + * @param Resource[] $resources Resources to import * @param callable $callback Callback to run after import */ abstract protected function import(array $resources, callable $callback): void; diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 8a474c9..66c0018 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -525,7 +525,7 @@ protected function createAttribute(Attribute $resource): bool $twoWayKey = null; - if ($type === UtopiaDatabase::VAR_RELATIONSHIP && isset($relatedCollection) && $options['twoWay']) { + if ($type === UtopiaDatabase::VAR_RELATIONSHIP && $options['twoWay']) { $twoWayKey = $options['twoWayKey']; $options['relatedCollection'] = $collection->getId(); $options['twoWayKey'] = $resource->getKey(); @@ -581,18 +581,15 @@ protected function createAttribute(Attribute $resource): bool try { switch ($type) { case UtopiaDatabase::VAR_RELATIONSHIP: - if ( - isset($relatedCollection) - && !$this->database->createRelationship( - collection: 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), - relatedCollection: 'database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId(), - type: $options['relationType'], - twoWay: $options['twoWay'], - id: $resource->getKey(), - twoWayKey: $options['twoWay'] ? $twoWayKey : $options['twoWayKey'] ?? null, - onDelete: $options['onDelete'], - ) - ) { + if (!$this->database->createRelationship( + collection: 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), + relatedCollection: 'database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId(), + type: $options['relationType'], + twoWay: $options['twoWay'], + id: $resource->getKey(), + twoWayKey: $options['twoWay'] ? $twoWayKey : $options['twoWayKey'] ?? null, + onDelete: $options['onDelete'], + )) { throw new Exception( resourceName: $resource->getName(), resourceGroup: $resource->getGroup(), @@ -633,7 +630,7 @@ protected function createAttribute(Attribute $resource): bool ); } - if ($type === UtopiaDatabase::VAR_RELATIONSHIP && isset($relatedCollection) && $options['twoWay']) { + if ($type === UtopiaDatabase::VAR_RELATIONSHIP && $options['twoWay']) { $this->database->purgeCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId()); } diff --git a/src/Migration/Exception.php b/src/Migration/Exception.php index 093e520..1b7ed0f 100644 --- a/src/Migration/Exception.php +++ b/src/Migration/Exception.php @@ -40,6 +40,9 @@ public function getResourceId(): string return $this->resourceId ?? ''; } + /** + * @return array + */ public function jsonSerialize(): array { return [ @@ -48,7 +51,7 @@ public function jsonSerialize(): array 'resourceName' => $this->resourceName, 'resourceGroup' => $this->resourceGroup, 'resourceId' => $this->resourceId, - 'trace' => $this->getPrevious()->getTrace(), + 'trace' => $this->getPrevious()?->getTraceAsString(), ]; } } diff --git a/src/Migration/Resource.php b/src/Migration/Resource.php index f953b9f..d317b76 100644 --- a/src/Migration/Resource.php +++ b/src/Migration/Resource.php @@ -81,6 +81,9 @@ abstract class Resource implements \JsonSerializable protected string $message = ''; + /** + * @var array + */ protected array $permissions = []; abstract public static function getName(): string; diff --git a/src/Migration/Resources/Auth/User.php b/src/Migration/Resources/Auth/User.php index 7b44baa..3f72eef 100644 --- a/src/Migration/Resources/Auth/User.php +++ b/src/Migration/Resources/Auth/User.php @@ -118,6 +118,8 @@ public function getPhone(): ?string /** * Get Labels + * + * @return array */ public function getLabels(): array { @@ -163,6 +165,8 @@ public function getDisabled(): bool /** * Get Preferences + * + * @return array */ public function getPreferences(): array { diff --git a/src/Migration/Resources/Database/Attributes/Enum.php b/src/Migration/Resources/Database/Attributes/Enum.php index f1a76cc..0c24e27 100644 --- a/src/Migration/Resources/Database/Attributes/Enum.php +++ b/src/Migration/Resources/Database/Attributes/Enum.php @@ -46,6 +46,7 @@ public function __construct( * documentSecurity: bool, * permissions: ?array * }, + * size: int, * required: bool, * default: ?string, * array: bool, diff --git a/src/Migration/Resources/Functions/Func.php b/src/Migration/Resources/Functions/Func.php index 4f1e56a..930f921 100644 --- a/src/Migration/Resources/Functions/Func.php +++ b/src/Migration/Resources/Functions/Func.php @@ -8,8 +8,8 @@ class Func extends Resource { /** - * @param string $name * @param string $id + * @param string $name * @param string $runtime * @param array $execute * @param bool $enabled @@ -17,6 +17,7 @@ class Func extends Resource * @param string $schedule * @param int $timeout * @param string $activeDeployment + * @param string $entrypoint */ public function __construct( string $id, @@ -87,6 +88,9 @@ public function getFunctionName(): string return $this->name; } + /** + * @return array + */ public function getExecute(): array { return $this->execute; @@ -102,6 +106,9 @@ public function getRuntime(): string return $this->runtime; } + /** + * @return array + */ public function getEvents(): array { return $this->events; diff --git a/src/Migration/Resources/Storage/Bucket.php b/src/Migration/Resources/Storage/Bucket.php index f454161..cd6fdf0 100644 --- a/src/Migration/Resources/Storage/Bucket.php +++ b/src/Migration/Resources/Storage/Bucket.php @@ -109,6 +109,9 @@ public function getMaxFileSize(): ?int } + /** + * @return array + */ public function getAllowedFileExtensions(): array { return $this->allowedFileExtensions; diff --git a/src/Migration/Resources/Storage/File.php b/src/Migration/Resources/Storage/File.php index 925600e..ba47ec0 100644 --- a/src/Migration/Resources/Storage/File.php +++ b/src/Migration/Resources/Storage/File.php @@ -9,7 +9,7 @@ class File extends Resource { /** * @param string $id - * @param Bucket|null $bucket + * @param Bucket $bucket * @param string $name * @param string $signature * @param string $mimeType @@ -21,7 +21,7 @@ class File extends Resource */ public function __construct( string $id, - private readonly ?Bucket $bucket = null, + private readonly Bucket $bucket, private readonly string $name = '', private readonly string $signature = '', private readonly string $mimeType = '', @@ -43,7 +43,7 @@ public static function fromArray(array $array): self { return new self( $array['id'], - Bucket::fromArray($array['bucket']), // Do we need here only the BucketId? + Bucket::fromArray($array['bucket']), $array['name'] ?? '', $array['signature'] ?? '', $array['mimeType'] ?? '', diff --git a/src/Migration/Source.php b/src/Migration/Source.php index 43548f5..0f00d1e 100644 --- a/src/Migration/Source.php +++ b/src/Migration/Source.php @@ -4,6 +4,9 @@ abstract class Source extends Target { + /** + * @var callable(array): void $transferCallback + */ protected $transferCallback; /** @@ -27,7 +30,12 @@ public function callback(array $resources): void * @param callable $callback Callback to run after transfer * @param string $rootResourceId Root resource ID, If enabled you can only transfer a single root resource */ - public function run(array $resources, callable $callback, string $rootResourceId = '', int $batchSize = 100): void + public function run( + array $resources, + callable $callback, + string $rootResourceId = '', + int $batchSize = 100 + ): void { $this->rootResourceId = $rootResourceId; @@ -101,7 +109,7 @@ public function exportResources(array $resources, int $batchSize): void * @param int $batchSize Max 100 * @param array $resources Resources to export */ - abstract protected function exportGroupAuth(int $batchSize, array $resources); + abstract protected function exportGroupAuth(int $batchSize, array $resources): void; /** * Export Databases Group @@ -109,7 +117,7 @@ abstract protected function exportGroupAuth(int $batchSize, array $resources); * @param int $batchSize Max 100 * @param array $resources Resources to export */ - abstract protected function exportGroupDatabases(int $batchSize, array $resources); + abstract protected function exportGroupDatabases(int $batchSize, array $resources): void; /** * Export Storage Group @@ -117,7 +125,7 @@ abstract protected function exportGroupDatabases(int $batchSize, array $resource * @param int $batchSize Max 5 * @param array $resources Resources to export */ - abstract protected function exportGroupStorage(int $batchSize, array $resources); + abstract protected function exportGroupStorage(int $batchSize, array $resources): void; /** * Export Functions Group @@ -125,5 +133,5 @@ abstract protected function exportGroupStorage(int $batchSize, array $resources) * @param int $batchSize Max 100 * @param array $resources Resources to export */ - abstract protected function exportGroupFunctions(int $batchSize, array $resources); + abstract protected function exportGroupFunctions(int $batchSize, array $resources): void; } diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index e3e34c0..d437fa0 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -81,6 +81,9 @@ public static function getName(): string return 'Appwrite'; } + /** + * @return array + */ public static function getSupportedResources(): array { return [ From de4c092c2774e5694c5189be33aa00f8ab743517 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 6 Aug 2024 11:48:07 +0300 Subject: [PATCH 164/185] getBatchSize --- src/Migration/Destination.php | 3 --- src/Migration/Destinations/Appwrite.php | 5 ----- src/Migration/Source.php | 15 +++++++++------ src/Migration/Sources/Appwrite.php | 7 ++++++- src/Migration/Target.php | 5 ----- src/Migration/Transfer.php | 3 --- 6 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/Migration/Destination.php b/src/Migration/Destination.php index 3c1146e..39d1b39 100644 --- a/src/Migration/Destination.php +++ b/src/Migration/Destination.php @@ -27,13 +27,11 @@ public function setSource(Source $source): self * @param array $resources Resources to transfer * @param callable $callback Callback to run after transfer * @param string $rootResourceId Root resource ID, If enabled you can only transfer a single root resource - * @param int $batchSize The number of resources to transfer in a single batch */ public function run( array $resources, callable $callback, string $rootResourceId = '', - int $batchSize = 100, ): void { $this->source->run( $resources, @@ -41,7 +39,6 @@ function (array $resources) use ($callback) { $this->import($resources, $callback); }, $rootResourceId, - $batchSize ); } diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 8c81131..8a474c9 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -1340,9 +1340,4 @@ private function importDeployment(Deployment $deployment): Resource return $deployment; } - - public function getBatchSize(): int - { - return 200; - } } diff --git a/src/Migration/Source.php b/src/Migration/Source.php index 6bd38c2..7841761 100644 --- a/src/Migration/Source.php +++ b/src/Migration/Source.php @@ -27,7 +27,7 @@ public function callback(array $resources): void * @param callable $callback Callback to run after transfer * @param string $rootResourceId Root resource ID, If enabled you can only transfer a single root resource */ - public function run(array $resources, callable $callback, string $rootResourceId = '', int $batchSize = 100): void + public function run(array $resources, callable $callback, string $rootResourceId = ''): void { $this->rootResourceId = $rootResourceId; @@ -46,21 +46,20 @@ public function run(array $resources, callable $callback, string $rootResourceId $this->cache->addAll($prunedResources); }; - $batchSize = $this->getBatchSize(); - - $this->exportResources($resources, $batchSize); + $this->exportResources($resources); } /** * Export Resources * * @param array $resources Resources to export - * @param int $batchSize Max 100 */ - public function exportResources(array $resources, int $batchSize): void + public function exportResources(array $resources): void { // Convert Resources back into their relevant groups + $batchSize = $this->getBatchSize(); + $groups = []; foreach ($resources as $resource) { if (\in_array($resource, Transfer::GROUP_AUTH_RESOURCES)) { @@ -96,6 +95,10 @@ public function exportResources(array $resources, int $batchSize): void } } } + public function getBatchSize(): int + { + return 100; + } /** * Export Auth Group diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index e3e34c0..8277dfb 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -1468,4 +1468,9 @@ private function exportDeploymentData(Func $func, array $deployment): void } } } -} + + public function getBatchSize(): int + { + return 250; + } +} \ No newline at end of file diff --git a/src/Migration/Target.php b/src/Migration/Target.php index e4f0140..a8165c2 100644 --- a/src/Migration/Target.php +++ b/src/Migration/Target.php @@ -225,9 +225,4 @@ public function addWarning(Warning $warning): void public function shutdown(): void { } - - public function getBatchSize(): int - { - return 100; - } } diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index ce5f0ec..af460b0 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -181,14 +181,12 @@ public function getStatusCounters(): array * @param array $resources Resources to transfer * @param callable $callback Callback to run after transfer * @param string|null $rootResourceId Root resource ID, If enabled you can only transfer a single root resource - * @param int $batchSize * @throws \Exception */ public function run( array $resources, callable $callback, string $rootResourceId = null, - int $batchSize = 100 ): void { // Allows you to push entire groups if you want. $computedResources = []; @@ -217,7 +215,6 @@ public function run( $computedResources, $callback, $rootResourceId, - $batchSize ); } From ae6151e7a65fc5bf253461cd8db909001d7549e9 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 6 Aug 2024 12:32:45 +0300 Subject: [PATCH 165/185] Add rootResourceType --- src/Migration/Destination.php | 2 ++ src/Migration/Source.php | 3 ++- src/Migration/Sources/Appwrite.php | 10 +++++----- src/Migration/Target.php | 2 ++ src/Migration/Transfer.php | 6 +++++- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Migration/Destination.php b/src/Migration/Destination.php index 39d1b39..3981f39 100644 --- a/src/Migration/Destination.php +++ b/src/Migration/Destination.php @@ -32,6 +32,7 @@ public function run( array $resources, callable $callback, string $rootResourceId = '', + string $rootResourceType = '', ): void { $this->source->run( $resources, @@ -39,6 +40,7 @@ function (array $resources) use ($callback) { $this->import($resources, $callback); }, $rootResourceId, + $rootResourceType, ); } diff --git a/src/Migration/Source.php b/src/Migration/Source.php index 7841761..3a67291 100644 --- a/src/Migration/Source.php +++ b/src/Migration/Source.php @@ -27,9 +27,10 @@ public function callback(array $resources): void * @param callable $callback Callback to run after transfer * @param string $rootResourceId Root resource ID, If enabled you can only transfer a single root resource */ - public function run(array $resources, callable $callback, string $rootResourceId = ''): void + public function run(array $resources, callable $callback, string $rootResourceId = '', string $rootResourceType = ''): void { $this->rootResourceId = $rootResourceId; + $this->rootResourceType = $rootResourceType; $this->transferCallback = function (array $returnedResources) use ($callback, $resources) { $prunedResources = []; diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 8277dfb..2b1aa1d 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -377,7 +377,7 @@ private function exportUsers(int $batchSize): void $queries = [Query::limit($batchSize)]; - if (!empty($this->rootResourceId)) { + if ($this->rootResourceId !== '' && $this->rootResourceType === Resource::TYPE_USER) { $queries[] = Query::equal('$id', $this->rootResourceId); $queries[] = Query::limit(1); } @@ -430,7 +430,7 @@ private function exportTeams(int $batchSize): void $queries = [Query::limit($batchSize)]; - if (!empty($this->rootResourceId)) { + if ($this->rootResourceId !== '' && $this->rootResourceType === Resource::TYPE_TEAM) { $queries[] = Query::equal('$id', $this->rootResourceId); $queries[] = Query::limit(1); } @@ -869,7 +869,7 @@ private function exportDatabases(int $batchSize): void while (true) { $queries = [Query::limit($batchSize)]; - if (!empty($this->rootResourceId)) { + if ($this->rootResourceId !== '' && $this->rootResourceType === Resource::TYPE_DATABASE) { $queries[] = Query::equal('$id', $this->rootResourceId); $queries[] = Query::limit(1); } @@ -1113,7 +1113,7 @@ private function exportBuckets(int $batchSize): void { $queries = []; - if (!empty($this->rootResourceId)) { + if ($this->rootResourceId !== '' && $this->rootResourceType === Resource::TYPE_BUCKET) { $queries[] = Query::equal('$id', $this->rootResourceId); $queries[] = Query::limit(1); } @@ -1281,7 +1281,7 @@ private function exportFunctions(int $batchSize): void $queries = []; - if (!empty($this->rootResourceId)) { + if ($this->rootResourceId !== '' && $this->rootResourceType === Resource::TYPE_FUNCTION) { $queries[] = Query::equal('$id', $this->rootResourceId); $queries[] = Query::limit(1); } diff --git a/src/Migration/Target.php b/src/Migration/Target.php index a8165c2..c0e01f4 100644 --- a/src/Migration/Target.php +++ b/src/Migration/Target.php @@ -33,6 +33,8 @@ abstract class Target protected string $rootResourceId = ''; + protected string $rootResourceType = ''; + abstract public static function getName(): string; abstract public static function getSupportedResources(): array; diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index af460b0..0fac57d 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -187,9 +187,13 @@ public function run( array $resources, callable $callback, string $rootResourceId = null, + string $rootResourceType = null, ): void { // Allows you to push entire groups if you want. $computedResources = []; + $rootResourceId = $rootResourceId ?? ''; + $rootResourceType = $rootResourceType ?? ''; + foreach ($resources as $resource) { if (is_array($resource)) { $computedResources = array_merge($computedResources, $resource); @@ -201,7 +205,7 @@ public function run( $computedResources = array_map('strtolower', $computedResources); // Check we don't have multiple root resources if rootResourceId is set - $rootResourceId = $rootResourceId ?? ''; + if ($rootResourceId) { $rootResourceCount = \count(\array_intersect($computedResources, self::ROOT_RESOURCES)); if ($rootResourceCount > 1) { From f524f51cc87a5b7e3dcc34914024551e326833ae Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 6 Aug 2024 13:08:09 +0300 Subject: [PATCH 166/185] $rootResourceType check --- src/Migration/Transfer.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index 0fac57d..2e4ff8b 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -204,21 +204,22 @@ public function run( $computedResources = array_map('strtolower', $computedResources); - // Check we don't have multiple root resources if rootResourceId is set - - if ($rootResourceId) { - $rootResourceCount = \count(\array_intersect($computedResources, self::ROOT_RESOURCES)); - if ($rootResourceCount > 1) { - throw new \Exception('Multiple root resources found. Only one root resource can be transferred at a time if using $rootResourceId.'); + if ($rootResourceId !== '') { + if ($rootResourceType === '') { + throw new \Exception('Please $rootResourceId while using $rootResourceId'); } + + $computedResources = [$rootResourceType]; } + $this->resources = $computedResources; $this->destination->run( $computedResources, $callback, $rootResourceId, + $rootResourceType, ); } From 9088c29df1767137eecc513e2325fa5201d711ce Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 6 Aug 2024 13:10:50 +0300 Subject: [PATCH 167/185] Remove line --- src/Migration/Transfer.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index 2e4ff8b..c3c6faf 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -212,7 +212,6 @@ public function run( $computedResources = [$rootResourceType]; } - $this->resources = $computedResources; $this->destination->run( From d130355bf42f4d776a997fcac39b1f1acb7ec2cb Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 6 Aug 2024 13:17:38 +0300 Subject: [PATCH 168/185] Add description Resource type --- src/Migration/Sources/Appwrite.php | 1 + src/Migration/Transfer.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 2b1aa1d..7954bf6 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -870,6 +870,7 @@ private function exportDatabases(int $batchSize): void $queries = [Query::limit($batchSize)]; if ($this->rootResourceId !== '' && $this->rootResourceType === Resource::TYPE_DATABASE) { + var_dump('DBG query for rootResourceType TYPE_DATABASE , rootResourceId = ' . $this->rootResourceId); $queries[] = Query::equal('$id', $this->rootResourceId); $queries[] = Query::limit(1); } diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index c3c6faf..15bb17e 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -206,7 +206,7 @@ public function run( if ($rootResourceId !== '') { if ($rootResourceType === '') { - throw new \Exception('Please $rootResourceId while using $rootResourceId'); + throw new \Exception('Resource type must be set when resource ID is set.'); } $computedResources = [$rootResourceType]; From 5b795994af5894c3d6e8042925bae54d105ac92c Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 6 Aug 2024 13:40:24 +0300 Subject: [PATCH 169/185] Add void --- src/Migration/Sources/Firebase.php | 4 ++-- src/Migration/Sources/NHost.php | 4 ++-- src/Migration/Sources/Supabase.php | 2 +- tests/Migration/Unit/Adapters/MockSource.php | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Migration/Sources/Firebase.php b/src/Migration/Sources/Firebase.php index e25a688..015ae73 100644 --- a/src/Migration/Sources/Firebase.php +++ b/src/Migration/Sources/Firebase.php @@ -256,7 +256,7 @@ private function exportUsers(int $batchSize): void } } - protected function exportGroupDatabases(int $batchSize, array $resources) + protected function exportGroupDatabases(int $batchSize, array $resources): void { // Check if Firestore is enabled try { @@ -785,7 +785,7 @@ private function exportFile(File $file): void } } - protected function exportGroupFunctions(int $batchSize, array $resources) + protected function exportGroupFunctions(int $batchSize, array $resources): void { throw new \Exception('Not implemented'); } diff --git a/src/Migration/Sources/NHost.php b/src/Migration/Sources/NHost.php index 49d4d4a..b6e24ae 100644 --- a/src/Migration/Sources/NHost.php +++ b/src/Migration/Sources/NHost.php @@ -688,7 +688,7 @@ private function convertIndex(array $index, Collection $collection): Index|false } } - protected function exportGroupStorage(int $batchSize, array $resources) + protected function exportGroupStorage(int $batchSize, array $resources): void { try { if (\in_array(Resource::TYPE_BUCKET, $resources)) { @@ -839,7 +839,7 @@ private function exportFile(File $file): void } } - protected function exportGroupFunctions(int $batchSize, array $resources) + protected function exportGroupFunctions(int $batchSize, array $resources): void { throw new \Exception('Not Implemented'); } diff --git a/src/Migration/Sources/Supabase.php b/src/Migration/Sources/Supabase.php index b141516..e06af13 100644 --- a/src/Migration/Sources/Supabase.php +++ b/src/Migration/Sources/Supabase.php @@ -420,7 +420,7 @@ private function convertMimes(array $mimes): array return $extensions; } - protected function exportGroupStorage(int $batchSize, array $resources) + protected function exportGroupStorage(int $batchSize, array $resources): void { try { if (\in_array(Resource::TYPE_BUCKET, $resources)) { diff --git a/tests/Migration/Unit/Adapters/MockSource.php b/tests/Migration/Unit/Adapters/MockSource.php index 742469c..66f22ac 100644 --- a/tests/Migration/Unit/Adapters/MockSource.php +++ b/tests/Migration/Unit/Adapters/MockSource.php @@ -91,7 +91,7 @@ public function report(array $resources = []): array * @param int $batchSize Max 100 * @param string[] $resources Resources to export */ - protected function exportGroupAuth(int $batchSize, array $resources) + protected function exportGroupAuth(int $batchSize, array $resources): void { foreach (Transfer::GROUP_AUTH_RESOURCES as $resource) { if (!\in_array($resource, $resources)) { @@ -108,7 +108,7 @@ protected function exportGroupAuth(int $batchSize, array $resources) * @param int $batchSize Max 100 * @param string[] $resources Resources to export */ - protected function exportGroupDatabases(int $batchSize, array $resources) + protected function exportGroupDatabases(int $batchSize, array $resources): void { foreach (Transfer::GROUP_DATABASES_RESOURCES as $resource) { if (!\in_array($resource, $resources)) { @@ -125,7 +125,7 @@ protected function exportGroupDatabases(int $batchSize, array $resources) * @param int $batchSize Max 5 * @param string[] $resources Resources to export */ - protected function exportGroupStorage(int $batchSize, array $resources) + protected function exportGroupStorage(int $batchSize, array $resources): void { foreach (Transfer::GROUP_STORAGE_RESOURCES as $resource) { if (!\in_array($resource, $resources)) { @@ -142,7 +142,7 @@ protected function exportGroupStorage(int $batchSize, array $resources) * @param int $batchSize Max 100 * @param string[] $resources Resources to export */ - protected function exportGroupFunctions(int $batchSize, array $resources) + protected function exportGroupFunctions(int $batchSize, array $resources): void { foreach (Transfer::GROUP_FUNCTIONS_RESOURCES as $resource) { if (!\in_array($resource, $resources)) { From 8b98bb4c5357bfe8e607b942931ec7dc652ec128 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 6 Aug 2024 17:23:31 +0300 Subject: [PATCH 170/185] Check $rootResources --- src/Migration/Transfer.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index 15bb17e..35544dd 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -209,7 +209,19 @@ public function run( throw new \Exception('Resource type must be set when resource ID is set.'); } - $computedResources = [$rootResourceType]; + $rootResources = \array_intersect($computedResources, self::ROOT_RESOURCES); + + if (\count($rootResources) > 1) { + throw new \Exception('Multiple root resources found. Only one root resource can be transferred at a time if using $rootResourceId.'); + } + + if (\count($rootResources) === 0) { + throw new \Exception('No root resources found.'); + } + + if (\end($rootResources) !== $rootResourceType) { + throw new \Exception('$rootResourceType Does not match'); + } } $this->resources = $computedResources; From 7f8fc814bc9a4d71408493e0defdcc5e2513f44d Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 6 Aug 2024 17:34:18 +0300 Subject: [PATCH 171/185] Check $rootResources --- src/Migration/Transfer.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index 35544dd..6ea4255 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -209,6 +209,10 @@ public function run( throw new \Exception('Resource type must be set when resource ID is set.'); } + if(!in_array($rootResourceType, self::ROOT_RESOURCES)){ + throw new \Exception('Resource type must be one of ' . implode(', ', self::ROOT_RESOURCES)); + } + $rootResources = \array_intersect($computedResources, self::ROOT_RESOURCES); if (\count($rootResources) > 1) { @@ -219,9 +223,7 @@ public function run( throw new \Exception('No root resources found.'); } - if (\end($rootResources) !== $rootResourceType) { - throw new \Exception('$rootResourceType Does not match'); - } + } $this->resources = $computedResources; From 0fe0e9abc2211c11624d544c23bf46def8efb5f2 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 6 Aug 2024 17:37:04 +0300 Subject: [PATCH 172/185] Check $rootResources --- src/Migration/Transfer.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index 6ea4255..d0aa220 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -222,8 +222,6 @@ public function run( if (\count($rootResources) === 0) { throw new \Exception('No root resources found.'); } - - } $this->resources = $computedResources; From 274115b3246e40b9235f259288a9bbc3f403227c Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 7 Aug 2024 15:56:55 +0300 Subject: [PATCH 173/185] Add error hook --- src/Migration/Target.php | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Migration/Target.php b/src/Migration/Target.php index c0e01f4..56ca183 100644 --- a/src/Migration/Target.php +++ b/src/Migration/Target.php @@ -2,6 +2,8 @@ namespace Utopia\Migration; +use Utopia\Database\Document; + abstract class Target { /** @@ -70,12 +72,11 @@ abstract public function report(array $resources = []): array; /** * Make an API call * - * @param string $method - * @param string $path - * @param array $headers - * @param array $params - * @param array $responseHeaders + * @param array $headers + * @param array $params + * @param array $responseHeaders * @return array|string + * * @throws \Exception */ protected function call( @@ -88,10 +89,10 @@ protected function call( $headers = \array_merge($this->headers, $headers); $ch = \curl_init(( \str_contains($path, 'http') - ? $path.(($method == 'GET' && ! empty($params)) ? '?' . \http_build_query($params) : '') + ? $path.(($method == 'GET' && ! empty($params)) ? '?'.\http_build_query($params) : '') : $this->endpoint.$path.( ($method == 'GET' && ! empty($params)) - ? '?' . \http_build_query($params) + ? '?'.\http_build_query($params) : '' ) )); @@ -164,8 +165,7 @@ protected function call( /** * Flatten params array to PHP multiple format * - * @param array $data - * @param string $prefix + * @param array $data * @return array */ protected function flatten(array $data, string $prefix = ''): array @@ -224,7 +224,10 @@ public function addWarning(Warning $warning): void /** * Completion callback */ - public function shutdown(): void - { - } + public function shutdown(): void {} + + /** + * Completion callback + */ + public function error(Document $migration): void {} } From d579f344cea485b6519b3050f41d976c62c3c4ba Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 7 Aug 2024 16:33:05 +0300 Subject: [PATCH 174/185] Error no params --- src/Migration/Target.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Migration/Target.php b/src/Migration/Target.php index 56ca183..813ee6e 100644 --- a/src/Migration/Target.php +++ b/src/Migration/Target.php @@ -229,5 +229,5 @@ public function shutdown(): void {} /** * Completion callback */ - public function error(Document $migration): void {} + public function error(): void {} } From 2bb9c1bf89eff0e90e47f3844cb43eda80e6a069 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 20 Aug 2024 16:02:03 +0300 Subject: [PATCH 175/185] lint --- src/Migration/Sources/Appwrite.php | 2 +- src/Migration/Target.php | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 0ec2e06..6edf209 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -1477,4 +1477,4 @@ public function getBatchSize(): int { return 250; } -} \ No newline at end of file +} diff --git a/src/Migration/Target.php b/src/Migration/Target.php index 813ee6e..30e4c8b 100644 --- a/src/Migration/Target.php +++ b/src/Migration/Target.php @@ -2,8 +2,6 @@ namespace Utopia\Migration; -use Utopia\Database\Document; - abstract class Target { /** @@ -224,10 +222,14 @@ public function addWarning(Warning $warning): void /** * Completion callback */ - public function shutdown(): void {} + public function shutdown(): void + { + } /** * Completion callback */ - public function error(): void {} + public function error(): void + { + } } From 0cbd135fbdc782fce4bf3da2e41c024a898db1ed Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 22 Aug 2024 19:38:28 +1200 Subject: [PATCH 176/185] Fix lint --- src/Migration/Transfer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index d0aa220..44463f7 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -209,7 +209,7 @@ public function run( throw new \Exception('Resource type must be set when resource ID is set.'); } - if(!in_array($rootResourceType, self::ROOT_RESOURCES)){ + if(!in_array($rootResourceType, self::ROOT_RESOURCES)) { throw new \Exception('Resource type must be one of ' . implode(', ', self::ROOT_RESOURCES)); } From 0791f429761bfd98e33707bbe47ad60f117dd874 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 22 Aug 2024 22:59:37 +1200 Subject: [PATCH 177/185] Fix tests --- .github/workflows/tests.yml | 8 +- Dockerfile | 8 +- composer.json | 2 +- composer.lock | 126 +++++++++--------- tests/Migration/Unit/General/TransferTest.php | 11 +- 5 files changed, 81 insertions(+), 74 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2bc1aa5..d6f3380 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,7 +1,9 @@ name: Tests on: pull_request: - push: { branches: [main] } + push: + branches: + - main jobs: tests: @@ -12,10 +14,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Run Tests run: | - docker-compose up -d --build + docker compose up -d --build sleep 5 docker compose exec tests php vendor/bin/phpunit \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index a5a5269..bc56b8d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,23 @@ -FROM supabase/postgres:15.1.0.96 as supabase-db +FROM supabase/postgres:15.1.0.96 AS supabase-db COPY tests/Migration/resources/supabase/1_globals.sql /docker-entrypoint-initdb.d/1_globals.sql COPY tests/Migration/resources/supabase/2_main.sql /docker-entrypoint-initdb.d/2_main.sql RUN rm -rf /docker-entrypoint-initdb.d/migrate.sh -FROM postgres:alpine3.18 as nhost-db +FROM postgres:alpine3.18 AS nhost-db COPY tests/Migration/resources/nhost/1_globals.sql /docker-entrypoint-initdb.d/1_globals.sql COPY tests/Migration/resources/nhost/2_main.sql /docker-entrypoint-initdb.d/2_main.sql -FROM composer:2.0 as composer +FROM composer:2.0 AS composer COPY composer.json /app COPY composer.lock /app RUN composer install --ignore-platform-reqs -FROM php:8.3.3-cli-alpine3.19 as tests +FROM php:8.3.10-cli-alpine3.20 AS tests # Postgres RUN set -ex \ diff --git a/composer.json b/composer.json index 99b7cb8..83ab780 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "ext-curl": "*", "ext-openssl": "*", "appwrite/appwrite": "11.1.*", - "utopia-php/database": "0.50.*", + "utopia-php/database": "0.52.*", "utopia-php/storage": "0.18.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*" diff --git a/composer.lock b/composer.lock index fa35220..7f8b344 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": "7162a38cd3804bfd37499285693dd65c", + "content-hash": "710d0646ba3d1752ef9e2580d7bca33d", "packages": [ { "name": "appwrite/appwrite", @@ -308,16 +308,16 @@ }, { "name": "utopia-php/database", - "version": "0.50.1", + "version": "0.52.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "1745147bef29a9bddf5dd03fd9174ec29e2c26f0" + "reference": "0b48921dd5e9e07529983f954cf987e7d4461f6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/1745147bef29a9bddf5dd03fd9174ec29e2c26f0", - "reference": "1745147bef29a9bddf5dd03fd9174ec29e2c26f0", + "url": "https://api.github.com/repos/utopia-php/database/zipball/0b48921dd5e9e07529983f954cf987e7d4461f6e", + "reference": "0b48921dd5e9e07529983f954cf987e7d4461f6e", "shasum": "" }, "require": { @@ -329,14 +329,14 @@ "utopia-php/mongo": "0.3.*" }, "require-dev": { - "fakerphp/faker": "^1.14", - "laravel/pint": "1.13.*", - "pcov/clobber": "^2.0", - "phpstan/phpstan": "1.10.*", - "phpunit/phpunit": "^9.4", - "rregeer/phpunit-coverage-check": "^0.3.1", - "swoole/ide-helper": "4.8.0", - "utopia-php/cli": "^0.14.0" + "fakerphp/faker": "1.23.*", + "laravel/pint": "1.17.*", + "pcov/clobber": "2.0.*", + "phpstan/phpstan": "1.11.*", + "phpunit/phpunit": "9.6.*", + "rregeer/phpunit-coverage-check": "0.3.*", + "swoole/ide-helper": "5.1.3", + "utopia-php/cli": "0.14.*" }, "type": "library", "autoload": { @@ -358,9 +358,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.50.1" + "source": "https://github.com/utopia-php/database/tree/0.52.0" }, - "time": "2024-07-26T11:56:05+00:00" + "time": "2024-08-21T08:11:14+00:00" }, { "name": "utopia-php/dsn", @@ -411,16 +411,16 @@ }, { "name": "utopia-php/framework", - "version": "0.33.6", + "version": "0.33.8", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "8fe57da0cecd57e3b17cd395b4a666a24f4c07a6" + "reference": "a7f577540a25cb90896fef2b64767bf8d700f3c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/8fe57da0cecd57e3b17cd395b4a666a24f4c07a6", - "reference": "8fe57da0cecd57e3b17cd395b4a666a24f4c07a6", + "url": "https://api.github.com/repos/utopia-php/http/zipball/a7f577540a25cb90896fef2b64767bf8d700f3c5", + "reference": "a7f577540a25cb90896fef2b64767bf8d700f3c5", "shasum": "" }, "require": { @@ -450,9 +450,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.6" + "source": "https://github.com/utopia-php/http/tree/0.33.8" }, - "time": "2024-03-21T18:10:57+00:00" + "time": "2024-08-15T14:10:09+00:00" }, { "name": "utopia-php/mongo", @@ -691,16 +691,16 @@ }, { "name": "laravel/pint", - "version": "v1.17.0", + "version": "v1.17.2", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5" + "reference": "e8a88130a25e3f9d4d5785e6a1afca98268ab110" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5", - "reference": "4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5", + "url": "https://api.github.com/repos/laravel/pint/zipball/e8a88130a25e3f9d4d5785e6a1afca98268ab110", + "reference": "e8a88130a25e3f9d4d5785e6a1afca98268ab110", "shasum": "" }, "require": { @@ -711,13 +711,13 @@ "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.59.3", - "illuminate/view": "^10.48.12", - "larastan/larastan": "^2.9.7", + "friendsofphp/php-cs-fixer": "^3.61.1", + "illuminate/view": "^10.48.18", + "larastan/larastan": "^2.9.8", "laravel-zero/framework": "^10.4.0", "mockery/mockery": "^1.6.12", "nunomaduro/termwind": "^1.15.1", - "pestphp/pest": "^2.34.8" + "pestphp/pest": "^2.35.0" }, "bin": [ "builds/pint" @@ -753,7 +753,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2024-07-23T16:40:20+00:00" + "time": "2024-08-06T15:11:54+00:00" }, { "name": "myclabs/deep-copy", @@ -1068,16 +1068,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.11.8", + "version": "1.11.11", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec" + "reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec", - "reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/707c2aed5d8d0075666e673a5e71440c1d01a5a3", + "reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3", "shasum": "" }, "require": { @@ -1122,36 +1122,36 @@ "type": "github" } ], - "time": "2024-07-24T07:01:22+00:00" + "time": "2024-08-19T14:37:29+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "11.0.5", + "version": "11.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "19b6365ab8b59a64438c0c3f4241feeb480c9861" + "reference": "ebdffc9e09585dafa71b9bffcdb0a229d4704c45" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/19b6365ab8b59a64438c0c3f4241feeb480c9861", - "reference": "19b6365ab8b59a64438c0c3f4241feeb480c9861", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ebdffc9e09585dafa71b9bffcdb0a229d4704c45", + "reference": "ebdffc9e09585dafa71b9bffcdb0a229d4704c45", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^5.0", + "nikic/php-parser": "^5.1.0", "php": ">=8.2", - "phpunit/php-file-iterator": "^5.0", - "phpunit/php-text-template": "^4.0", - "sebastian/code-unit-reverse-lookup": "^4.0", - "sebastian/complexity": "^4.0", - "sebastian/environment": "^7.0", - "sebastian/lines-of-code": "^3.0", - "sebastian/version": "^5.0", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.0", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.1", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { "phpunit/phpunit": "^11.0" @@ -1163,7 +1163,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "11.0-dev" + "dev-main": "11.0.x-dev" } }, "autoload": { @@ -1192,7 +1192,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.5" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.6" }, "funding": [ { @@ -1200,7 +1200,7 @@ "type": "github" } ], - "time": "2024-07-03T05:05:37+00:00" + "time": "2024-08-22T04:37:56+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1449,16 +1449,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.2.8", + "version": "11.2.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a7a29e8d3113806f18f99d670f580a30e8ffff39" + "reference": "c197bbaaca360efda351369bf1fd9cc1ca6bcbf7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a7a29e8d3113806f18f99d670f580a30e8ffff39", - "reference": "a7a29e8d3113806f18f99d670f580a30e8ffff39", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c197bbaaca360efda351369bf1fd9cc1ca6bcbf7", + "reference": "c197bbaaca360efda351369bf1fd9cc1ca6bcbf7", "shasum": "" }, "require": { @@ -1529,7 +1529,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.2.8" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.2.9" }, "funding": [ { @@ -1545,7 +1545,7 @@ "type": "tidelift" } ], - "time": "2024-07-18T14:56:37+00:00" + "time": "2024-07-30T11:09:23+00:00" }, { "name": "sebastian/cli-parser", @@ -1719,16 +1719,16 @@ }, { "name": "sebastian/comparator", - "version": "6.0.1", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "131942b86d3587291067a94f295498ab6ac79c20" + "reference": "450d8f237bd611c45b5acf0733ce43e6bb280f81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/131942b86d3587291067a94f295498ab6ac79c20", - "reference": "131942b86d3587291067a94f295498ab6ac79c20", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/450d8f237bd611c45b5acf0733ce43e6bb280f81", + "reference": "450d8f237bd611c45b5acf0733ce43e6bb280f81", "shasum": "" }, "require": { @@ -1784,7 +1784,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.0.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.0.2" }, "funding": [ { @@ -1792,7 +1792,7 @@ "type": "github" } ], - "time": "2024-07-03T04:48:07+00:00" + "time": "2024-08-12T06:07:25+00:00" }, { "name": "sebastian/complexity", diff --git a/tests/Migration/Unit/General/TransferTest.php b/tests/Migration/Unit/General/TransferTest.php index 54b87cc..0283437 100644 --- a/tests/Migration/Unit/General/TransferTest.php +++ b/tests/Migration/Unit/General/TransferTest.php @@ -37,9 +37,9 @@ public function testRootResourceId(): void */ try { $this->transfer->run([Resource::TYPE_USER, Resource::TYPE_DATABASE], function () {}, 'rootResourceId'); - $this->fail('Multiple Root resources should not be allowed'); + $this->fail('Multiple root resources should not be allowed'); } catch (\Exception $e) { - $this->assertEquals('Multiple root resources found. Only one root resource can be transferred at a time if using $rootResourceId.', $e->getMessage()); + $this->assertEquals('Resource type must be set when resource ID is set.', $e->getMessage()); } $this->source->pushMockResource(new Database('test', 'test')); @@ -48,7 +48,12 @@ public function testRootResourceId(): void /** * TEST FOR SUCCESS */ - $this->transfer->run([Resource::TYPE_DATABASE], function () {}, 'test'); + $this->transfer->run( + [Resource::TYPE_DATABASE], + function () {}, + 'test', + Resource::TYPE_DATABASE + ); $this->assertCount(1, $this->destination->getResourceTypeData(Transfer::GROUP_DATABASES, Resource::TYPE_DATABASE)); $database = $this->destination->getResourceById(Transfer::GROUP_DATABASES, Resource::TYPE_DATABASE, 'test'); From 676600b8c41a9622f1a164d308d95a7b13d52659 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 22 Aug 2024 23:01:32 +1200 Subject: [PATCH 178/185] Update PHP version --- composer.json | 2 +- composer.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 83ab780..450cd5c 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "check": "./vendor/bin/phpstan analyse --level=8 --memory-limit 512M" }, "require": { - "php": "8.3", + "php": "8.3.*", "ext-curl": "*", "ext-openssl": "*", "appwrite/appwrite": "11.1.*", diff --git a/composer.lock b/composer.lock index 7f8b344..d37ed66 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": "710d0646ba3d1752ef9e2580d7bca33d", + "content-hash": "09c49772fb03e8a2c305a6e24f3e2bf7", "packages": [ { "name": "appwrite/appwrite", @@ -2819,7 +2819,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "8.3", + "php": "8.3.*", "ext-curl": "*", "ext-openssl": "*" }, From 9376205c3300e6b2e5456d6590c1d2c1d86032b5 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 22 Aug 2024 23:02:10 +1200 Subject: [PATCH 179/185] Remove redundant header --- src/Migration/Destinations/Appwrite.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 66c0018..a699185 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -83,8 +83,7 @@ public function __construct( $this->client = (new Client()) ->setEndpoint($endpoint) ->setProject($project) - ->setKey($key) - ->addHeader('x-appwrite-preserve-dates', 'true'); + ->setKey($key); $this->functions = new Functions($this->client); $this->storage = new Storage($this->client); From e1c87520d18e014d135ce3746f3e743b9cd2348a Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 22 Aug 2024 23:47:21 +1200 Subject: [PATCH 180/185] Review --- src/Migration/Destinations/Appwrite.php | 27 ++++++++----------------- src/Migration/Sources/Appwrite.php | 9 ++------- src/Migration/Target.php | 2 +- 3 files changed, 11 insertions(+), 27 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index a699185..05d71cd 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -854,8 +854,6 @@ protected function createIndex(Index $resource): bool */ protected function createDocument(Document $resource, bool $isLast): bool { - $this->database->setPreserveDates(true); - // Check if document has already been created $exists = \array_key_exists( $resource->getId(), @@ -865,7 +863,7 @@ protected function createDocument(Document $resource, bool $isLast): bool if ($exists) { $resource->setStatus( Resource::STATUS_SKIPPED, - 'Document has been already created by relationship' + 'Document has already been created' ); return false; } @@ -877,21 +875,10 @@ protected function createDocument(Document $resource, bool $isLast): bool if ($isLast) { try { - - //$databaseInternalId = $resource->getCollection()->getDatabase()->getInternalId(); - //$collectionInternalId = $resource->getCollection()->getInternalId(); - - /** - * Make this use cache! - */ $database = $this->database->getDocument( 'databases', $resource->getCollection()->getDatabase()->getId(), ); - - /** - * Make this use cache! - */ $collection = $this->database->getDocument( 'database_' . $database->getInternalId(), $resource->getCollection()->getId(), @@ -900,16 +887,18 @@ protected function createDocument(Document $resource, bool $isLast): bool $databaseInternalId = $database->getInternalId(); $collectionInternalId = $collection->getInternalId(); - $this->database->createDocuments( - 'database_' . $databaseInternalId . '_collection_' . $collectionInternalId, - $this->documentBuffer - ); + $this->database + ->setPreserveDates(true) + ->createDocuments( + 'database_' . $databaseInternalId . '_collection_' . $collectionInternalId, + $this->documentBuffer + ); } finally { $this->documentBuffer = []; + $this->database->setPreserveDates(false); } } - $this->database->setPreserveDates(false); return true; } diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 6edf209..db78340 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -660,11 +660,9 @@ private function exportDocuments(int $batchSize): void ); foreach ($response['documents'] as $document) { - /** - * Hack for many 2 many - */ + // HACK: Handle many to many if(!empty($manyToMany)) { - $stack = ['$id']; // Adding $id becuase can't select only relations + $stack = ['$id']; // Adding $id because we can't select only relations foreach ($manyToMany as $relation) { $stack[] = $relation . '.$id'; } @@ -683,9 +681,6 @@ private function exportDocuments(int $batchSize): void } } } - /** - * end Hack for many 2 many - */ $id = $document['$id']; $permissions = $document['$permissions']; diff --git a/src/Migration/Target.php b/src/Migration/Target.php index 30e4c8b..664f5c1 100644 --- a/src/Migration/Target.php +++ b/src/Migration/Target.php @@ -227,7 +227,7 @@ public function shutdown(): void } /** - * Completion callback + * Error callback */ public function error(): void { From ea6529bd0991b157f5006f54cebd7d21e2195ad2 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 22 Aug 2024 18:37:50 +0300 Subject: [PATCH 181/185] database 50 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 450cd5c..b121efa 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "ext-curl": "*", "ext-openssl": "*", "appwrite/appwrite": "11.1.*", - "utopia-php/database": "0.52.*", + "utopia-php/database": "0.50.*", "utopia-php/storage": "0.18.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*" From 7fb236459f4b86813d3df261a557340d98b00544 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 22 Aug 2024 18:50:59 +0300 Subject: [PATCH 182/185] 0.49 db --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index b121efa..7df5bf7 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "ext-curl": "*", "ext-openssl": "*", "appwrite/appwrite": "11.1.*", - "utopia-php/database": "0.50.*", + "utopia-php/database": "0.49.*", "utopia-php/storage": "0.18.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*" From 04ad7e4a2d73af911f7fa33b0681311ef0762379 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 22 Aug 2024 18:56:21 +0300 Subject: [PATCH 183/185] database 50 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7df5bf7..b121efa 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "ext-curl": "*", "ext-openssl": "*", "appwrite/appwrite": "11.1.*", - "utopia-php/database": "0.49.*", + "utopia-php/database": "0.50.*", "utopia-php/storage": "0.18.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*" From 0fc22342b90a99466cf64eeadfbf524fc2c3fc91 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 22 Aug 2024 19:09:35 +0300 Subject: [PATCH 184/185] back to 52 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index b121efa..450cd5c 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "ext-curl": "*", "ext-openssl": "*", "appwrite/appwrite": "11.1.*", - "utopia-php/database": "0.50.*", + "utopia-php/database": "0.52.*", "utopia-php/storage": "0.18.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*" From 420cb3be7f8919fe41de35ff1b866fd7932f7384 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 23 Aug 2024 15:40:29 +1200 Subject: [PATCH 185/185] Remove var dump --- src/Migration/Sources/Appwrite.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index db78340..07789a4 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -868,7 +868,6 @@ private function exportDatabases(int $batchSize): void $queries = [Query::limit($batchSize)]; if ($this->rootResourceId !== '' && $this->rootResourceType === Resource::TYPE_DATABASE) { - var_dump('DBG query for rootResourceType TYPE_DATABASE , rootResourceId = ' . $this->rootResourceId); $queries[] = Query::equal('$id', $this->rootResourceId); $queries[] = Query::limit(1); }