chore: remove old broken migrations
This commit is contained in:
parent
e6432df04b
commit
22038daa0e
|
@ -41,18 +41,6 @@ abstract class Migration
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
public static array $versions = [
|
public static array $versions = [
|
||||||
'0.13.0' => 'V12',
|
|
||||||
'0.13.1' => 'V12',
|
|
||||||
'0.13.2' => 'V12',
|
|
||||||
'0.13.3' => 'V12',
|
|
||||||
'0.13.4' => 'V12',
|
|
||||||
'0.14.0' => 'V13',
|
|
||||||
'0.14.1' => 'V13',
|
|
||||||
'0.14.2' => 'V13',
|
|
||||||
'0.15.0' => 'V14',
|
|
||||||
'0.15.1' => 'V14',
|
|
||||||
'0.15.2' => 'V14',
|
|
||||||
'0.15.3' => 'V14',
|
|
||||||
'1.0.0-RC1' => 'V15'
|
'1.0.0-RC1' => 'V15'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -1,618 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Appwrite\Migration\Version;
|
|
||||||
|
|
||||||
use Appwrite\Migration\Migration;
|
|
||||||
use Utopia\App;
|
|
||||||
use Utopia\CLI\Console;
|
|
||||||
use Utopia\Database\Database;
|
|
||||||
use Utopia\Database\Document;
|
|
||||||
use Utopia\Database\ID;
|
|
||||||
use Utopia\Database\Query;
|
|
||||||
|
|
||||||
class V12 extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var \PDO $pdo
|
|
||||||
*/
|
|
||||||
private $pdo;
|
|
||||||
|
|
||||||
public function execute(): void
|
|
||||||
{
|
|
||||||
global $register;
|
|
||||||
Console::log('Migrating project: ' . $this->project->getAttribute('name') . ' (' . $this->project->getId() . ')');
|
|
||||||
|
|
||||||
$this->pdo = $register->get('db');
|
|
||||||
|
|
||||||
Console::info('Migrating Project Schemas');
|
|
||||||
$this->migrateProjectSchema($this->project->getId());
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Switch to migrated Console Project
|
|
||||||
*/
|
|
||||||
if ($this->project->getId() === 'console') {
|
|
||||||
$this->consoleDB->setNamespace('_console');
|
|
||||||
$this->projectDB->setNamespace('_console');
|
|
||||||
}
|
|
||||||
|
|
||||||
Console::info('Migrating Permissions');
|
|
||||||
$this->fixPermissions();
|
|
||||||
Console::info('Migrating Collections');
|
|
||||||
$this->migrateCustomCollections();
|
|
||||||
$this->fixCollections();
|
|
||||||
Console::info('Migrating Documents');
|
|
||||||
$this->forEachDocument([$this, 'fixDocument']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrate Project Tables.
|
|
||||||
*
|
|
||||||
* @param string $projectId
|
|
||||||
* @return void
|
|
||||||
* @throws \Exception
|
|
||||||
* @throws \PDOException
|
|
||||||
*/
|
|
||||||
private function migrateProjectSchema(string $projectId): void
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Remove empty generated Console Project.
|
|
||||||
*/
|
|
||||||
if ($this->consoleDB->getNamespace() === '_project_console' && $projectId === 'console') {
|
|
||||||
$all = ['_console_bucket_1', '_console_bucket_1_perms'];
|
|
||||||
foreach ($this->collections as $collection) {
|
|
||||||
$all[] = "_{$projectId}_{$collection['$id']}";
|
|
||||||
$all[] = "_{$projectId}_{$collection['$id']}_perms";
|
|
||||||
}
|
|
||||||
$this->pdo->prepare('DROP TABLE IF EXISTS ' . implode(', ', $all) . ';')->execute();
|
|
||||||
} elseif ($this->projectDB->getNamespace() === '_console') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rename Database Tables.
|
|
||||||
*/
|
|
||||||
foreach ($this->collections as $collection) {
|
|
||||||
$id = $collection['$id'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Skip new tables that don't exists on old schema.
|
|
||||||
*/
|
|
||||||
if (in_array($id, ['buckets', 'deployments', 'builds'])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->pdo->prepare("ALTER TABLE IF EXISTS `{$this->projectDB->getDefaultDatabase()}`.`_project_{$projectId}_{$id}` RENAME TO `_{$projectId}_{$id}`")->execute();
|
|
||||||
$this->pdo->prepare("CREATE TABLE IF NOT EXISTS `{$this->projectDB->getDefaultDatabase()}`.`_{$projectId}_{$id}_perms` (
|
|
||||||
`_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`_type` VARCHAR(12) NOT NULL,
|
|
||||||
`_permission` VARCHAR(255) NOT NULL,
|
|
||||||
`_document` VARCHAR(255) NOT NULL,
|
|
||||||
PRIMARY KEY (`_id`),
|
|
||||||
UNIQUE INDEX `_index1` (`_type`,`_document`,`_permission`),
|
|
||||||
INDEX `_index2` (`_permission`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;")->execute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrate all Collection Structure.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
protected function fixCollections(): void
|
|
||||||
{
|
|
||||||
foreach ($this->collections as $collection) {
|
|
||||||
$id = $collection['$id'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Skip new tables that don't exists on old schema.
|
|
||||||
*/
|
|
||||||
if (in_array($id, ['buckets', 'deployments', 'builds'])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Console::log("- {$id}");
|
|
||||||
switch ($id) {
|
|
||||||
case 'sessions':
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Rename providerToken to providerAccessToken
|
|
||||||
*/
|
|
||||||
$this->projectDB->renameAttribute($id, 'providerToken', 'providerAccessToken');
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'providerAccessToken' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Create providerRefreshToken
|
|
||||||
*/
|
|
||||||
$this->projectDB->createAttribute(collection: $id, id: 'providerRefreshToken', type: Database::VAR_STRING, size: 16384, signed: true, required: false, filters: ['encrypt']);
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'providerRefreshToken' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Create providerAccessTokenExpiry
|
|
||||||
*/
|
|
||||||
$this->projectDB->createAttribute(collection: $id, id: 'providerAccessTokenExpiry', type: Database::VAR_INTEGER, size: 0, required: false);
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'providerAccessTokenExpiry' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'memberships':
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Add search attribute and index to memberships.
|
|
||||||
*/
|
|
||||||
$this->projectDB->createAttribute(collection: $id, id: 'search', type: Database::VAR_STRING, size: 16384, required: false);
|
|
||||||
$this->projectDB->createIndex(collection: $id, id: '_key_search', type: Database::INDEX_FULLTEXT, attributes: ['search']);
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'search' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'files':
|
|
||||||
/**
|
|
||||||
* Create bucket table if not exists.
|
|
||||||
*/
|
|
||||||
$this->createCollection('buckets');
|
|
||||||
|
|
||||||
if (!$this->projectDB->findOne('buckets', [Query::equal('$id', ['default'])])) {
|
|
||||||
$this->projectDB->createDocument('buckets', new Document([
|
|
||||||
'$id' => ID::custom('default'),
|
|
||||||
'$collection' => ID::custom('buckets'),
|
|
||||||
'dateCreated' => \time(),
|
|
||||||
'dateUpdated' => \time(),
|
|
||||||
'name' => 'Default',
|
|
||||||
'permission' => 'file',
|
|
||||||
'maximumFileSize' => (int) App::getEnv('_APP_STORAGE_LIMIT', 0), // 10MB
|
|
||||||
'allowedFileExtensions' => [],
|
|
||||||
'enabled' => true,
|
|
||||||
'encryption' => true,
|
|
||||||
'antivirus' => true,
|
|
||||||
'$read' => ['role:all'],
|
|
||||||
'$write' => ['role:all'],
|
|
||||||
'search' => 'buckets Default',
|
|
||||||
]));
|
|
||||||
$this->createCollection('files', 'bucket_1');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrate all files to default Bucket.
|
|
||||||
*/
|
|
||||||
$nextDocument = null;
|
|
||||||
do {
|
|
||||||
$queries = [Query::limit($this->limit)];
|
|
||||||
if ($nextDocument !== null) {
|
|
||||||
$queries[] = Query::cursorAfter($nextDocument);
|
|
||||||
}
|
|
||||||
$documents = $this->projectDB->find('files', $queries);
|
|
||||||
$count = count($documents);
|
|
||||||
\Co\run(function (array $documents) {
|
|
||||||
foreach ($documents as $document) {
|
|
||||||
go(function (Document $document) {
|
|
||||||
/**
|
|
||||||
* Update File Path
|
|
||||||
*/
|
|
||||||
$path = "/storage/uploads/app-{$this->project->getId()}";
|
|
||||||
$new = str_replace($path, "{$path}/default", $document->getAttribute('path'));
|
|
||||||
$document->setAttribute('path', $new);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populate search string from Migration to 0.12.
|
|
||||||
*/
|
|
||||||
if (empty($document->getAttribute('search'))) {
|
|
||||||
$document->setAttribute('search', $this->buildSearchAttribute(['$id', 'name'], $document));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set new values.
|
|
||||||
*/
|
|
||||||
$document
|
|
||||||
->setAttribute('bucketId', 'default')
|
|
||||||
->setAttribute('chunksTotal', 1)
|
|
||||||
->setAttribute('chunksUploaded', 1);
|
|
||||||
|
|
||||||
$this->projectDB->createDocument('bucket_1', $document);
|
|
||||||
}, $document);
|
|
||||||
}
|
|
||||||
}, $documents);
|
|
||||||
|
|
||||||
if ($count !== $this->limit) {
|
|
||||||
$nextDocument = null;
|
|
||||||
} else {
|
|
||||||
$nextDocument = end($documents);
|
|
||||||
$nextDocument->setAttribute('$collection', 'files');
|
|
||||||
}
|
|
||||||
} while (!is_null($nextDocument));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rename folder on volumes.
|
|
||||||
*/
|
|
||||||
$path = "/storage/uploads/app-{$this->project->getId()}";
|
|
||||||
|
|
||||||
if (is_dir("{$path}/")) {
|
|
||||||
mkdir("/storage/uploads/app-{$this->project->getId()}/default");
|
|
||||||
|
|
||||||
foreach (new \DirectoryIterator($path) as $fileinfo) {
|
|
||||||
if ($fileinfo->isDir() && !$fileinfo->isDot() && $fileinfo->getFilename() !== 'default') {
|
|
||||||
rename("{$path}/{$fileinfo->getFilename()}", "{$path}/default/{$fileinfo->getFilename()}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'functions':
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Rename tag to deployment
|
|
||||||
*/
|
|
||||||
$this->projectDB->renameAttribute($id, 'tag', 'deployment');
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'deployment' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create deployments table if not exists.
|
|
||||||
*/
|
|
||||||
$this->createCollection('deployments');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create builds table if not exists.
|
|
||||||
*/
|
|
||||||
$this->createCollection('builds');
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'executions':
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Rename tag to deployment
|
|
||||||
*/
|
|
||||||
$this->projectDB->renameAttribute($id, 'tagId', 'deploymentId');
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'deploymentId' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Create statusCode
|
|
||||||
*/
|
|
||||||
$this->projectDB->createAttribute(collection: $id, id: 'statusCode', type: Database::VAR_INTEGER, size: 0, required: false);
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'statusCode' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'teams':
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Rename tag to deployment
|
|
||||||
*/
|
|
||||||
$this->projectDB->renameAttribute($id, 'sum', 'total');
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'total' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
usleep(100000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrates permissions to dedicated table.
|
|
||||||
*
|
|
||||||
* @param \Utopia\Database\Document $document
|
|
||||||
* @param string $internalId
|
|
||||||
* @return void
|
|
||||||
* @throws \Exception
|
|
||||||
* @throws \PDOException
|
|
||||||
*/
|
|
||||||
protected function migratePermissionsToDedicatedTable(string $collection, Document $document): void
|
|
||||||
{
|
|
||||||
$sql = "SELECT _read, _write FROM `{$this->projectDB->getDefaultDatabase()}`.`{$this->projectDB->getNamespace()}_{$collection}` WHERE _uid = {$this->pdo->quote($document->getid())}";
|
|
||||||
$stmt = $this->pdo->prepare($sql);
|
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
$permissions = $stmt->fetch();
|
|
||||||
|
|
||||||
$read = json_decode($permissions['_read'] ?? null) ?? [];
|
|
||||||
$write = json_decode($permissions['_write'] ?? null) ?? [];
|
|
||||||
|
|
||||||
$permissions = [];
|
|
||||||
foreach ($read as $permission) {
|
|
||||||
$permissions[] = "('read', '{$permission}', '{$document->getId()}')";
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($write as $permission) {
|
|
||||||
$permissions[] = "('write', '{$permission}', '{$document->getId()}')";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($permissions)) {
|
|
||||||
$queryPermissions = "INSERT IGNORE INTO `{$this->projectDB->getDefaultDatabase()}`.`{$this->projectDB->getNamespace()}_{$collection}_perms` (_type, _permission, _document) VALUES " . implode(', ', $permissions);
|
|
||||||
$stmtPermissions = $this->pdo->prepare($queryPermissions);
|
|
||||||
$stmtPermissions->execute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrates all user's database collections.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
protected function migrateCustomCollections(): void
|
|
||||||
{
|
|
||||||
$nextCollection = null;
|
|
||||||
|
|
||||||
do {
|
|
||||||
$queries = [Query::limit($this->limit)];
|
|
||||||
if ($nextCollection !== null) {
|
|
||||||
$queries[] = Query::cursorAfter($nextCollection);
|
|
||||||
}
|
|
||||||
$documents = $this->projectDB->find('collections', $queries);
|
|
||||||
$count = count($documents);
|
|
||||||
|
|
||||||
\Co\run(function (array $documents) {
|
|
||||||
foreach ($documents as $document) {
|
|
||||||
go(function (Document $collection) {
|
|
||||||
$id = $collection->getId();
|
|
||||||
$projectId = $this->project->getId();
|
|
||||||
$internalId = $collection->getInternalId();
|
|
||||||
|
|
||||||
if ($this->projectDB->exists(App::getEnv('_APP_DB_SCHEMA', 'appwrite'), "collection_{$internalId}")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Console::log("- {$id} ({$collection->getAttribute('name')})");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rename user's colletion table schema
|
|
||||||
*/
|
|
||||||
$this->pdo->prepare("ALTER TABLE IF EXISTS `{$this->projectDB->getDefaultDatabase()}`.`_project_{$projectId}_collection_{$id}` RENAME TO `_{$projectId}_collection_{$internalId}`")->execute();
|
|
||||||
$this->pdo->prepare("CREATE TABLE IF NOT EXISTS `{$this->projectDB->getDefaultDatabase()}`.`_{$projectId}_collection_{$internalId}_perms` (
|
|
||||||
`_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`_type` VARCHAR(12) NOT NULL,
|
|
||||||
`_permission` VARCHAR(255) NOT NULL,
|
|
||||||
`_document` VARCHAR(255) NOT NULL,
|
|
||||||
PRIMARY KEY (`_id`),
|
|
||||||
UNIQUE INDEX `_index1` (`_type`,`_document`,`_permission`),
|
|
||||||
INDEX `_index2` (`_permission`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;")->execute();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update metadata table.
|
|
||||||
*/
|
|
||||||
$this->pdo->prepare("UPDATE `{$this->projectDB->getDefaultDatabase()}`.`_{$projectId}__metadata`
|
|
||||||
SET
|
|
||||||
_uid = 'collection_{$internalId}',
|
|
||||||
name = 'collection_{$internalId}'
|
|
||||||
WHERE _uid = 'collection_{$id}';
|
|
||||||
")->execute();
|
|
||||||
|
|
||||||
|
|
||||||
$nextDocument = null;
|
|
||||||
|
|
||||||
do {
|
|
||||||
$queries = [Query::limit($this->limit)];
|
|
||||||
if ($nextDocument !== null) {
|
|
||||||
$queries[] = Query::cursorAfter($nextDocument);
|
|
||||||
}
|
|
||||||
$documents = $this->projectDB->find('collection_' . $internalId, $queries);
|
|
||||||
$count = count($documents);
|
|
||||||
|
|
||||||
foreach ($documents as $document) {
|
|
||||||
go(function (Document $document, string $internalId) {
|
|
||||||
$this->migratePermissionsToDedicatedTable("collection_{$internalId}", $document);
|
|
||||||
}, $document, $internalId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($count !== $this->limit) {
|
|
||||||
$nextDocument = null;
|
|
||||||
} else {
|
|
||||||
$nextDocument = end($documents);
|
|
||||||
}
|
|
||||||
} while (!is_null($nextDocument));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove _read and _write columns
|
|
||||||
*/
|
|
||||||
$this->pdo->prepare("
|
|
||||||
ALTER TABLE `{$this->projectDB->getDefaultDatabase()}`.`{$this->projectDB->getNamespace()}_collection_{$internalId}`
|
|
||||||
DROP COLUMN _read,
|
|
||||||
DROP COLUMN _write
|
|
||||||
")->execute();
|
|
||||||
}, $document);
|
|
||||||
}
|
|
||||||
}, $documents);
|
|
||||||
|
|
||||||
if ($count !== $this->limit) {
|
|
||||||
$nextCollection = null;
|
|
||||||
} else {
|
|
||||||
$nextCollection = end($documents);
|
|
||||||
}
|
|
||||||
} while (!is_null($nextCollection));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrate all Permission to new System with dedicated Table.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
protected function fixPermissions()
|
|
||||||
{
|
|
||||||
foreach ($this->collections as $collection) {
|
|
||||||
$id = $collection['$id'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Skip new tables that don't exists on old schema.
|
|
||||||
*/
|
|
||||||
if (in_array($id, ['buckets', 'deployments', 'builds'])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Check if permissions have already been migrated.
|
|
||||||
*/
|
|
||||||
try {
|
|
||||||
$stmtCheck = $this->pdo->prepare("SHOW COLUMNS from `{$this->projectDB->getDefaultDatabase()}`.`{$this->projectDB->getNamespace()}_{$id}` LIKE '_read'");
|
|
||||||
$stmtCheck->execute();
|
|
||||||
|
|
||||||
if (empty($stmtCheck->fetchAll())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
if ($th->getCode() === "42S02") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
throw $th;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Console::log("- {$collection['$id']}");
|
|
||||||
$nextDocument = null;
|
|
||||||
|
|
||||||
do {
|
|
||||||
$queries = [Query::limit($this->limit)];
|
|
||||||
if ($nextDocument !== null) {
|
|
||||||
$queries[] = Query::cursorAfter($nextDocument);
|
|
||||||
}
|
|
||||||
$documents = $this->projectDB->find($id, $queries);
|
|
||||||
$count = count($documents);
|
|
||||||
|
|
||||||
\Co\run(function (array $documents) {
|
|
||||||
foreach ($documents as $document) {
|
|
||||||
go(function (Document $document) {
|
|
||||||
$this->migratePermissionsToDedicatedTable($document->getCollection(), $document);
|
|
||||||
}, $document);
|
|
||||||
}
|
|
||||||
}, $documents);
|
|
||||||
|
|
||||||
if ($count !== $this->limit) {
|
|
||||||
$nextDocument = null;
|
|
||||||
} else {
|
|
||||||
$nextDocument = end($documents);
|
|
||||||
}
|
|
||||||
} while (!is_null($nextDocument));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove _read and _write columns
|
|
||||||
*/
|
|
||||||
$this->pdo->prepare("
|
|
||||||
ALTER TABLE `{$this->projectDB->getDefaultDatabase()}`.`{$this->projectDB->getNamespace()}_{$id}`
|
|
||||||
DROP COLUMN _read,
|
|
||||||
DROP COLUMN _write
|
|
||||||
")->execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Timeout to give MariaDB some room to breath
|
|
||||||
*/
|
|
||||||
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.13.0');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populate search string from Migration to 0.12.
|
|
||||||
*/
|
|
||||||
if (empty($document->getAttribute('search'))) {
|
|
||||||
$document->setAttribute('search', $this->buildSearchAttribute(['$id', 'name'], $document));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'users':
|
|
||||||
/**
|
|
||||||
* Populate search string from Migration to 0.12.
|
|
||||||
*/
|
|
||||||
if (empty($document->getAttribute('search'))) {
|
|
||||||
$document->setAttribute('search', $this->buildSearchAttribute(['$id', 'email', 'name'], $document));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'teams':
|
|
||||||
/**
|
|
||||||
* Populate search string from Migration to 0.12.
|
|
||||||
*/
|
|
||||||
if (empty($document->getAttribute('search'))) {
|
|
||||||
$document->setAttribute('search', $this->buildSearchAttribute(['$id', 'name'], $document));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'functions':
|
|
||||||
$document->setAttribute('deployment', null);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populate search string from Migration to 0.12.
|
|
||||||
*/
|
|
||||||
if (empty($document->getAttribute('search'))) {
|
|
||||||
$document->setAttribute('search', $this->buildSearchAttribute(['$id', 'name', 'runtime'], $document));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'executions':
|
|
||||||
/**
|
|
||||||
* Populate search string from Migration to 0.12.
|
|
||||||
*/
|
|
||||||
if (empty($document->getAttribute('search'))) {
|
|
||||||
$document->setAttribute('search', $this->buildSearchAttribute(['$id', 'functionId'], $document));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'memberships':
|
|
||||||
/**
|
|
||||||
* Populate search string.
|
|
||||||
*/
|
|
||||||
if (empty($document->getAttribute('search'))) {
|
|
||||||
$document->setAttribute('search', $this->buildSearchAttribute(['$id', 'userId'], $document));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'sessions':
|
|
||||||
$document
|
|
||||||
->setAttribute('providerRefreshToken', '')
|
|
||||||
->setAttribute('providerAccessTokenExpiry', 0)
|
|
||||||
->setAttribute('providerAccessToken', $document->getAttribute('providerToken', ''))
|
|
||||||
->removeAttribute('providerToken');
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $document;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a search string for a fulltext index.
|
|
||||||
*
|
|
||||||
* @param array $values
|
|
||||||
* @param Document $document
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
private function buildSearchAttribute(array $values, Document $document): string
|
|
||||||
{
|
|
||||||
$values = array_filter(array_map(fn (string $value) => $document->getAttribute($value) ?? '', $values));
|
|
||||||
|
|
||||||
return implode(' ', $values);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,337 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Appwrite\Migration\Version;
|
|
||||||
|
|
||||||
use Appwrite\Migration\Migration;
|
|
||||||
use Utopia\CLI\Console;
|
|
||||||
use Utopia\Database\Database;
|
|
||||||
use Utopia\Database\Document;
|
|
||||||
|
|
||||||
class V13 extends Migration
|
|
||||||
{
|
|
||||||
public array $events = [
|
|
||||||
'account.create',
|
|
||||||
'account.update.email',
|
|
||||||
'account.update.name',
|
|
||||||
'account.update.password',
|
|
||||||
'account.update.prefs',
|
|
||||||
'account.recovery.create',
|
|
||||||
'account.recovery.update',
|
|
||||||
'account.verification.create',
|
|
||||||
'account.verification.update',
|
|
||||||
'account.delete',
|
|
||||||
'account.sessions.create',
|
|
||||||
'account.sessions.delete',
|
|
||||||
'database.collections.create',
|
|
||||||
'database.collections.update',
|
|
||||||
'database.collections.delete',
|
|
||||||
'database.attributes.create',
|
|
||||||
'database.attributes.delete',
|
|
||||||
'database.indexes.create',
|
|
||||||
'database.indexes.delete',
|
|
||||||
'database.documents.create',
|
|
||||||
'database.documents.update',
|
|
||||||
'database.documents.delete',
|
|
||||||
'functions.create',
|
|
||||||
'functions.update',
|
|
||||||
'functions.delete',
|
|
||||||
'functions.deployments.create',
|
|
||||||
'functions.deployments.update',
|
|
||||||
'functions.deployments.delete',
|
|
||||||
'functions.executions.create',
|
|
||||||
'functions.executions.update',
|
|
||||||
'storage.files.create',
|
|
||||||
'storage.files.update',
|
|
||||||
'storage.files.delete',
|
|
||||||
'storage.buckets.create',
|
|
||||||
'storage.buckets.update',
|
|
||||||
'storage.buckets.delete',
|
|
||||||
'users.create',
|
|
||||||
'users.update.prefs',
|
|
||||||
'users.update.email',
|
|
||||||
'users.update.name',
|
|
||||||
'users.update.password',
|
|
||||||
'users.update.status',
|
|
||||||
'users.sessions.delete',
|
|
||||||
'users.delete',
|
|
||||||
'teams.create',
|
|
||||||
'teams.update',
|
|
||||||
'teams.delete',
|
|
||||||
'teams.memberships.create',
|
|
||||||
'teams.memberships.update',
|
|
||||||
'teams.memberships.update.status',
|
|
||||||
'teams.memberships.delete'
|
|
||||||
];
|
|
||||||
|
|
||||||
public function execute(): void
|
|
||||||
{
|
|
||||||
Console::log('Migrating project: ' . $this->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 'projects':
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Rename providers to authProviders.
|
|
||||||
*/
|
|
||||||
$this->projectDB->renameAttribute($id, 'providers', 'authProviders');
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'providers' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'users':
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Recreate sessions for new subquery.
|
|
||||||
*/
|
|
||||||
$this->projectDB->deleteAttribute($id, 'sessions');
|
|
||||||
$this->projectDB->createAttribute(
|
|
||||||
collection: $id,
|
|
||||||
id: 'sessions',
|
|
||||||
required: false,
|
|
||||||
type: Database::VAR_STRING,
|
|
||||||
format: '',
|
|
||||||
size: 16384,
|
|
||||||
filters: ['subQuerySessions']
|
|
||||||
);
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'sessions' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Recreate tokens for new subquery.
|
|
||||||
*/
|
|
||||||
$this->projectDB->deleteAttribute($id, 'tokens');
|
|
||||||
$this->projectDB->createAttribute(
|
|
||||||
collection: $id,
|
|
||||||
id: 'tokens',
|
|
||||||
required: false,
|
|
||||||
type: Database::VAR_STRING,
|
|
||||||
format: '',
|
|
||||||
size: 16384,
|
|
||||||
filters: ['subQueryTokens']
|
|
||||||
);
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'tokens' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Recreate memberships for new subquery.
|
|
||||||
*/
|
|
||||||
$this->projectDB->deleteAttribute($id, 'memberships');
|
|
||||||
$this->projectDB->createAttribute(
|
|
||||||
collection: $id,
|
|
||||||
id: 'memberships',
|
|
||||||
required: false,
|
|
||||||
type: Database::VAR_STRING,
|
|
||||||
format: '',
|
|
||||||
size: 16384,
|
|
||||||
filters: ['subQueryMemberships']
|
|
||||||
);
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'memberships' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'sessions':
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Add new index for users.
|
|
||||||
*/
|
|
||||||
$this->projectDB->createIndex(collection: $id, id: '_key_user', type: Database::INDEX_KEY, attributes: ['userId'], orders: [Database::ORDER_ASC]);
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'_key_user' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'builds':
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Increase stdout size.
|
|
||||||
*/
|
|
||||||
$this->projectDB->updateAttribute($id, 'stdout', size: 1_000_000);
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'stdout' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Increase stderr size.
|
|
||||||
*/
|
|
||||||
$this->projectDB->updateAttribute($id, 'stderr', size: 1_000_000);
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'stderr' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'executions':
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Rename stdout to response.
|
|
||||||
* Increase response size.
|
|
||||||
*/
|
|
||||||
$this->projectDB->renameAttribute($id, 'stdout', 'response');
|
|
||||||
$this->projectDB->updateAttribute($id, 'response', size: 1_000_000);
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'stdout' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Increase stderr size.
|
|
||||||
*/
|
|
||||||
$this->projectDB->updateAttribute($id, 'stderr', size: 1_000_000);
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'stderr' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'stats':
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Increase value size ot BIGINT.
|
|
||||||
*/
|
|
||||||
$this->projectDB->updateAttribute($id, 'value', size: 8);
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'size' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'tokens':
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Create new Tokens collection.
|
|
||||||
*/
|
|
||||||
$this->createCollection('tokens');
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'tokens': {$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.14.0');
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'functions':
|
|
||||||
/**
|
|
||||||
* Migrate events.
|
|
||||||
*/
|
|
||||||
if (!empty($document->getAttribute('events'))) {
|
|
||||||
$document->setAttribute('events', $this->migrateEvents($document->getAttribute('events')));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'webhooks':
|
|
||||||
/**
|
|
||||||
* Migrate events.
|
|
||||||
*/
|
|
||||||
if (!empty($document->getAttribute('events'))) {
|
|
||||||
$document->setAttribute('events', $this->migrateEvents($document->getAttribute('events')));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'users':
|
|
||||||
/**
|
|
||||||
* Remove deleted users.
|
|
||||||
*/
|
|
||||||
if ($document->getAttribute('deleted', false) === true) {
|
|
||||||
$this->projectDB->deleteDocument('users', $document->getId());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $document;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function migrateEvents(array $events): array
|
|
||||||
{
|
|
||||||
return array_filter(array_unique(array_map(function ($event) {
|
|
||||||
if (!in_array($event, $this->events)) {
|
|
||||||
return $event;
|
|
||||||
}
|
|
||||||
$parts = \explode('.', $event);
|
|
||||||
$first = array_shift($parts);
|
|
||||||
switch ($first) {
|
|
||||||
case 'account':
|
|
||||||
case 'users':
|
|
||||||
$first = 'users';
|
|
||||||
|
|
||||||
switch ($parts[0]) {
|
|
||||||
case 'recovery':
|
|
||||||
case 'sessions':
|
|
||||||
case 'verification':
|
|
||||||
$second = array_shift($parts);
|
|
||||||
return 'users.*.' . $second . '.*.' . implode('.', $parts);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return 'users.*.' . implode('.', $parts);
|
|
||||||
}
|
|
||||||
case 'functions':
|
|
||||||
switch ($parts[0]) {
|
|
||||||
case 'deployments':
|
|
||||||
case 'executions':
|
|
||||||
$second = array_shift($parts);
|
|
||||||
return 'functions.*.' . $second . '.*.' . implode('.', $parts);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return 'functions.*.' . implode('.', $parts);
|
|
||||||
}
|
|
||||||
case 'teams':
|
|
||||||
switch ($parts[0]) {
|
|
||||||
case 'memberships':
|
|
||||||
$second = array_shift($parts);
|
|
||||||
return 'teams.*.' . $second . '.*.' . implode('.', $parts);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return 'teams.*.' . implode('.', $parts);
|
|
||||||
}
|
|
||||||
case 'storage':
|
|
||||||
$second = array_shift($parts);
|
|
||||||
switch ($second) {
|
|
||||||
case 'buckets':
|
|
||||||
return 'buckets.*.' . implode('.', $parts);
|
|
||||||
case 'files':
|
|
||||||
return 'buckets.*.' . $second . '.*.' . implode('.', $parts);
|
|
||||||
} // intentional fallthrough
|
|
||||||
case 'database':
|
|
||||||
$second = array_shift($parts);
|
|
||||||
switch ($second) {
|
|
||||||
case 'collections':
|
|
||||||
return 'collections.*.' . implode('.', $parts);
|
|
||||||
case 'documents':
|
|
||||||
case 'indexes':
|
|
||||||
case 'attributes':
|
|
||||||
return 'collections.*.' . $second . '.*.' . implode('.', $parts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}, $events)));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,807 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Appwrite\Migration\Version;
|
|
||||||
|
|
||||||
use Appwrite\Migration\Migration;
|
|
||||||
use Exception;
|
|
||||||
use Utopia\CLI\Console;
|
|
||||||
use Utopia\Database\Database;
|
|
||||||
use Utopia\Database\Document;
|
|
||||||
use Utopia\Database\ID;
|
|
||||||
use Utopia\Database\Query;
|
|
||||||
|
|
||||||
class V14 extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var \PDO $pdo
|
|
||||||
*/
|
|
||||||
private $pdo;
|
|
||||||
|
|
||||||
public function execute(): void
|
|
||||||
{
|
|
||||||
global $register;
|
|
||||||
$this->pdo = $register->get('db');
|
|
||||||
|
|
||||||
if ($this->project->getId() === 'console' && $this->project->getInternalId() !== 'console') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable SubQueries for Speed.
|
|
||||||
*/
|
|
||||||
foreach (['subQueryAttributes', 'subQueryIndexes', 'subQueryPlatforms', 'subQueryDomains', 'subQueryKeys', 'subQueryWebhooks', 'subQuerySessions', 'subQueryTokens', 'subQueryMemberships'] as $name) {
|
|
||||||
Database::addFilter($name, fn () => null, fn () => []);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console::log('Migrating project: ' . $this->project->getAttribute('name') . ' (' . $this->project->getId() . ')');
|
|
||||||
Console::info('Migrating Collections');
|
|
||||||
$this->migrateCollections();
|
|
||||||
Console::info('Create Default Database Layer');
|
|
||||||
$this->createDatabaseLayer();
|
|
||||||
if ($this->project->getId() !== 'console') {
|
|
||||||
Console::info('Migrating Database Collections');
|
|
||||||
$this->migrateCustomCollections();
|
|
||||||
}
|
|
||||||
Console::info('Migrating Documents');
|
|
||||||
$this->forEachDocument([$this, 'fixDocument']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the default Database for existing Projects.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
* @throws \Throwable
|
|
||||||
*/
|
|
||||||
public function createDatabaseLayer(): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
if (!$this->projectDB->exists('databases')) {
|
|
||||||
$this->createCollection('databases');
|
|
||||||
}
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning($th->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->project->getInternalId() === 'console') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->projectDB->createDocument('databases', new Document([
|
|
||||||
'$id' => ID::custom('default'),
|
|
||||||
'name' => 'Default',
|
|
||||||
'search' => 'default Default'
|
|
||||||
]));
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning($th->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrates all Files.
|
|
||||||
*
|
|
||||||
* @param \Utopia\Database\Document $bucket
|
|
||||||
* @return void
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
protected function migrateBucketFiles(Document $bucket): void
|
|
||||||
{
|
|
||||||
$nextFile = null;
|
|
||||||
do {
|
|
||||||
$queries = [Query::limit($this->limit)];
|
|
||||||
if ($nextFile !== null) {
|
|
||||||
$queries[] = Query::cursorAfter($nextFile);
|
|
||||||
}
|
|
||||||
$documents = $this->projectDB->find("bucket_{$bucket->getInternalId()}", $queries);
|
|
||||||
$count = count($documents);
|
|
||||||
|
|
||||||
foreach ($documents as $document) {
|
|
||||||
go(function (Document $bucket, Document $document) {
|
|
||||||
Console::log("Migrating File {$document->getId()}");
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Migrate $createdAt.
|
|
||||||
*/
|
|
||||||
if (empty($document->getCreatedAt())) {
|
|
||||||
$document->setAttribute('$createdAt', $document->getAttribute('dateCreated'));
|
|
||||||
$this->projectDB->updateDocument("bucket_{$bucket->getInternalId()}", $document->getId(), $document);
|
|
||||||
}
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning($th->getMessage());
|
|
||||||
}
|
|
||||||
}, $bucket, $document);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($count !== $this->limit) {
|
|
||||||
$nextFile = null;
|
|
||||||
} else {
|
|
||||||
$nextFile = end($documents);
|
|
||||||
}
|
|
||||||
} while (!is_null($nextFile));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrates all Database Collections.
|
|
||||||
* @return void
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
protected function migrateCustomCollections(): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->pdo->prepare("ALTER TABLE IF EXISTS `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}_collections` RENAME TO `_{$this->project->getInternalId()}_database_1`")->execute();
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning($th->getMessage());
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$this->pdo->prepare("ALTER TABLE IF EXISTS `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}_collections_perms` RENAME TO `_{$this->project->getInternalId()}_database_1_perms`")->execute();
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning($th->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update metadata table.
|
|
||||||
*/
|
|
||||||
try {
|
|
||||||
$this->pdo->prepare("UPDATE `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}__metadata`
|
|
||||||
SET
|
|
||||||
_uid = 'database_1',
|
|
||||||
name = 'database_1'
|
|
||||||
WHERE _uid = 'collections';
|
|
||||||
")->execute();
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning($th->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Add Database ID for Collections.
|
|
||||||
*/
|
|
||||||
$this->createAttributeFromCollection($this->projectDB, 'database_1', 'databaseId', 'collections');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add Database Internal ID for Collections.
|
|
||||||
*/
|
|
||||||
$this->createAttributeFromCollection($this->projectDB, 'database_1', 'databaseInternalId', 'collections');
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning($th->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
$nextCollection = null;
|
|
||||||
|
|
||||||
do {
|
|
||||||
$queries = [Query::limit($this->limit)];
|
|
||||||
if ($nextCollection !== null) {
|
|
||||||
$queries[] = Query::cursorAfter($nextCollection);
|
|
||||||
}
|
|
||||||
$documents = $this->projectDB->find('database_1', $queries);
|
|
||||||
$count = count($documents);
|
|
||||||
|
|
||||||
\Co\run(function (array $documents) {
|
|
||||||
foreach ($documents as $document) {
|
|
||||||
go(function (Document $collection) {
|
|
||||||
$id = $collection->getId();
|
|
||||||
$internalId = $collection->getInternalId();
|
|
||||||
|
|
||||||
Console::log("- {$id} ({$collection->getAttribute('name')})");
|
|
||||||
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Rename user's colletion table schema
|
|
||||||
*/
|
|
||||||
$this->createNewMetaData("collection_{$internalId}", "database_1_collection_{$internalId}");
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning($th->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Update metadata table.
|
|
||||||
*/
|
|
||||||
$this->pdo->prepare("UPDATE `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getInternalId()}__metadata`
|
|
||||||
SET
|
|
||||||
_uid = 'database_1_collection_{$internalId}',
|
|
||||||
name = 'database_1_collection_{$internalId}'
|
|
||||||
WHERE _uid = 'collection_{$internalId}';
|
|
||||||
")->execute();
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning($th->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Update internal ID's.
|
|
||||||
*/
|
|
||||||
$collection
|
|
||||||
->setAttribute('databaseId', 'default')
|
|
||||||
->setAttribute('databaseInternalId', '1');
|
|
||||||
$this->projectDB->updateDocument('database_1', $collection->getId(), $collection);
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning($th->getMessage());
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Migrate Attributes
|
|
||||||
*/
|
|
||||||
$this->migrateAttributesAndCollections('attributes', $collection);
|
|
||||||
/**
|
|
||||||
* Migrate Indexes
|
|
||||||
*/
|
|
||||||
$this->migrateAttributesAndCollections('indexes', $collection);
|
|
||||||
}, $document);
|
|
||||||
}
|
|
||||||
}, $documents);
|
|
||||||
|
|
||||||
if ($count !== $this->limit) {
|
|
||||||
$nextCollection = null;
|
|
||||||
} else {
|
|
||||||
$nextCollection = end($documents);
|
|
||||||
}
|
|
||||||
} while (!is_null($nextCollection));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function migrateAttributesAndCollections(string $type, Document $collection): void
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Offset pagination instead of cursor, since documents are re-created!
|
|
||||||
*/
|
|
||||||
$offset = 0;
|
|
||||||
$attributesCount = $this->projectDB->count($type, queries: [Query::equal('collectionId', [$collection->getId()])]);
|
|
||||||
|
|
||||||
do {
|
|
||||||
$queries = [
|
|
||||||
Query::limit($this->limit),
|
|
||||||
Query::offset($offset),
|
|
||||||
Query::equal('collectionId', [$collection->getId()]),
|
|
||||||
];
|
|
||||||
$documents = $this->projectDB->find($type, $queries);
|
|
||||||
$offset += $this->limit;
|
|
||||||
|
|
||||||
foreach ($documents as $document) {
|
|
||||||
go(function (Document $document, string $internalId, string $type) {
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Skip already migrated Documents.
|
|
||||||
*/
|
|
||||||
if (!is_null($document->getAttribute('databaseId'))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Add Internal ID 'collectionInternalId' for Subqueries.
|
|
||||||
*/
|
|
||||||
$document->setAttribute('collectionInternalId', $internalId);
|
|
||||||
/**
|
|
||||||
* Add Internal ID 'databaseInternalId' for Subqueries.
|
|
||||||
*/
|
|
||||||
$document->setAttribute('databaseInternalId', '1');
|
|
||||||
/**
|
|
||||||
* Add Internal ID 'databaseId'.
|
|
||||||
*/
|
|
||||||
$document->setAttribute('databaseId', 'default');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Re-create Attribute.
|
|
||||||
*/
|
|
||||||
$this->projectDB->deleteDocument($document->getCollection(), $document->getId());
|
|
||||||
$this->projectDB->createDocument($document->getCollection(), $document->setAttribute('$id', "1_{$internalId}_{$document->getAttribute('key')}"));
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::error("Failed to {$type} document: " . $th->getMessage());
|
|
||||||
}
|
|
||||||
}, $document, $collection->getInternalId(), $type);
|
|
||||||
}
|
|
||||||
} while ($offset < $attributesCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrate all Collections.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
protected function migrateCollections(): void
|
|
||||||
{
|
|
||||||
foreach ($this->collections as $collection) {
|
|
||||||
$id = $collection['$id'];
|
|
||||||
|
|
||||||
Console::log("- {$id}");
|
|
||||||
|
|
||||||
$this->createNewMetaData($id);
|
|
||||||
|
|
||||||
$this->projectDB->setNamespace("_{$this->project->getInternalId()}");
|
|
||||||
|
|
||||||
switch ($id) {
|
|
||||||
case 'attributes':
|
|
||||||
case 'indexes':
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Create 'databaseInternalId' attribute
|
|
||||||
*/
|
|
||||||
$this->createAttributeFromCollection($this->projectDB, $id, 'databaseId');
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'databaseInternalId' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Create 'databaseInternalId' attribute
|
|
||||||
*/
|
|
||||||
$this->createAttributeFromCollection($this->projectDB, $id, 'databaseInternalId');
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'databaseInternalId' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
|
|
||||||
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_db_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("'teamInternalId' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'platforms':
|
|
||||||
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 'signatureKey' attribute
|
|
||||||
*/
|
|
||||||
$this->createAttributeFromCollection($this->projectDB, $id, 'signatureKey');
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("'signatureKey' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
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_phone' from {$id}: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'tokens':
|
|
||||||
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("'teamInternalId' 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(50000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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');
|
|
||||||
|
|
||||||
if (!empty($document->getAttribute('teamId')) && is_null($document->getAttribute('teamInternalId'))) {
|
|
||||||
$internalId = $this->projectDB->getDocument('teams', $document->getAttribute('teamId'))->getInternalId();
|
|
||||||
$document->setAttribute('teamInternalId', $internalId);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'keys':
|
|
||||||
/**
|
|
||||||
* Add new 'expire' attribute and default to never (0).
|
|
||||||
*/
|
|
||||||
if (is_null($document->getAttribute('expire'))) {
|
|
||||||
$document->setAttribute('expire', 0);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Add Internal ID 'projectId' for Subqueries.
|
|
||||||
*/
|
|
||||||
if (!empty($document->getAttribute('projectId')) && is_null($document->getAttribute('projectInternalId'))) {
|
|
||||||
$internalId = $this->projectDB->getDocument('projects', $document->getAttribute('projectId'))->getInternalId();
|
|
||||||
$document->setAttribute('projectInternalId', $internalId);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'audit':
|
|
||||||
/**
|
|
||||||
* Add Database Layer to collection resource.
|
|
||||||
*/
|
|
||||||
if (str_starts_with($document->getAttribute('resource'), 'collection/')) {
|
|
||||||
$document
|
|
||||||
->setAttribute('resource', "database/default/{$document->getAttribute('resource')}")
|
|
||||||
->setAttribute('event', "databases.default.{$document->getAttribute('event')}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (str_starts_with($document->getAttribute('resource'), 'document/')) {
|
|
||||||
$collectionId = explode('.', $document->getAttribute('event'))[1];
|
|
||||||
$document
|
|
||||||
->setAttribute('resource', "database/default/collection/{$collectionId}/{$document->getAttribute('resource')}")
|
|
||||||
->setAttribute('event', "databases.default.{$document->getAttribute('event')}");
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'stats':
|
|
||||||
/**
|
|
||||||
* Add Database Layer to stats metric.
|
|
||||||
*/
|
|
||||||
if (str_starts_with($document->getAttribute('metric'), 'database.')) {
|
|
||||||
$metric = ltrim($document->getAttribute('metric'), 'database.');
|
|
||||||
$document->setAttribute('metric', "databases.default.{$metric}");
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'webhooks':
|
|
||||||
/**
|
|
||||||
* Add new 'signatureKey' attribute and generate a random value.
|
|
||||||
*/
|
|
||||||
if (empty($document->getAttribute('signatureKey'))) {
|
|
||||||
$document->setAttribute('signatureKey', \bin2hex(\random_bytes(64)));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Add Internal ID 'projectId' for Subqueries.
|
|
||||||
*/
|
|
||||||
if (!empty($document->getAttribute('projectId')) && is_null($document->getAttribute('projectInternalId'))) {
|
|
||||||
$internalId = $this->projectDB->getDocument('projects', $document->getAttribute('projectId'))->getInternalId();
|
|
||||||
$document->setAttribute('projectInternalId', $internalId);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'domains':
|
|
||||||
/**
|
|
||||||
* Add Internal ID 'projectId' for Subqueries.
|
|
||||||
*/
|
|
||||||
if (!empty($document->getAttribute('projectId')) && is_null($document->getAttribute('projectInternalId'))) {
|
|
||||||
$internalId = $this->projectDB->getDocument('projects', $document->getAttribute('projectId'))->getInternalId();
|
|
||||||
$document->setAttribute('projectInternalId', $internalId);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'tokens':
|
|
||||||
case 'sessions':
|
|
||||||
/**
|
|
||||||
* Add Internal ID 'userId' for Subqueries.
|
|
||||||
*/
|
|
||||||
if (!empty($document->getAttribute('userId')) && is_null($document->getAttribute('userInternalId'))) {
|
|
||||||
$internalId = $this->projectDB->getDocument('users', $document->getAttribute('userId'))->getInternalId();
|
|
||||||
$document->setAttribute('userInternalId', $internalId);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'memberships':
|
|
||||||
/**
|
|
||||||
* Add Internal ID 'userId' for Subqueries.
|
|
||||||
*/
|
|
||||||
if (!empty($document->getAttribute('userId')) && is_null($document->getAttribute('userInternalId'))) {
|
|
||||||
$internalId = $this->projectDB->getDocument('users', $document->getAttribute('userId'))->getInternalId();
|
|
||||||
$document->setAttribute('userInternalId', $internalId);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Add Internal ID 'teamId' for Subqueries.
|
|
||||||
*/
|
|
||||||
if (!empty($document->getAttribute('teamId')) && is_null($document->getAttribute('teamInternalId'))) {
|
|
||||||
$internalId = $this->projectDB->getDocument('teams', $document->getAttribute('teamId'))->getInternalId();
|
|
||||||
$document->setAttribute('teamInternalId', $internalId);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'platforms':
|
|
||||||
/**
|
|
||||||
* Migrate dateCreated to $createdAt.
|
|
||||||
*/
|
|
||||||
if (empty($document->getCreatedAt())) {
|
|
||||||
$document->setAttribute('$createdAt', $document->getAttribute('dateCreated'));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Migrate dateUpdated to $updatedAt.
|
|
||||||
*/
|
|
||||||
if (empty($document->getUpdatedAt())) {
|
|
||||||
$document->setAttribute('$updatedAt', $document->getAttribute('dateUpdated'));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Add Internal ID 'projectId' for Subqueries.
|
|
||||||
*/
|
|
||||||
if (!empty($document->getAttribute('projectId')) && is_null($document->getAttribute('projectInternalId'))) {
|
|
||||||
$internalId = $this->projectDB->getDocument('projects', $document->getAttribute('projectId'))->getInternalId();
|
|
||||||
$document->setAttribute('projectInternalId', $internalId);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'buckets':
|
|
||||||
/**
|
|
||||||
* Migrate dateCreated to $createdAt.
|
|
||||||
*/
|
|
||||||
if (empty($document->getCreatedAt())) {
|
|
||||||
$document->setAttribute('$createdAt', $document->getAttribute('dateCreated'));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Migrate dateUpdated to $updatedAt.
|
|
||||||
*/
|
|
||||||
if (empty($document->getUpdatedAt())) {
|
|
||||||
$document->setAttribute('$updatedAt', $document->getAttribute('dateUpdated'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrate all Storage Buckets to use Internal ID.
|
|
||||||
*/
|
|
||||||
$internalId = $this->projectDB->getDocument('buckets', $document->getId())->getInternalId();
|
|
||||||
$this->createNewMetaData("bucket_{$internalId}");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrate all Storage Bucket Files.
|
|
||||||
*/
|
|
||||||
$this->migrateBucketFiles($document);
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'users':
|
|
||||||
/**
|
|
||||||
* Set 'phoneVerification' to false if not set.
|
|
||||||
*/
|
|
||||||
if (is_null($document->getAttribute('phoneVerification'))) {
|
|
||||||
$document->setAttribute('phoneVerification', false);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'functions':
|
|
||||||
/**
|
|
||||||
* Migrate dateCreated to $createdAt.
|
|
||||||
*/
|
|
||||||
if (empty($document->getCreatedAt())) {
|
|
||||||
$document->setAttribute('$createdAt', $document->getAttribute('dateCreated'));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Migrate dateUpdated to $updatedAt.
|
|
||||||
*/
|
|
||||||
if (empty($document->getUpdatedAt())) {
|
|
||||||
$document->setAttribute('$updatedAt', $document->getAttribute('dateUpdated'));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'deployments':
|
|
||||||
case 'executions':
|
|
||||||
case 'teams':
|
|
||||||
/**
|
|
||||||
* Migrate dateCreated to $createdAt.
|
|
||||||
*/
|
|
||||||
if (empty($document->getCreatedAt())) {
|
|
||||||
$document->setAttribute('$createdAt', $document->getAttribute('dateCreated'));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $document;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates new metadata that was introduced for a collection and enforces the Internal ID.
|
|
||||||
*
|
|
||||||
* @param string $id
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
protected function createNewMetaData(string $id, string $to = null): void
|
|
||||||
{
|
|
||||||
$to ??= $id;
|
|
||||||
/**
|
|
||||||
* Skip files collection.
|
|
||||||
*/
|
|
||||||
if (in_array($id, ['files', 'databases'])) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Replace project UID with Internal ID.
|
|
||||||
*/
|
|
||||||
$this->pdo->prepare("ALTER TABLE IF EXISTS `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getId()}_{$id}` RENAME TO `_{$this->project->getInternalId()}_{$to}`")->execute();
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("Migrating {$id} Collection: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Replace project UID with Internal ID on permissions table.
|
|
||||||
*/
|
|
||||||
$this->pdo->prepare("ALTER TABLE IF EXISTS `{$this->projectDB->getDefaultDatabase()}`.`_{$this->project->getId()}_{$id}_perms` RENAME TO `_{$this->project->getInternalId()}_{$to}_perms`")->execute();
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("Migrating {$id} Collection: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Add _createdAt attribute.
|
|
||||||
*/
|
|
||||||
$this->pdo->prepare("ALTER TABLE `_{$this->project->getInternalId()}_{$to}` ADD COLUMN IF NOT EXISTS `_createdAt` int unsigned DEFAULT NULL")->execute();
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("Migrating {$id} Collection: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Add _updatedAt attribute.
|
|
||||||
*/
|
|
||||||
$this->pdo->prepare("ALTER TABLE `_{$this->project->getInternalId()}_{$to}` ADD COLUMN IF NOT EXISTS `_updatedAt` int unsigned DEFAULT NULL")->execute();
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("Migrating {$id} Collection: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Create index for _createdAt.
|
|
||||||
*/
|
|
||||||
$this->pdo->prepare("CREATE INDEX IF NOT EXISTS `_created_at` ON `_{$this->project->getInternalId()}_{$to}` (`_createdAt`)")->execute();
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("Migrating {$id} Collection: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Create index for _updatedAt.
|
|
||||||
*/
|
|
||||||
$this->pdo->prepare("CREATE INDEX IF NOT EXISTS `_updated_at` ON `_{$this->project->getInternalId()}_{$to}` (`_updatedAt`)")->execute();
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::warning("Migrating {$id} Collection: {$th->getMessage()}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Unit\Migration;
|
|
||||||
|
|
||||||
use ReflectionClass;
|
|
||||||
use Appwrite\Migration\Version\V12;
|
|
||||||
use Utopia\Database\Document;
|
|
||||||
use Utopia\Database\ID;
|
|
||||||
|
|
||||||
class MigrationV12Test extends MigrationTest
|
|
||||||
{
|
|
||||||
public function setUp(): void
|
|
||||||
{
|
|
||||||
$this->migration = new V12();
|
|
||||||
$reflector = new ReflectionClass('Appwrite\Migration\Version\V12');
|
|
||||||
$this->method = $reflector->getMethod('fixDocument');
|
|
||||||
$this->method->setAccessible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testMigrationProjects(): void
|
|
||||||
{
|
|
||||||
$document = $this->fixDocument(new Document([
|
|
||||||
'$id' => ID::custom('project'),
|
|
||||||
'$collection' => ID::custom('projects'),
|
|
||||||
'name' => 'Appwrite',
|
|
||||||
'version' => '0.12.0',
|
|
||||||
'search' => ''
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->assertEquals($document->getAttribute('version'), '0.13.0');
|
|
||||||
$this->assertEquals($document->getAttribute('search'), 'project Appwrite');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testMigrationUsers(): void
|
|
||||||
{
|
|
||||||
$document = $this->fixDocument(new Document([
|
|
||||||
'$id' => ID::custom('user'),
|
|
||||||
'$collection' => ID::custom('users'),
|
|
||||||
'email' => 'test@appwrite.io',
|
|
||||||
'name' => 'Torsten Dittmann'
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->assertEquals($document->getAttribute('search'), 'user test@appwrite.io Torsten Dittmann');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testMigrationTeams(): void
|
|
||||||
{
|
|
||||||
$document = $this->fixDocument(new Document([
|
|
||||||
'$id' => ID::custom('team'),
|
|
||||||
'$collection' => ID::custom('teams'),
|
|
||||||
'name' => 'Appwrite'
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->assertEquals($document->getAttribute('search'), 'team Appwrite');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testMigrationFunctions(): void
|
|
||||||
{
|
|
||||||
$document = $this->fixDocument(new Document([
|
|
||||||
'$id' => ID::custom('function'),
|
|
||||||
'$collection' => ID::custom('functions'),
|
|
||||||
'name' => 'My Function',
|
|
||||||
'runtime' => 'php-8.0'
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->assertEquals($document->getAttribute('search'), 'function My Function php-8.0');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testMigrationExecutions(): void
|
|
||||||
{
|
|
||||||
$document = $this->fixDocument(new Document([
|
|
||||||
'$id' => ID::custom('execution'),
|
|
||||||
'$collection' => ID::custom('executions'),
|
|
||||||
'functionId' => ID::custom('function')
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->assertEquals($document->getAttribute('search'), 'execution function');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Unit\Migration;
|
|
||||||
|
|
||||||
use ReflectionClass;
|
|
||||||
use Appwrite\Migration\Version\V13;
|
|
||||||
use Utopia\Database\Document;
|
|
||||||
use Utopia\Database\ID;
|
|
||||||
|
|
||||||
class MigrationV13Test extends MigrationTest
|
|
||||||
{
|
|
||||||
public function setUp(): void
|
|
||||||
{
|
|
||||||
$this->migration = new V13();
|
|
||||||
$reflector = new ReflectionClass('Appwrite\Migration\Version\V13');
|
|
||||||
$this->method = $reflector->getMethod('fixDocument');
|
|
||||||
$this->method->setAccessible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testMigrateFunctions(): void
|
|
||||||
{
|
|
||||||
$document = $this->fixDocument(new Document([
|
|
||||||
'$id' => ID::custom('func'),
|
|
||||||
'$collection' => ID::custom('functions'),
|
|
||||||
'events' => ['account.create', 'users.create']
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->assertEquals($document->getAttribute('events'), ['users.*.create']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testMigrationWebhooks(): void
|
|
||||||
{
|
|
||||||
$document = $this->fixDocument(new Document([
|
|
||||||
'$id' => ID::custom('webh'),
|
|
||||||
'$collection' => ID::custom('webhooks'),
|
|
||||||
'events' => ['account.create', 'users.create']
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->assertEquals($document->getAttribute('events'), ['users.*.create']);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,173 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Unit\Migration;
|
|
||||||
|
|
||||||
use ReflectionClass;
|
|
||||||
use Appwrite\Migration\Version\V14;
|
|
||||||
use Utopia\Database\Document;
|
|
||||||
use Utopia\Database\ID;
|
|
||||||
|
|
||||||
class MigrationV14Test extends MigrationTest
|
|
||||||
{
|
|
||||||
public function setUp(): void
|
|
||||||
{
|
|
||||||
$this->migration = new V14();
|
|
||||||
$reflector = new ReflectionClass('Appwrite\Migration\Version\V14');
|
|
||||||
$this->method = $reflector->getMethod('fixDocument');
|
|
||||||
$this->method->setAccessible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testMigrateProjects(): void
|
|
||||||
{
|
|
||||||
$document = $this->fixDocument(new Document([
|
|
||||||
'$id' => ID::custom('appwrite'),
|
|
||||||
'$collection' => ID::custom('projects'),
|
|
||||||
'version' => '0.14.0'
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->assertEquals($document->getAttribute('version'), '0.15.0');
|
|
||||||
$this->assertEquals($document->getAttribute('version'), '0.15.0');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testMigrateKeys(): void
|
|
||||||
{
|
|
||||||
$document = $this->fixDocument(new Document([
|
|
||||||
'$id' => ID::custom('appwrite'),
|
|
||||||
'$collection' => 'keys'
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->assertArrayHasKey('expire', $document->getArrayCopy());
|
|
||||||
$this->assertEquals($document->getAttribute('expire'), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testMigrateWebhooks(): void
|
|
||||||
{
|
|
||||||
$document = $this->fixDocument(new Document([
|
|
||||||
'$id' => ID::custom('appwrite'),
|
|
||||||
'$collection' => 'webhooks'
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->assertArrayHasKey('signatureKey', $document->getArrayCopy());
|
|
||||||
$this->assertEquals(strlen($document->getAttribute('signatureKey')), 128);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testMigrateUsers(): void
|
|
||||||
{
|
|
||||||
$document = $this->fixDocument(new Document([
|
|
||||||
'$id' => ID::custom('appwrite'),
|
|
||||||
'$collection' => ID::custom('users'),
|
|
||||||
'phoneVerification' => null
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->assertArrayHasKey('phoneVerification', $document->getArrayCopy());
|
|
||||||
$this->assertFalse($document->getAttribute('phoneVerification'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testMigratePlatforms(): void
|
|
||||||
{
|
|
||||||
$document = $this->fixDocument(new Document([
|
|
||||||
'$id' => ID::custom('appwrite'),
|
|
||||||
'$collection' => ID::custom('platforms'),
|
|
||||||
'$createdAt' => null,
|
|
||||||
'$updatedAt' => null,
|
|
||||||
'dateCreated' => 123456789,
|
|
||||||
'dateUpdated' => 987654321
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->assertEquals($document->getCreatedAt(), 123456789);
|
|
||||||
$this->assertEquals($document->getUpdatedAt(), 987654321);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testMigrateFunctions(): void
|
|
||||||
{
|
|
||||||
$document = $this->fixDocument(new Document([
|
|
||||||
'$id' => ID::custom('appwrite'),
|
|
||||||
'$collection' => ID::custom('functions'),
|
|
||||||
'$createdAt' => null,
|
|
||||||
'$updatedAt' => null,
|
|
||||||
'dateCreated' => 123456789,
|
|
||||||
'dateUpdated' => 987654321
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->assertEquals($document->getCreatedAt(), 123456789);
|
|
||||||
$this->assertEquals($document->getUpdatedAt(), 987654321);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testMigrateDeployments(): void
|
|
||||||
{
|
|
||||||
$document = $this->fixDocument(new Document([
|
|
||||||
'$id' => ID::custom('appwrite'),
|
|
||||||
'$collection' => ID::custom('deployments'),
|
|
||||||
'$createdAt' => null,
|
|
||||||
'dateCreated' => 123456789,
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->assertEquals($document->getCreatedAt(), 123456789);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testMigrateExecutions(): void
|
|
||||||
{
|
|
||||||
$document = $this->fixDocument(new Document([
|
|
||||||
'$id' => ID::custom('appwrite'),
|
|
||||||
'$collection' => ID::custom('executions'),
|
|
||||||
'$createdAt' => null,
|
|
||||||
'dateCreated' => 123456789,
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->assertEquals($document->getCreatedAt(), 123456789);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testMigrateTeams(): void
|
|
||||||
{
|
|
||||||
$document = $this->fixDocument(new Document([
|
|
||||||
'$id' => ID::custom('appwrite'),
|
|
||||||
'$collection' => ID::custom('teams'),
|
|
||||||
'$createdAt' => null,
|
|
||||||
'dateCreated' => 123456789,
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->assertEquals($document->getCreatedAt(), 123456789);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testMigrateAudits(): void
|
|
||||||
{
|
|
||||||
$document = $this->fixDocument(new Document([
|
|
||||||
'$id' => ID::custom('appwrite'),
|
|
||||||
'$collection' => ID::custom('audit'),
|
|
||||||
'resource' => 'collection/movies',
|
|
||||||
'event' => 'collections.movies.create'
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->assertEquals($document->getAttribute('resource'), 'database/default/collection/movies');
|
|
||||||
$this->assertEquals($document->getAttribute('event'), 'databases.default.collections.movies.create');
|
|
||||||
|
|
||||||
$document = $this->fixDocument(new Document([
|
|
||||||
'$id' => ID::custom('appwrite'),
|
|
||||||
'$collection' => ID::custom('audit'),
|
|
||||||
'resource' => 'document/avatar',
|
|
||||||
'event' => 'collections.movies.documents.avatar.create'
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->assertEquals($document->getAttribute('resource'), 'database/default/collection/movies/document/avatar');
|
|
||||||
$this->assertEquals($document->getAttribute('event'), 'databases.default.collections.movies.documents.avatar.create');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testMigrateStats(): void
|
|
||||||
{
|
|
||||||
$document = $this->fixDocument(new Document([
|
|
||||||
'$id' => ID::custom('appwrite'),
|
|
||||||
'$collection' => ID::custom('stats'),
|
|
||||||
'metric' => 'database.collections.62b2039844d4277495d0.documents.create'
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->assertEquals($document->getAttribute('metric'), 'databases.default.collections.62b2039844d4277495d0.documents.create');
|
|
||||||
|
|
||||||
$document = $this->fixDocument(new Document([
|
|
||||||
'$id' => ID::custom('appwrite'),
|
|
||||||
'$collection' => ID::custom('stats'),
|
|
||||||
'metric' => 'users.create'
|
|
||||||
]));
|
|
||||||
|
|
||||||
$this->assertEquals($document->getAttribute('metric'), 'users.create');
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue