mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
feat: moving team deletion to api
This commit is contained in:
@@ -19,6 +19,7 @@ use Appwrite\Utopia\Response;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
@@ -37,6 +38,7 @@ use Utopia\Database\Validator\Query\Limit;
|
||||
use Utopia\Database\Validator\Query\Offset;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
@@ -338,10 +340,16 @@ App::delete('/v1/teams/:teamId')
|
||||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->inject('response')
|
||||
->inject('getProjectDB')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForDeletes')
|
||||
->action(function (string $teamId, Response $response, Database $dbForProject, Event $queueForEvents, Delete $queueForDeletes) {
|
||||
->inject('deviceForFiles')
|
||||
->inject('deviceForFunctions')
|
||||
->inject('deviceForBuilds')
|
||||
->inject('deviceForCache')
|
||||
->inject('project')
|
||||
->action(function (string $teamId, Response $response, callable $getProjectDB, Database $dbForProject, Database $dbForConsole, Event $queueForEvents, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, Document $project) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
@@ -353,9 +361,166 @@ App::delete('/v1/teams/:teamId')
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove team from DB');
|
||||
}
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($team);
|
||||
$teamInternalId = $team->getInternalId();
|
||||
|
||||
// Loading deleting functions
|
||||
$deleteById = function (Document $document, Database $database, callable $callback = null): void {
|
||||
if ($database->deleteDocument($document->getCollection(), $document->getId())) {
|
||||
if (is_callable($callback)) {
|
||||
$callback($document);
|
||||
}
|
||||
} else {
|
||||
Console::error('Failed to delete document: ' . $document->getId());
|
||||
}
|
||||
};
|
||||
|
||||
$deleteRule = function (Database $dbForConsole, Document $document): void {
|
||||
|
||||
$domain = $document->getAttribute('domain');
|
||||
$directory = APP_STORAGE_CERTIFICATES . '/' . $domain;
|
||||
$checkTraversal = realpath($directory) === $directory;
|
||||
|
||||
if ($checkTraversal && is_dir($directory)) {
|
||||
// Delete files, so Traefik is aware of change
|
||||
array_map('unlink', glob($directory . '/*.*'));
|
||||
rmdir($directory);
|
||||
Console::info("Deleted certificate files for {$domain}");
|
||||
} else {
|
||||
Console::info("No certificate files found for {$domain}");
|
||||
}
|
||||
|
||||
// Delete certificate document, so Appwrite is aware of change
|
||||
if (isset($document['certificateId'])) {
|
||||
$dbForConsole->deleteDocument('certificates', $document['certificateId']);
|
||||
}
|
||||
};
|
||||
|
||||
$deleteByGroup = function (string $collection, array $queries, Database $database, callable $callback = null) use ($deleteById): void {
|
||||
$count = 0;
|
||||
$chunk = 0;
|
||||
$limit = 50;
|
||||
$sum = $limit;
|
||||
|
||||
$executionStart = \microtime(true);
|
||||
|
||||
while ($sum === $limit) {
|
||||
$chunk++;
|
||||
|
||||
$results = $database->find($collection, \array_merge([Query::limit($limit)], $queries));
|
||||
|
||||
$sum = count($results);
|
||||
|
||||
Console::info('Deleting chunk #' . $chunk . '. Found ' . $sum . ' documents');
|
||||
|
||||
foreach ($results as $document) {
|
||||
$deleteById($document, $database, $callback);
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
$executionEnd = \microtime(true);
|
||||
|
||||
Console::info("Deleted {$count} document by group in " . ($executionEnd - $executionStart) . " seconds");
|
||||
};
|
||||
|
||||
$deleteProject = function (Database $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, Document $document) use ($deleteByGroup, $deleteRule): void {
|
||||
$projectId = $document->getId();
|
||||
$projectInternalId = $document->getInternalId();
|
||||
|
||||
// Delete project tables
|
||||
$dbForProject = $getProjectDB($document);
|
||||
|
||||
while (true) {
|
||||
$collections = $dbForProject->listCollections();
|
||||
|
||||
if (empty($collections)) {
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($collections as $collection) {
|
||||
$dbForProject->deleteCollection($collection->getId());
|
||||
}
|
||||
}
|
||||
|
||||
// Delete Platforms
|
||||
$deleteByGroup('platforms', [
|
||||
Query::equal('projectInternalId', [$projectInternalId])
|
||||
], $dbForConsole);
|
||||
|
||||
// Delete project and function rules
|
||||
$deleteByGroup('rules', [
|
||||
Query::equal('projectInternalId', [$projectInternalId])
|
||||
], $dbForConsole, function (Document $document) use ($dbForConsole, $deleteRule) {
|
||||
$deleteRule($dbForConsole, $document);
|
||||
});
|
||||
|
||||
// Delete Keys
|
||||
$deleteByGroup('keys', [
|
||||
Query::equal('projectInternalId', [$projectInternalId])
|
||||
], $dbForConsole);
|
||||
|
||||
// Delete Webhooks
|
||||
$deleteByGroup('webhooks', [
|
||||
Query::equal('projectInternalId', [$projectInternalId])
|
||||
], $dbForConsole);
|
||||
|
||||
// Delete VCS Installations
|
||||
$deleteByGroup('installations', [
|
||||
Query::equal('projectInternalId', [$projectInternalId])
|
||||
], $dbForConsole);
|
||||
|
||||
// Delete VCS Repositories
|
||||
$deleteByGroup('repositories', [
|
||||
Query::equal('projectInternalId', [$projectInternalId]),
|
||||
], $dbForConsole);
|
||||
|
||||
// Delete VCS commments
|
||||
$deleteByGroup('vcsComments', [
|
||||
Query::equal('projectInternalId', [$projectInternalId]),
|
||||
], $dbForConsole);
|
||||
|
||||
// Delete metadata tables
|
||||
try {
|
||||
$dbForProject->deleteCollection('_metadata');
|
||||
} catch (\Throwable) {
|
||||
// Ignore: deleteCollection tries to delete a metadata entry after the collection is deleted,
|
||||
// which will throw an exception here because the metadata collection is already deleted.
|
||||
}
|
||||
|
||||
// Delete all storage directories
|
||||
$deviceForFiles->delete($deviceForFiles->getRoot(), true);
|
||||
$deviceForFunctions->delete($deviceForFunctions->getRoot(), true);
|
||||
$deviceForBuilds->delete($deviceForBuilds->getRoot(), true);
|
||||
$deviceForCache->delete($deviceForCache->getRoot(), true);
|
||||
};
|
||||
|
||||
$deleteProjectsByTeam = function (Database $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, Document $document) use ($deleteProject): void {
|
||||
|
||||
$projects = $dbForConsole->find('projects', [
|
||||
Query::equal('teamInternalId', [$document->getInternalId()])
|
||||
]);
|
||||
foreach ($projects as $project) {
|
||||
$deleteProject($dbForConsole, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $project);
|
||||
$dbForConsole->deleteDocument('projects', $project->getId());
|
||||
}
|
||||
};
|
||||
|
||||
// Delete Memberships
|
||||
$deleteByGroup(
|
||||
'memberships',
|
||||
[
|
||||
Query::equal('teamInternalId', [$teamInternalId])
|
||||
],
|
||||
$dbForProject,
|
||||
function (Document $membership) use ($dbForProject) {
|
||||
$userId = $membership->getAttribute('userId');
|
||||
$dbForProject->purgeCachedDocument('users', $userId);
|
||||
}
|
||||
);
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
$deleteProjectsByTeam($dbForConsole, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $team);
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setParam('teamId', $team->getId())
|
||||
|
||||
+4
-1
@@ -167,7 +167,6 @@ const DELETE_TYPE_PROJECTS = 'projects';
|
||||
const DELETE_TYPE_FUNCTIONS = 'functions';
|
||||
const DELETE_TYPE_DEPLOYMENTS = 'deployments';
|
||||
const DELETE_TYPE_USERS = 'users';
|
||||
const DELETE_TYPE_TEAMS = 'teams';
|
||||
const DELETE_TYPE_EXECUTIONS = 'executions';
|
||||
const DELETE_TYPE_AUDIT = 'audit';
|
||||
const DELETE_TYPE_ABUSE = 'abuse';
|
||||
@@ -1424,6 +1423,10 @@ App::setResource('deviceForBuilds', function ($project) {
|
||||
return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
|
||||
}, ['project']);
|
||||
|
||||
App::setResource('deviceForCache', function (Document $project) {
|
||||
return getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId());
|
||||
}, ['project']);
|
||||
|
||||
function getDevice($root): Device
|
||||
{
|
||||
$connection = System::getEnv('_APP_CONNECTIONS_STORAGE', '');
|
||||
|
||||
@@ -93,12 +93,6 @@ class Deletes extends Action
|
||||
case DELETE_TYPE_USERS:
|
||||
$this->deleteUser($getProjectDB, $document, $project);
|
||||
break;
|
||||
case DELETE_TYPE_TEAMS:
|
||||
$this->deleteMemberships($getProjectDB, $document, $project);
|
||||
if ($project->getId() === 'console') {
|
||||
$this->deleteProjectsByTeam($dbForConsole, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $document);
|
||||
}
|
||||
break;
|
||||
case DELETE_TYPE_BUCKETS:
|
||||
$this->deleteBucket($getProjectDB, $deviceForFiles, $document, $project);
|
||||
break;
|
||||
@@ -410,54 +404,6 @@ class Deletes extends Action
|
||||
], $dbForProject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $getProjectDB
|
||||
* @param Document $document teams document
|
||||
* @param Document $project
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function deleteMemberships(callable $getProjectDB, Document $document, Document $project): void
|
||||
{
|
||||
$dbForProject = $getProjectDB($project);
|
||||
$teamInternalId = $document->getInternalId();
|
||||
|
||||
// Delete Memberships
|
||||
$this->deleteByGroup(
|
||||
'memberships',
|
||||
[
|
||||
Query::equal('teamInternalId', [$teamInternalId])
|
||||
],
|
||||
$dbForProject,
|
||||
function (Document $membership) use ($dbForProject) {
|
||||
$userId = $membership->getAttribute('userId');
|
||||
$dbForProject->purgeCachedDocument('users', $userId);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Database $dbForConsole
|
||||
* @param Document $document
|
||||
* @return void
|
||||
* @throws Authorization
|
||||
* @throws \Utopia\Database\Exception
|
||||
* @throws Conflict
|
||||
* @throws Restricted
|
||||
* @throws Structure
|
||||
*/
|
||||
private function deleteProjectsByTeam(Database $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, Document $document): void
|
||||
{
|
||||
|
||||
$projects = $dbForConsole->find('projects', [
|
||||
Query::equal('teamInternalId', [$document->getInternalId()])
|
||||
]);
|
||||
foreach ($projects as $project) {
|
||||
$this->deleteProject($dbForConsole, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $project);
|
||||
$dbForConsole->deleteDocument('projects', $project->getId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Database $dbForConsole
|
||||
* @param callable $getProjectDB
|
||||
|
||||
Reference in New Issue
Block a user