diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php index 92c5378425..0b6a34ee95 100644 --- a/src/Appwrite/Migration/Migration.php +++ b/src/Appwrite/Migration/Migration.php @@ -45,7 +45,7 @@ abstract class Migration '0.14.0' => 'V13', '0.14.1' => 'V13', '0.14.2' => 'V13', - '0.15.0' => 'V13' + '0.15.0' => 'V14' ]; /** @@ -228,6 +228,87 @@ abstract class Migration } } + /** + * Creates attribute from collections.php + * + * @param \Utopia\Database\Database $database + * @param string $collectionId + * @param string $attributeId + * @return void + * @throws \Exception + * @throws \Utopia\Database\Exception\Duplicate + * @throws \Utopia\Database\Exception\Limit + */ + public function createAttributeFromCollection(Database $database, string $collectionId, string $attributeId): void + { + $collection = Config::getParam('collections', [])[$collectionId] ?? null; + + if (is_null($collection)) { + throw new Exception("Collection {$collectionId} not found"); + } + $attributes = $collection['attributes']; + + $attributeKey = array_search($attributeId, array_column($attributes, '$id')); + + if (!$attributeKey) { + throw new Exception("Attribute {$attributeId} not found"); + } + + $attribute = $attributes[$attributeKey]; + + $database->createAttribute( + collection: $collectionId, + id: $attributeId, + type: $attribute['type'], + size: $attribute['size'], + required: $attribute['required'] ?? false, + default: $attribute['default'] ?? null, + signed: $attribute['signed'] ?? false, + array: $attribute['array'] ?? false, + format: $attribute['format'] ?? '', + formatOptions: $attribute['formatOptions'] ?? [], + filters: $attribute['filters'] ?? [], + ); + } + + /** + * Creates index from collections.php + * + * @param \Utopia\Database\Database $database + * @param string $collectionId + * @param string $indexId + * @return void + * @throws \Exception + * @throws \Utopia\Database\Exception\Duplicate + * @throws \Utopia\Database\Exception\Limit + */ + public function createIndexFromCollection(Database $database, string $collectionId, string $indexId): void + { + $collection = Config::getParam('collections', [])[$collectionId] ?? null; + + if (is_null($collection)) { + throw new Exception("Collection {$collectionId} not found"); + } + $indexes = $collection['indexes']; + + $indexKey = array_search($indexId, array_column($indexes, '$id')); + + if (!$indexKey) { + throw new Exception("Attribute {$indexId} not found"); + } + + $index = $indexes[$indexKey]; + + $database->createIndex( + collection: $collectionId, + id: $indexId, + type: $index['type'], + attributes: $index['attributes'], + lengths: $index['lengths'] ?? [], + orders: $index['orders'] ?? [] + ); + } + /** * Executes migration for set project. */ diff --git a/src/Appwrite/Migration/Version/V14.php b/src/Appwrite/Migration/Version/V14.php new file mode 100644 index 0000000000..fb1fc20a32 --- /dev/null +++ b/src/Appwrite/Migration/Version/V14.php @@ -0,0 +1,290 @@ +project->getAttribute('name') . ' (' . $this->project->getId() . ')'); + Console::info('Migrating Collections'); + $this->migrateCollections(); + Console::info('Migrating Documents'); + $this->forEachDocument([$this, 'fixDocument']); + } + + /** + * Migrate all Collections. + * + * @return void + */ + protected function migrateCollections(): void + { + foreach ($this->collections as $collection) { + $id = $collection['$id']; + + Console::log("- {$id}"); + switch ($id) { + case 'attributes': + case 'indexes': + try { + /** + * Create 'collectionInternalId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'collectionInternalId'); + } catch (\Throwable $th) { + Console::warning("'collectionInternalId' from {$id}: {$th->getMessage()}"); + } + + try { + /** + * Re-Create '_key_collection' index + */ + $this->projectDB->deleteIndex($id, '_key_collection'); + $this->createIndexFromCollection($this->projectDB, $id, '_key_collection'); + } catch (\Throwable $th) { + Console::warning("'_key_collection' from {$id}: {$th->getMessage()}"); + } + + break; + case 'projects': + try { + /** + * Create 'teamInternalId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'teamInternalId'); + } catch (\Throwable $th) { + Console::warning("'collectionInternalId' from {$id}: {$th->getMessage()}"); + } + + break; + case 'platforms': + try { + /** + * Create 'projectInternalId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'projectInternalId'); + } catch (\Throwable $th) { + Console::warning("'collectionInternalId' from {$id}: {$th->getMessage()}"); + } + try { + /** + * Re-Create '_key_project' index + */ + $this->projectDB->deleteIndex($id, '_key_project'); + $this->createIndexFromCollection($this->projectDB, $id, '_key_project'); + } catch (\Throwable $th) { + Console::warning("'_key_project' from {$id}: {$th->getMessage()}"); + } + + break; + case 'domains': + try { + /** + * Create 'projectInternalId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'projectInternalId'); + } catch (\Throwable $th) { + Console::warning("'projectInternalId' from {$id}: {$th->getMessage()}"); + } + try { + /** + * Re-Create '_key_project' index + */ + $this->projectDB->deleteIndex($id, '_key_project'); + $this->createIndexFromCollection($this->projectDB, $id, '_key_project'); + } catch (\Throwable $th) { + Console::warning("'_key_project' from {$id}: {$th->getMessage()}"); + } + + break; + case 'keys': + try { + /** + * Create 'projectInternalId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'projectInternalId'); + } catch (\Throwable $th) { + Console::warning("'projectInternalId' from {$id}: {$th->getMessage()}"); + } + try { + /** + * Create 'expire' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'expire'); + } catch (\Throwable $th) { + Console::warning("'expire' from {$id}: {$th->getMessage()}"); + } + try { + /** + * Re-Create '_key_project' index + */ + $this->projectDB->deleteIndex($id, '_key_project'); + $this->createIndexFromCollection($this->projectDB, $id, '_key_project'); + } catch (\Throwable $th) { + Console::warning("'_key_project' from {$id}: {$th->getMessage()}"); + } + + break; + case 'webhooks': + try { + /** + * Create 'projectInternalId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'projectInternalId'); + } catch (\Throwable $th) { + Console::warning("'projectInternalId' from {$id}: {$th->getMessage()}"); + } + try { + /** + * Re-Create '_key_project' index + */ + $this->projectDB->deleteIndex($id, '_key_project'); + $this->createIndexFromCollection($this->projectDB, $id, '_key_project'); + } catch (\Throwable $th) { + Console::warning("'_key_project' from {$id}: {$th->getMessage()}"); + } + + break; + case 'users': + try { + /** + * Create 'phone' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'phone'); + } catch (\Throwable $th) { + Console::warning("'phone' from {$id}: {$th->getMessage()}"); + } + try { + /** + * Create 'phoneVerification' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'phoneVerification'); + } catch (\Throwable $th) { + Console::warning("'phoneVerification' from {$id}: {$th->getMessage()}"); + } + try { + /** + * Create '_key_phone' index + */ + $this->createIndexFromCollection($this->projectDB, $id, '_key_phone'); + } catch (\Throwable $th) { + Console::warning("'_key_project' from {$id}: {$th->getMessage()}"); + } + + break; + case 'tokens': + try { + /** + * Create 'userInternalId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'userInternalId'); + } catch (\Throwable $th) { + Console::warning("'userInternalId' from {$id}: {$th->getMessage()}"); + } + try { + /** + * Re-Create '_key_user' index + */ + $this->projectDB->deleteIndex($id, '_key_user'); + $this->createIndexFromCollection($this->projectDB, $id, '_key_user'); + } catch (\Throwable $th) { + Console::warning("'_key_user' from {$id}: {$th->getMessage()}"); + } + + break; + case 'sessions': + try { + /** + * Create 'userInternalId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'userInternalId'); + } catch (\Throwable $th) { + Console::warning("'userInternalId' from {$id}: {$th->getMessage()}"); + } + try { + /** + * Re-Create '_key_user' index + */ + $this->projectDB->deleteIndex($id, '_key_user'); + $this->createIndexFromCollection($this->projectDB, $id, '_key_user'); + } catch (\Throwable $th) { + Console::warning("'_key_user' from {$id}: {$th->getMessage()}"); + } + + break; + case 'memberships': + try { + /** + * Create 'teamInternalId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'teamInternalId'); + } catch (\Throwable $th) { + Console::warning("'userInternalId' from {$id}: {$th->getMessage()}"); + } + try { + /** + * Create 'userInternalId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'userInternalId'); + } catch (\Throwable $th) { + Console::warning("'userInternalId' from {$id}: {$th->getMessage()}"); + } + try { + /** + * Re-Create '_key_unique' index + */ + $this->projectDB->deleteIndex($id, '_key_unique'); + $this->createIndexFromCollection($this->projectDB, $id, '_key_unique'); + } catch (\Throwable $th) { + Console::warning("'_key_unique' from {$id}: {$th->getMessage()}"); + } + try { + /** + * Re-Create '_key_team' index + */ + $this->projectDB->deleteIndex($id, '_key_team'); + $this->createIndexFromCollection($this->projectDB, $id, '_key_team'); + } catch (\Throwable $th) { + Console::warning("'_key_team' from {$id}: {$th->getMessage()}"); + } + try { + /** + * Re-Create '_key_user' index + */ + $this->projectDB->deleteIndex($id, '_key_user'); + $this->createIndexFromCollection($this->projectDB, $id, '_key_user'); + } catch (\Throwable $th) { + Console::warning("'_key_user' from {$id}: {$th->getMessage()}"); + } + break; + } + usleep(100000); + } + } + + /** + * Fix run on each document + * + * @param \Utopia\Database\Document $document + * @return \Utopia\Database\Document + */ + protected function fixDocument(Document $document) + { + switch ($document->getCollection()) { + case 'projects': + /** + * Bump Project version number. + */ + $document->setAttribute('version', '0.15.0'); + + break; + } + + return $document; + } +}