feat: moving team deletion to api

This commit is contained in:
Binyamin Yawitz
2024-05-29 13:59:18 -04:00
parent 1cb490a36e
commit 3c012679fb
3 changed files with 174 additions and 60 deletions
+170 -5
View File
@@ -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
View File
@@ -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', '');
-54
View File
@@ -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