mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge remote-tracking branch 'upstream/documents-db-api' into vector-db-api
This commit is contained in:
@@ -31,6 +31,8 @@ use Utopia\Queue\Message;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\System\System;
|
||||
|
||||
use function Swoole\Coroutine\batch;
|
||||
|
||||
class Deletes extends Action
|
||||
{
|
||||
protected array $selects = ['$sequence', '$id', '$collection', '$permissions', '$updatedAt'];
|
||||
@@ -51,6 +53,7 @@ class Deletes extends Action
|
||||
->inject('project')
|
||||
->inject('dbForPlatform')
|
||||
->inject('getProjectDB')
|
||||
->inject('getDatabasesDB')
|
||||
->inject('getLogsDB')
|
||||
->inject('deviceForFiles')
|
||||
->inject('deviceForFunctions')
|
||||
@@ -74,6 +77,7 @@ class Deletes extends Action
|
||||
Document $project,
|
||||
Database $dbForPlatform,
|
||||
callable $getProjectDB,
|
||||
callable $getDatabasesDB,
|
||||
callable $getLogsDB,
|
||||
Device $deviceForFiles,
|
||||
Device $deviceForFunctions,
|
||||
@@ -106,7 +110,7 @@ class Deletes extends Action
|
||||
case DELETE_TYPE_DOCUMENT:
|
||||
switch ($document->getCollection()) {
|
||||
case DELETE_TYPE_PROJECTS:
|
||||
$this->deleteProject($dbForPlatform, $getProjectDB, $deviceForFiles, $deviceForSites, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $document);
|
||||
$this->deleteProject($dbForPlatform, $getProjectDB, $getDatabasesDB, $deviceForFiles, $deviceForSites, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $document);
|
||||
break;
|
||||
case DELETE_TYPE_SITES:
|
||||
$this->deleteSite($dbForPlatform, $getProjectDB, $deviceForSites, $deviceForBuilds, $deviceForFiles, $document, $certificates, $project);
|
||||
@@ -138,7 +142,7 @@ class Deletes extends Action
|
||||
}
|
||||
break;
|
||||
case DELETE_TYPE_TEAM_PROJECTS:
|
||||
$this->deleteProjectsByTeam($dbForPlatform, $getProjectDB, $certificates, $document);
|
||||
$this->deleteProjectsByTeam($dbForPlatform, $getProjectDB, $getDatabasesDB, $certificates, $document);
|
||||
break;
|
||||
case DELETE_TYPE_EXECUTIONS:
|
||||
$this->deleteExecutionLogs($project, $getProjectDB, $executionRetention);
|
||||
@@ -468,7 +472,7 @@ class Deletes extends Action
|
||||
* @throws Structure
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function deleteProjectsByTeam(Database $dbForPlatform, callable $getProjectDB, CertificatesAdapter $certificates, Document $document): void
|
||||
protected function deleteProjectsByTeam(Database $dbForPlatform, callable $getProjectDB, callable $getDatabasesDB, CertificatesAdapter $certificates, Document $document): void
|
||||
{
|
||||
|
||||
$projects = $dbForPlatform->find('projects', [
|
||||
@@ -483,7 +487,7 @@ class Deletes extends Action
|
||||
$deviceForBuilds = getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
|
||||
$deviceForCache = getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId());
|
||||
|
||||
$this->deleteProject($dbForPlatform, $getProjectDB, $deviceForFiles, $deviceForSites, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $project);
|
||||
$this->deleteProject($dbForPlatform, $getProjectDB, $getDatabasesDB, $deviceForFiles, $deviceForSites, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $project);
|
||||
$dbForPlatform->deleteDocument('projects', $project->getId());
|
||||
}
|
||||
}
|
||||
@@ -501,7 +505,7 @@ class Deletes extends Action
|
||||
* @throws Authorization
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
protected function deleteProject(Database $dbForPlatform, callable $getProjectDB, Device $deviceForFiles, Device $deviceForSites, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, Document $document): void
|
||||
protected function deleteProject(Database $dbForPlatform, callable $getProjectDB, callable $getDatabasesDB, Device $deviceForFiles, Device $deviceForSites, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, Document $document): void
|
||||
{
|
||||
$projectInternalId = $document->getSequence();
|
||||
$projectId = $document->getId();
|
||||
@@ -527,27 +531,45 @@ class Deletes extends Action
|
||||
$projectTables = !\in_array($dsn->getHost(), $sharedTables);
|
||||
$sharedTablesV1 = \in_array($dsn->getHost(), $sharedTablesV1);
|
||||
$sharedTablesV2 = !$projectTables && !$sharedTablesV1;
|
||||
$databaseDSNKeys = ['database','documentsDatabase'];
|
||||
|
||||
/**
|
||||
* @var $dbForProject Database
|
||||
*/
|
||||
$dbForProject->foreach(Database::METADATA, function (Document $collection) use ($dbForProject, $projectTables, $projectCollectionIds) {
|
||||
try {
|
||||
if ($projectTables || !\in_array($collection->getId(), $projectCollectionIds)) {
|
||||
$dbForProject->deleteCollection($collection->getId());
|
||||
} else {
|
||||
$this->deleteByGroup(
|
||||
$collection->getId(),
|
||||
[
|
||||
Query::orderAsc()
|
||||
],
|
||||
database: $dbForProject
|
||||
);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
Console::error('Error deleting ' . $collection->getId() . ' ' . $e->getMessage());
|
||||
}
|
||||
});
|
||||
$exectionActionPerDatabase = function (string $databaseDSNKey, $callback) use ($getDatabasesDB, $document) {
|
||||
/**
|
||||
* @var Database $dbForDatabases
|
||||
*/
|
||||
$dbForDatabases = $getDatabasesDB(new Document(['database' => $document->getAttribute($databaseDSNKey)]), $document);
|
||||
$callback($dbForDatabases);
|
||||
};
|
||||
|
||||
batch(array_map(
|
||||
fn ($databaseDSNKey) =>
|
||||
fn () => $exectionActionPerDatabase(
|
||||
$databaseDSNKey,
|
||||
function (Database $dbForDatabases) use ($projectTables, $projectCollectionIds) {
|
||||
$dbForDatabases->foreach(
|
||||
Database::METADATA,
|
||||
function (Document $collection) use ($dbForDatabases, $projectTables, $projectCollectionIds) {
|
||||
try {
|
||||
if ($projectTables || !\in_array($collection->getId(), $projectCollectionIds, true)) {
|
||||
$dbForDatabases->deleteCollection($collection->getId());
|
||||
} else {
|
||||
$this->deleteByGroup(
|
||||
$collection->getId(),
|
||||
[Query::orderAsc()],
|
||||
database: $dbForDatabases
|
||||
);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
Console::error(
|
||||
'Error deleting ' . $collection->getId() . ' ' . $e->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
),
|
||||
$databaseDSNKeys
|
||||
));
|
||||
|
||||
// Delete Platforms
|
||||
$this->deleteByGroup('platforms', [
|
||||
@@ -601,7 +623,15 @@ class Deletes extends Action
|
||||
|
||||
// Delete metadata table
|
||||
if ($projectTables) {
|
||||
$dbForProject->deleteCollection(Database::METADATA);
|
||||
batch(array_map(
|
||||
fn ($databaseDSNKey) => fn () =>
|
||||
$exectionActionPerDatabase(
|
||||
$databaseDSNKey,
|
||||
fn (Database $dbForDatabases) =>
|
||||
$dbForDatabases->deleteCollection(Database::METADATA)
|
||||
),
|
||||
$databaseDSNKeys
|
||||
));
|
||||
} elseif ($sharedTablesV1) {
|
||||
$this->deleteByGroup(
|
||||
Database::METADATA,
|
||||
|
||||
@@ -68,6 +68,8 @@ class StatsUsage extends Action
|
||||
METRIC_BUILDS => true,
|
||||
METRIC_COLLECTIONS => true,
|
||||
METRIC_DOCUMENTS => true,
|
||||
METRIC_COLLECTIONS_DOCUMENTSDB => true,
|
||||
METRIC_DOCUMENTS_DOCUMENTSDB => true,
|
||||
METRIC_COLLECTIONS_VECTORDB => true,
|
||||
METRIC_DOCUMENTS_VECTORDB => true,
|
||||
METRIC_DATABASES_STORAGE => true,
|
||||
|
||||
@@ -12,6 +12,8 @@ use Tests\E2E\Scopes\SideClient;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\System\System;
|
||||
|
||||
@@ -112,6 +114,92 @@ class ProjectsConsoleClientTest extends Scope
|
||||
];
|
||||
}
|
||||
|
||||
public function testDeleteProjectWithMultiDB(): void
|
||||
{
|
||||
// Create a team and project
|
||||
$team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'teamId' => ID::unique(),
|
||||
'name' => 'MultiDB Team',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $team['headers']['status-code']);
|
||||
$teamId = $team['body']['$id'];
|
||||
|
||||
$project = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'projectId' => ID::unique(),
|
||||
'name' => 'MultiDB Project',
|
||||
'teamId' => $teamId,
|
||||
'region' => System::getEnv('_APP_REGION', 'default')
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $project['headers']['status-code']);
|
||||
$projectId = $project['body']['$id'];
|
||||
|
||||
$projectAdminHeaders = array_merge($this->getHeaders(), [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-mode' => 'admin',
|
||||
]);
|
||||
|
||||
// Create legacy database and collection
|
||||
$database = $this->client->call(Client::METHOD_POST, '/databases', $projectAdminHeaders, [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'Legacy DB',
|
||||
]);
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
$databaseId = $database['body']['$id'];
|
||||
|
||||
$collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', $projectAdminHeaders, [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Legacy Collection',
|
||||
'documentSecurity' => true,
|
||||
'permissions' => [
|
||||
Permission::create(Role::any()),
|
||||
],
|
||||
]);
|
||||
$this->assertEquals(201, $collection['headers']['status-code']);
|
||||
|
||||
// Create documentsdb database and collection
|
||||
$documentsDb = $this->client->call(Client::METHOD_POST, '/documentsdb', $projectAdminHeaders, [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'Documents DB',
|
||||
]);
|
||||
$this->assertEquals(201, $documentsDb['headers']['status-code']);
|
||||
$documentsDbId = $documentsDb['body']['$id'];
|
||||
|
||||
$documentsCollection = $this->client->call(Client::METHOD_POST, '/documentsdb/' . $documentsDbId . '/collections', $projectAdminHeaders, [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Documents Collection',
|
||||
'documentSecurity' => true,
|
||||
'permissions' => [
|
||||
Permission::create(Role::any()),
|
||||
],
|
||||
]);
|
||||
$this->assertEquals(201, $documentsCollection['headers']['status-code']);
|
||||
|
||||
// Delete project
|
||||
$delete = $this->client->call(Client::METHOD_DELETE, '/projects/' . $projectId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(204, $delete['headers']['status-code']);
|
||||
|
||||
// Ensure project is gone
|
||||
$getProject = $this->client->call(Client::METHOD_GET, '/projects/' . $projectId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(404, $getProject['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testCreateDuplicateProject(): void
|
||||
{
|
||||
// Create a team
|
||||
|
||||
Reference in New Issue
Block a user