appwrite/src/Appwrite/Platform/Workers/Deletes.php

1347 lines
51 KiB
PHP
Raw Normal View History

<?php
2023-06-05 16:13:00 +00:00
namespace Appwrite\Platform\Workers;
use Appwrite\Auth\Auth;
2024-11-20 10:10:57 +00:00
use Appwrite\Certificates\Adapter as CertificatesAdapter;
2025-04-25 14:25:26 +00:00
use Appwrite\Deletes\Identities;
use Appwrite\Deletes\Targets;
use Appwrite\Extend\Exception;
2023-08-23 20:19:22 +00:00
use Executor\Executor;
2023-10-01 08:04:12 +00:00
use Throwable;
use Utopia\Abuse\Adapters\TimeLimit\Database as AbuseDatabase;
2023-06-05 16:13:00 +00:00
use Utopia\Audit\Audit;
use Utopia\Cache\Adapter\Filesystem;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
2024-05-09 04:52:53 +00:00
use Utopia\Config\Config;
2024-03-06 17:34:21 +00:00
use Utopia\Database\Database;
2022-11-21 14:24:52 +00:00
use Utopia\Database\DateTime;
2023-06-05 16:13:00 +00:00
use Utopia\Database\Document;
2024-03-06 17:34:21 +00:00
use Utopia\Database\Exception as DatabaseException;
2023-06-05 16:13:00 +00:00
use Utopia\Database\Exception\Authorization;
2023-10-01 08:04:12 +00:00
use Utopia\Database\Exception\Conflict;
use Utopia\Database\Exception\Restricted;
use Utopia\Database\Exception\Structure;
2023-06-05 16:13:00 +00:00
use Utopia\Database\Query;
2025-02-17 12:03:56 +00:00
use Utopia\Database\Validator\Authorization as ValidatorAuthorization;
2024-05-09 04:52:53 +00:00
use Utopia\DSN\DSN;
use Utopia\Logger\Log;
2023-06-05 16:13:00 +00:00
use Utopia\Platform\Action;
use Utopia\Queue\Message;
use Utopia\Storage\Device;
2024-04-01 11:02:47 +00:00
use Utopia\System\System;
2023-06-05 16:13:00 +00:00
class Deletes extends Action
{
2025-05-26 05:42:11 +00:00
protected array $selects = ['$sequence', '$id', '$collection', '$permissions', '$updatedAt'];
2025-03-26 07:20:44 +00:00
2023-06-05 16:13:00 +00:00
public static function getName(): string
2022-05-16 09:58:17 +00:00
{
2023-06-05 16:13:00 +00:00
return 'deletes';
}
2023-06-05 16:13:00 +00:00
/**
* @throws Exception
*/
public function __construct()
{
2023-06-05 16:13:00 +00:00
$this
->desc('Deletes worker')
->inject('message')
2025-01-16 06:05:22 +00:00
->inject('project')
->inject('dbForPlatform')
2023-06-05 16:13:00 +00:00
->inject('getProjectDB')
2025-03-07 14:04:52 +00:00
->inject('getLogsDB')
2024-02-20 14:10:51 +00:00
->inject('deviceForFiles')
->inject('deviceForFunctions')
->inject('deviceForSites')
2024-02-20 14:10:51 +00:00
->inject('deviceForBuilds')
->inject('deviceForCache')
2024-11-20 10:10:57 +00:00
->inject('certificates')
->inject('executor')
2023-12-12 11:26:44 +00:00
->inject('executionRetention')
->inject('auditRetention')
->inject('log')
2025-05-14 06:27:51 +00:00
->callback($this->action(...));
}
2023-06-05 16:13:00 +00:00
/**
* @throws Exception
2023-10-01 08:04:12 +00:00
* @throws Throwable
2023-06-05 16:13:00 +00:00
*/
public function action(
Message $message,
Document $project,
Database $dbForPlatform,
callable $getProjectDB,
callable $getLogsDB,
Device $deviceForFiles,
Device $deviceForFunctions,
Device $deviceForSites,
Device $deviceForBuilds,
Device $deviceForCache,
CertificatesAdapter $certificates,
2025-04-08 08:41:39 +00:00
Executor $executor,
string $executionRetention,
string $auditRetention,
Log $log
): void {
2023-06-05 16:13:00 +00:00
$payload = $message->getPayload() ?? [];
if (empty($payload)) {
throw new Exception('Missing payload');
}
2025-09-02 13:31:53 +00:00
$type = $payload['type'] ?? '';
2023-06-05 16:13:00 +00:00
$datetime = $payload['datetime'] ?? null;
$hourlyUsageRetentionDatetime = $payload['hourlyUsageRetentionDatetime'] ?? null;
$resource = $payload['resource'] ?? null;
$resourceType = $payload['resourceType'] ?? null;
2023-06-05 16:13:00 +00:00
$document = new Document($payload['document'] ?? []);
$log->addTag('projectId', $project->getId());
$log->addTag('type', $type);
2024-01-02 13:02:11 +00:00
switch (\strval($type)) {
2020-12-18 14:05:15 +00:00
case DELETE_TYPE_DOCUMENT:
2021-06-12 10:07:26 +00:00
switch ($document->getCollection()) {
2021-10-26 00:14:55 +00:00
case DELETE_TYPE_PROJECTS:
$this->deleteProject($dbForPlatform, $getProjectDB, $deviceForFiles, $deviceForSites, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $certificates, $document);
break;
case DELETE_TYPE_SITES:
2025-02-25 15:42:25 +00:00
$this->deleteSite($dbForPlatform, $getProjectDB, $deviceForSites, $deviceForBuilds, $deviceForFiles, $document, $certificates, $project);
break;
2021-10-26 00:14:55 +00:00
case DELETE_TYPE_FUNCTIONS:
$this->deleteFunction($dbForPlatform, $getProjectDB, $deviceForFunctions, $deviceForBuilds, $certificates, $document, $project, $executor);
break;
case DELETE_TYPE_DEPLOYMENTS:
2025-04-08 08:41:39 +00:00
$this->deleteDeployment($dbForPlatform, $getProjectDB, $deviceForFunctions, $deviceForSites, $deviceForBuilds, $deviceForFiles, $document, $certificates, $project, $executor);
break;
2021-10-26 00:14:55 +00:00
case DELETE_TYPE_USERS:
2023-06-05 16:13:00 +00:00
$this->deleteUser($getProjectDB, $document, $project);
break;
2021-11-07 05:54:28 +00:00
case DELETE_TYPE_BUCKETS:
2024-02-20 14:10:51 +00:00
$this->deleteBucket($getProjectDB, $deviceForFiles, $document, $project);
break;
case DELETE_TYPE_INSTALLATIONS:
$this->deleteInstallation($dbForPlatform, $getProjectDB, $document, $project);
break;
2023-03-10 07:42:52 +00:00
case DELETE_TYPE_RULES:
$this->deleteRule($dbForPlatform, $document, $certificates);
2021-07-28 10:09:29 +00:00
break;
2025-08-14 12:32:03 +00:00
case DELETE_TYPE_TRANSACTION:
$this->deleteTransactionLogs($getProjectDB, $document, $project);
break;
default:
2021-09-01 09:13:23 +00:00
Console::error('No lazy delete operation available for document of type: ' . $document->getCollection());
break;
}
2020-12-14 21:26:37 +00:00
break;
2024-06-27 22:27:04 +00:00
case DELETE_TYPE_TEAM_PROJECTS:
$this->deleteProjectsByTeam($dbForPlatform, $getProjectDB, $certificates, $document);
2024-06-27 22:27:04 +00:00
break;
2020-12-27 17:57:35 +00:00
case DELETE_TYPE_EXECUTIONS:
2023-12-12 11:26:44 +00:00
$this->deleteExecutionLogs($project, $getProjectDB, $executionRetention);
2020-12-21 18:15:52 +00:00
break;
2020-12-18 14:05:15 +00:00
case DELETE_TYPE_AUDIT:
2023-12-12 11:26:44 +00:00
if (!$project->isEmpty()) {
$this->deleteAuditLogs($project, $getProjectDB, $auditRetention);
}
break;
case DELETE_TYPE_REALTIME:
$this->deleteRealtimeUsage($dbForPlatform, $datetime);
break;
case DELETE_TYPE_SESSIONS:
2023-12-12 11:26:44 +00:00
$this->deleteExpiredSessions($project, $getProjectDB);
break;
case DELETE_TYPE_USAGE:
2025-03-07 14:04:52 +00:00
$this->deleteUsageStats($project, $getProjectDB, $getLogsDB, $hourlyUsageRetentionDatetime);
2021-09-06 09:39:36 +00:00
break;
2022-08-15 09:05:41 +00:00
case DELETE_TYPE_CACHE_BY_RESOURCE:
$this->deleteCacheByResource($project, $getProjectDB, $resource, $resourceType);
2022-08-15 09:05:41 +00:00
break;
case DELETE_TYPE_CACHE_BY_TIMESTAMP:
2023-10-01 08:04:12 +00:00
$this->deleteCacheByDate($project, $getProjectDB, $datetime);
2022-07-03 09:36:59 +00:00
break;
2022-11-16 12:51:43 +00:00
case DELETE_TYPE_SCHEDULES:
$this->deleteSchedules($dbForPlatform, $getProjectDB, $datetime);
2022-11-16 12:51:43 +00:00
break;
2023-10-25 17:33:23 +00:00
case DELETE_TYPE_TOPIC:
$this->deleteTopic($project, $getProjectDB, $document);
2023-10-17 17:23:26 +00:00
break;
case DELETE_TYPE_TARGET:
2025-04-25 14:25:26 +00:00
Targets::deleteSubscribers($getProjectDB($project), $document);
break;
2024-01-19 04:23:44 +00:00
case DELETE_TYPE_EXPIRED_TARGETS:
$this->deleteExpiredTargets($project, $getProjectDB);
break;
case DELETE_TYPE_SESSION_TARGETS:
$this->deleteSessionTargets($project, $getProjectDB, $document);
break;
2025-01-16 07:44:47 +00:00
case DELETE_TYPE_MAINTENANCE:
2025-01-16 11:29:30 +00:00
$this->deleteExpiredTargets($project, $getProjectDB);
$this->deleteExecutionLogs($project, $getProjectDB, $executionRetention);
$this->deleteAuditLogs($project, $getProjectDB, $auditRetention);
2025-03-07 14:04:52 +00:00
$this->deleteUsageStats($project, $getProjectDB, $getLogsDB, $hourlyUsageRetentionDatetime);
2025-01-16 11:29:30 +00:00
$this->deleteExpiredSessions($project, $getProjectDB);
2025-09-02 13:31:53 +00:00
$this->deleteExpiredTransactions($project, $getProjectDB);
2025-01-16 07:44:47 +00:00
break;
2020-12-19 08:08:03 +00:00
default:
2024-01-02 13:02:11 +00:00
throw new \Exception('No delete operation for type: ' . \strval($type));
2021-09-01 09:13:23 +00:00
}
}
2022-11-16 12:51:43 +00:00
/**
* @param Database $dbForPlatform
2023-06-05 16:13:00 +00:00
* @param callable $getProjectDB
* @param string $datetime
2024-01-15 05:26:30 +00:00
* @param Document|null $document
2023-06-05 16:13:00 +00:00
* @return void
* @throws Authorization
2024-01-15 05:26:30 +00:00
* @throws Conflict
* @throws Restricted
* @throws Structure
* @throws DatabaseException
2022-11-16 12:51:43 +00:00
*/
private function deleteSchedules(Database $dbForPlatform, callable $getProjectDB, string $datetime): void
2022-11-16 12:51:43 +00:00
{
// Temporarly accepting both 'fra' and 'default'
// When all migrated, only use _APP_REGION with 'default' as default value
$regions = [System::getEnv('_APP_REGION', 'default')];
if (!in_array('default', $regions)) {
$regions[] = 'default';
}
2022-12-14 08:44:40 +00:00
$this->listByGroup(
2022-11-16 14:31:49 +00:00
'schedules',
[
Query::equal('region', $regions),
2022-11-16 12:51:43 +00:00
Query::lessThanEqual('resourceUpdatedAt', $datetime),
Query::equal('active', [false]),
2022-11-16 14:31:49 +00:00
],
$dbForPlatform,
function (Document $document) use ($dbForPlatform, $getProjectDB) {
$project = $dbForPlatform->getDocument('projects', $document->getAttribute('projectId'));
2022-11-16 12:51:43 +00:00
if ($project->isEmpty()) {
$dbForPlatform->deleteDocument('schedules', $document->getId());
Console::success('Deleted schedule for deleted project ' . $document->getAttribute('projectId'));
2022-11-16 14:31:49 +00:00
return;
2022-11-16 12:51:43 +00:00
}
2024-02-20 14:25:01 +00:00
$collectionId = match ($document->getAttribute('resourceType')) {
'function' => 'functions',
2025-01-20 09:43:50 +00:00
'execution' => 'executions',
2024-02-20 14:25:01 +00:00
'message' => 'messages'
};
try {
$resource = $getProjectDB($project)->getDocument(
$collectionId,
$document->getAttribute('resourceId')
);
} catch (Throwable $e) {
Console::error('Failed to get resource for schedule ' . $document->getId() . ' ' . $e->getMessage());
return;
}
2022-11-16 12:51:43 +00:00
2024-01-15 06:00:41 +00:00
$delete = true;
switch ($document->getAttribute('resourceType')) {
case 'function':
$delete = $resource->isEmpty();
break;
2025-01-20 09:43:50 +00:00
case 'execution':
$delete = false;
break;
2024-01-15 06:00:41 +00:00
}
2022-11-16 12:51:43 +00:00
2024-01-15 06:00:41 +00:00
if ($delete) {
$dbForPlatform->deleteDocument('schedules', $document->getId());
2024-01-15 06:00:41 +00:00
Console::success('Deleting schedule for ' . $document->getAttribute('resourceType') . ' ' . $document->getAttribute('resourceId'));
2022-11-16 12:51:43 +00:00
}
}
2022-11-16 14:31:49 +00:00
);
2022-11-16 12:51:43 +00:00
}
2023-10-17 17:23:26 +00:00
/**
* @param Document $project
* @param callable $getProjectDB
2023-10-17 17:23:26 +00:00
* @param Document $topic
* @throws Exception
*/
private function deleteTopic(Document $project, callable $getProjectDB, Document $topic)
2023-10-17 17:23:26 +00:00
{
if ($topic->isEmpty()) {
Console::error('Failed to delete subscribers. Topic not found');
return;
}
$this->deleteByGroup(
'subscribers',
[
2025-05-26 05:42:11 +00:00
Query::equal('topicInternalId', [$topic->getSequence()]),
2025-03-17 15:05:40 +00:00
Query::orderAsc(),
],
$getProjectDB($project)
);
2023-10-17 17:23:26 +00:00
}
2024-01-19 04:23:44 +00:00
/**
* @param Document $project
* @param callable $getProjectDB
* @param Document $target
* @return void
* @throws Exception
*/
private function deleteExpiredTargets(Document $project, callable $getProjectDB): void
2024-01-19 04:23:44 +00:00
{
2025-04-25 14:25:26 +00:00
Targets::delete($getProjectDB($project), Query::equal('expired', [true]));
2024-01-19 04:23:44 +00:00
}
private function deleteSessionTargets(Document $project, callable $getProjectDB, Document $session): void
{
2025-05-26 05:42:11 +00:00
Targets::delete($getProjectDB($project), Query::equal('sessionInternalId', [$session->getSequence()]));
}
2022-07-03 09:36:59 +00:00
/**
2023-06-13 08:15:38 +00:00
* @param Document $project
2023-06-05 16:13:00 +00:00
* @param callable $getProjectDB
* @param string $resource
2025-09-02 13:31:53 +00:00
* @param string|null $resourceType
2023-10-01 17:39:26 +00:00
* @return void
2023-10-01 08:04:12 +00:00
* @throws Authorization
* @throws Exception
2022-07-03 09:36:59 +00:00
*/
private function deleteCacheByResource(Document $project, callable $getProjectDB, string $resource, string $resourceType = null): void
2022-08-15 09:05:41 +00:00
{
2023-06-13 08:15:38 +00:00
$projectId = $project->getId();
2023-10-01 08:04:12 +00:00
$dbForProject = $getProjectDB($project);
$cache = new Cache(
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId)
);
2025-03-19 14:37:15 +00:00
$queries = [
Query::equal('resource', [$resource])
];
2025-03-17 15:05:40 +00:00
if (!empty($resourceType)) {
2025-03-19 14:37:15 +00:00
$queries[] = Query::equal('resourceType', [$resourceType]);
}
2025-03-26 07:20:44 +00:00
$queries[] = Query::select($this->selects);
2025-03-19 14:37:15 +00:00
$queries[] = Query::orderAsc();
2025-03-17 15:05:40 +00:00
$this->deleteByGroup(
'cache',
2025-03-19 14:37:15 +00:00
$queries,
$dbForProject,
function (Document $document) use ($cache, $projectId) {
$path = APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId . DIRECTORY_SEPARATOR . $document->getId();
if ($cache->purge($document->getId())) {
Console::success('Deleting cache file: ' . $path);
} else {
Console::error('Failed to delete cache file: ' . $path);
}
}
);
2022-08-15 09:05:41 +00:00
}
2023-06-13 08:15:38 +00:00
/**
2023-10-01 08:04:12 +00:00
* Document $project
* @param Document $project
* @param callable $getProjectDB
2023-06-13 08:15:38 +00:00
* @param string $datetime
2023-10-01 08:04:12 +00:00
* @return void
2023-06-13 08:15:38 +00:00
* @throws Exception
*/
2023-10-17 18:32:38 +00:00
private function deleteCacheByDate(Document $project, callable $getProjectDB, string $datetime): void
2022-07-03 09:36:59 +00:00
{
2023-10-01 08:04:12 +00:00
$projectId = $project->getId();
$dbForProject = $getProjectDB($project);
2022-07-03 09:36:59 +00:00
2023-10-01 08:04:12 +00:00
$cache = new Cache(
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId)
);
2023-06-13 08:15:38 +00:00
2025-03-20 07:07:32 +00:00
$queries = [
2025-03-28 06:16:25 +00:00
Query::select([...$this->selects, 'accessedAt']),
2023-10-01 08:04:12 +00:00
Query::lessThan('accessedAt', $datetime),
Query::orderDesc('accessedAt'),
2025-03-17 15:05:40 +00:00
Query::orderDesc(),
2023-10-01 08:04:12 +00:00
];
2023-10-01 08:04:12 +00:00
$this->deleteByGroup(
'cache',
2025-03-20 07:07:32 +00:00
$queries,
2023-10-01 08:04:12 +00:00
$dbForProject,
function (Document $document) use ($cache, $projectId) {
$path = APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId . DIRECTORY_SEPARATOR . $document->getId();
if ($cache->purge($document->getId())) {
Console::success('Deleting cache file: ' . $path);
} else {
Console::error('Failed to delete cache file: ' . $path);
2022-07-05 07:37:46 +00:00
}
2023-10-01 08:04:12 +00:00
}
);
2022-07-03 09:36:59 +00:00
}
2021-08-27 11:37:52 +00:00
/**
* @param Database $dbForPlatform
2023-06-05 16:13:00 +00:00
* @param callable $getProjectDB
2022-10-28 08:40:04 +00:00
* @param string $hourlyUsageRetentionDatetime
2023-10-01 17:39:26 +00:00
* @return void
* @throws Exception
2021-08-27 11:37:52 +00:00
*/
2025-03-07 14:04:52 +00:00
private function deleteUsageStats(Document $project, callable $getProjectDB, callable $getLogsDB, string $hourlyUsageRetentionDatetime): void
2021-09-01 09:13:23 +00:00
{
2025-09-02 13:31:53 +00:00
/** @var Database $dbForProject */
2023-12-12 11:26:44 +00:00
$dbForProject = $getProjectDB($project);
2025-03-12 11:58:06 +00:00
2025-03-28 06:16:25 +00:00
$selects = [...$this->selects, 'time'];
2025-03-28 06:04:50 +00:00
2025-03-07 14:04:52 +00:00
// Delete Usage stats from projectDB
2024-03-05 09:36:23 +00:00
$this->deleteByGroup('stats', [
2025-03-28 06:16:25 +00:00
Query::select($selects),
2025-03-17 15:05:40 +00:00
Query::equal('period', ['1h']),
2023-12-12 11:26:44 +00:00
Query::lessThan('time', $hourlyUsageRetentionDatetime),
2025-03-14 02:29:52 +00:00
Query::orderDesc('time'),
2025-03-25 02:51:47 +00:00
Query::orderDesc(),
2023-12-12 11:26:44 +00:00
], $dbForProject);
2025-03-07 14:04:52 +00:00
if ($project->getId() !== 'console') {
2025-09-02 13:31:53 +00:00
/** @var Database $dbForLogs */
$dbForLogs = call_user_func($getLogsDB, $project);
// Delete Usage stats from logsDB
$this->deleteByGroup('stats', [
2025-03-28 06:16:25 +00:00
Query::select($selects),
2025-03-17 15:05:40 +00:00
Query::equal('period', ['1h']),
Query::lessThan('time', $hourlyUsageRetentionDatetime),
2025-03-14 02:29:52 +00:00
Query::orderDesc('time'),
2025-03-25 02:51:47 +00:00
Query::orderDesc(),
], $dbForLogs);
}
2021-08-27 11:37:52 +00:00
}
2021-07-13 19:24:52 +00:00
/**
2023-06-05 16:13:00 +00:00
* @param callable $getProjectDB
2021-07-13 19:24:52 +00:00
* @param Document $document teams document
2022-08-13 07:57:04 +00:00
* @param Document $project
2023-10-01 17:39:26 +00:00
* @return void
2023-06-05 16:13:00 +00:00
* @throws Exception
2021-07-13 19:24:52 +00:00
*/
2024-06-27 22:27:04 +00:00
public function deleteMemberships(callable $getProjectDB, Document $document, Document $project): void
2021-08-27 04:37:15 +00:00
{
2023-10-01 08:04:12 +00:00
$dbForProject = $getProjectDB($project);
2025-05-26 05:42:11 +00:00
$teamInternalId = $document->getSequence();
// Delete Memberships
$this->deleteByGroup(
'memberships',
[
2025-03-17 15:05:40 +00:00
Query::equal('teamInternalId', [$teamInternalId]),
Query::orderAsc()
],
$dbForProject,
function (Document $membership) use ($dbForProject) {
$userId = $membership->getAttribute('userId');
2023-12-14 13:32:06 +00:00
$dbForProject->purgeCachedDocument('users', $userId);
}
);
}
/**
2025-09-02 13:31:53 +00:00
* @param Database $dbForPlatform
* @param Document $document
* @return void
* @throws Authorization
* @throws DatabaseException
* @throws Conflict
* @throws Restricted
* @throws Structure
* @throws Exception
*/
2025-04-11 14:52:19 +00:00
protected function deleteProjectsByTeam(Database $dbForPlatform, callable $getProjectDB, CertificatesAdapter $certificates, Document $document): void
{
2023-10-25 07:39:59 +00:00
$projects = $dbForPlatform->find('projects', [
2025-05-26 05:42:11 +00:00
Query::equal('teamInternalId', [$document->getSequence()]),
2025-04-11 14:52:19 +00:00
Query::equal('region', [System::getEnv('_APP_REGION', 'default')])
]);
2024-05-29 19:52:22 +00:00
foreach ($projects as $project) {
2024-05-29 19:52:22 +00:00
$deviceForFiles = getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
2024-10-22 17:45:49 +00:00
$deviceForSites = getDevice(APP_STORAGE_SITES . '/app-' . $project->getId());
2024-05-29 19:52:22 +00:00
$deviceForFunctions = getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
$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);
$dbForPlatform->deleteDocument('projects', $project->getId());
}
}
2021-07-13 19:24:52 +00:00
/**
* @param Database $dbForPlatform
2023-10-01 08:04:12 +00:00
* @param callable $getProjectDB
2024-02-20 14:10:51 +00:00
* @param Device $deviceForFiles
* @param Device $deviceForFunctions
* @param Device $deviceForBuilds
* @param Device $deviceForCache
2023-10-01 08:04:12 +00:00
* @param Document $document
2023-10-01 17:39:26 +00:00
* @return void
* @throws Exception
2023-10-01 17:39:26 +00:00
* @throws Authorization
* @throws DatabaseException
2021-07-13 19:24:52 +00:00
*/
2025-06-18 11:46:55 +00:00
protected function deleteProject(Database $dbForPlatform, callable $getProjectDB, Device $deviceForFiles, Device $deviceForSites, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, Document $document): void
{
2025-05-26 05:42:11 +00:00
$projectInternalId = $document->getSequence();
2024-09-25 13:49:49 +00:00
$projectId = $document->getId();
2024-05-09 04:52:53 +00:00
try {
$dsn = new DSN($document->getAttribute('database', 'console'));
} catch (\InvalidArgumentException) {
// TODO: Temporary until all projects are using shared tables
$dsn = new DSN('mysql://' . $document->getAttribute('database', 'console'));
}
2023-06-11 10:29:04 +00:00
$dbForProject = $getProjectDB($document);
$projectCollectionIds = [
...\array_keys(Config::getParam('collections', [])['projects']),
Audit::COLLECTION,
AbuseDatabase::COLLECTION,
];
2024-09-26 07:38:36 +00:00
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
2024-11-21 03:49:49 +00:00
$sharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES_V1', ''));
$projectTables = !\in_array($dsn->getHost(), $sharedTables);
$sharedTablesV1 = \in_array($dsn->getHost(), $sharedTablesV1);
$sharedTablesV2 = !$projectTables && !$sharedTablesV1;
2025-05-06 07:50:56 +00:00
/**
2025-05-06 09:18:29 +00:00
* @var $dbForProject Database
2025-05-06 07:50:56 +00:00
*/
2025-05-06 09:18:29 +00:00
$dbForProject->foreach(Database::METADATA, function (Document $collection) use ($dbForProject, $projectTables, $projectCollectionIds) {
2025-05-06 07:50:56 +00:00
try {
if ($projectTables || !\in_array($collection->getId(), $projectCollectionIds)) {
$dbForProject->deleteCollection($collection->getId());
} else {
$this->deleteByGroup(
$collection->getId(),
[
Query::orderAsc()
],
database: $dbForProject
);
}
2025-05-06 07:50:56 +00:00
} catch (Throwable $e) {
2025-09-02 13:31:53 +00:00
Console::error('Error deleting ' . $collection->getId() . ' ' . $e->getMessage());
}
2025-05-06 09:18:29 +00:00
});
2023-04-27 15:37:14 +00:00
// Delete Platforms
$this->deleteByGroup('platforms', [
2025-03-17 15:05:40 +00:00
Query::equal('projectInternalId', [$projectInternalId]),
Query::orderAsc()
], $dbForPlatform);
2023-04-27 15:37:14 +00:00
// Delete project and function rules
$this->deleteByGroup('rules', [
2025-03-17 15:05:40 +00:00
Query::equal('projectInternalId', [$projectInternalId]),
Query::orderAsc()
], $dbForPlatform, function (Document $document) use ($dbForPlatform, $certificates) {
$this->deleteRule($dbForPlatform, $document, $certificates);
});
2023-04-27 15:37:14 +00:00
// Delete Keys
$this->deleteByGroup('keys', [
2025-03-17 15:05:40 +00:00
Query::equal('projectInternalId', [$projectInternalId]),
Query::orderAsc()
], $dbForPlatform);
// Delete Webhooks
$this->deleteByGroup('webhooks', [
2025-03-17 15:05:40 +00:00
Query::equal('projectInternalId', [$projectInternalId]),
Query::orderAsc()
], $dbForPlatform);
2023-04-27 15:37:14 +00:00
2023-11-03 09:24:51 +00:00
// Delete VCS Installations
$this->deleteByGroup('installations', [
2025-03-17 15:05:40 +00:00
Query::equal('projectInternalId', [$projectInternalId]),
Query::orderAsc()
], $dbForPlatform);
2023-11-03 09:24:51 +00:00
2023-11-08 21:59:29 +00:00
// Delete VCS Repositories
2023-11-03 09:24:51 +00:00
$this->deleteByGroup('repositories', [
2023-11-03 10:02:50 +00:00
Query::equal('projectInternalId', [$projectInternalId]),
2025-03-17 15:05:40 +00:00
Query::orderAsc()
], $dbForPlatform);
2023-11-08 21:59:29 +00:00
2024-05-09 04:52:53 +00:00
// Delete VCS comments
2023-11-08 21:59:29 +00:00
$this->deleteByGroup('vcsComments', [
Query::equal('projectInternalId', [$projectInternalId]),
2025-03-17 15:05:40 +00:00
Query::orderAsc()
], $dbForPlatform);
2025-03-25 02:51:47 +00:00
// Delete Schedules
2024-09-25 13:39:31 +00:00
$this->deleteByGroup('schedules', [
2024-09-25 13:49:49 +00:00
Query::equal('projectId', [$projectId]),
2025-03-17 15:05:40 +00:00
Query::orderAsc()
], $dbForPlatform);
2024-09-25 13:39:31 +00:00
2024-05-09 04:52:53 +00:00
// Delete metadata table
2024-11-21 03:49:49 +00:00
if ($projectTables) {
$dbForProject->deleteCollection(Database::METADATA);
2024-11-21 03:49:49 +00:00
} elseif ($sharedTablesV1) {
2025-03-17 15:05:40 +00:00
$this->deleteByGroup(
Database::METADATA,
[
Query::orderAsc()
],
$dbForProject
);
2024-11-21 03:49:49 +00:00
} elseif ($sharedTablesV2) {
$queries = \array_map(
fn ($id) => Query::notEqual('$id', $id),
$projectCollectionIds
);
2025-03-17 15:05:40 +00:00
$queries[] = Query::orderAsc();
$this->deleteByGroup(
Database::METADATA,
$queries,
$dbForProject
);
}
2021-07-13 18:44:45 +00:00
// Delete all storage directories
2024-02-20 14:10:51 +00:00
$deviceForFiles->delete($deviceForFiles->getRoot(), true);
2024-10-22 17:45:49 +00:00
$deviceForSites->delete($deviceForSites->getRoot(), true);
2024-02-20 14:10:51 +00:00
$deviceForFunctions->delete($deviceForFunctions->getRoot(), true);
$deviceForBuilds->delete($deviceForBuilds->getRoot(), true);
$deviceForCache->delete($deviceForCache->getRoot(), true);
}
2021-07-13 19:24:52 +00:00
/**
2023-06-05 16:13:00 +00:00
* @param callable $getProjectDB
2021-07-13 19:24:52 +00:00
* @param Document $document user document
2022-08-13 07:57:04 +00:00
* @param Document $project
2023-10-01 17:39:26 +00:00
* @return void
2023-06-05 16:13:00 +00:00
* @throws Exception
2021-07-13 19:24:52 +00:00
*/
2023-10-17 18:32:38 +00:00
private function deleteUser(callable $getProjectDB, Document $document, Document $project): void
{
2021-07-12 21:57:37 +00:00
$userId = $document->getId();
2025-05-26 05:42:11 +00:00
$userInternalId = $document->getSequence();
2023-06-05 16:13:00 +00:00
$dbForProject = $getProjectDB($project);
2022-11-16 19:39:35 +00:00
// Delete all sessions of this user from the sessions table and update the sessions field of the user record
$this->deleteByGroup('sessions', [
2025-03-17 15:05:40 +00:00
Query::equal('userInternalId', [$userInternalId]),
Query::orderAsc()
2022-11-16 19:39:35 +00:00
], $dbForProject);
2023-12-14 13:32:06 +00:00
$dbForProject->purgeCachedDocument('users', $userId);
// Delete Memberships and decrement team membership counts
2021-07-13 19:24:52 +00:00
$this->deleteByGroup('memberships', [
2025-03-17 15:05:40 +00:00
Query::equal('userInternalId', [$userInternalId]),
Query::orderAsc()
2022-11-16 19:39:35 +00:00
], $dbForProject, function (Document $document) use ($dbForProject) {
if ($document->getAttribute('confirm')) { // Count only confirmed members
$teamId = $document->getAttribute('teamId');
2022-11-16 19:39:35 +00:00
$team = $dbForProject->getDocument('teams', $teamId);
2021-09-01 09:13:23 +00:00
if (!$team->isEmpty()) {
2024-02-12 01:18:19 +00:00
$dbForProject->decreaseDocumentAttribute('teams', $teamId, 'total', 1, 0);
}
}
});
2022-04-27 11:06:53 +00:00
// Delete tokens
$this->deleteByGroup('tokens', [
2025-03-17 15:05:40 +00:00
Query::equal('userInternalId', [$userInternalId]),
Query::orderAsc()
2022-11-16 19:39:35 +00:00
], $dbForProject);
// Delete identities
2025-04-25 14:25:26 +00:00
Identities::delete($dbForProject, Query::equal('userInternalId', [$userInternalId]));
// Delete targets
2025-04-25 14:25:26 +00:00
Targets::delete($dbForProject, Query::equal('userInternalId', [$userInternalId]));
}
2021-07-13 19:24:52 +00:00
/**
* @param database $dbForPlatform
2023-06-05 16:13:00 +00:00
* @param callable $getProjectDB
2022-07-11 15:12:41 +00:00
* @param string $datetime
2023-10-01 17:39:26 +00:00
* @return void
* @throws Exception
2021-07-13 19:24:52 +00:00
*/
2023-12-20 11:26:57 +00:00
private function deleteExecutionLogs(Document $project, callable $getProjectDB, string $datetime): void
2020-12-14 21:26:37 +00:00
{
2023-12-12 11:26:44 +00:00
$dbForProject = $getProjectDB($project);
2025-03-14 02:29:52 +00:00
2023-12-12 11:26:44 +00:00
// Delete Executions
$this->deleteByGroup('executions', [
2025-03-28 06:16:25 +00:00
Query::select([...$this->selects, '$createdAt']),
2025-03-14 02:29:52 +00:00
Query::lessThan('$createdAt', $datetime),
Query::orderDesc('$createdAt'),
2025-03-17 15:05:40 +00:00
Query::orderDesc(),
2023-12-12 11:26:44 +00:00
], $dbForProject);
2020-12-14 21:26:37 +00:00
}
2023-06-05 16:13:00 +00:00
/**
* @param Database $dbForPlatform
2023-06-05 16:13:00 +00:00
* @param callable $getProjectDB
* @return void
2023-10-01 08:04:12 +00:00
* @throws Exception|Throwable
2023-06-05 16:13:00 +00:00
*/
2023-12-12 11:26:44 +00:00
private function deleteExpiredSessions(Document $project, callable $getProjectDB): void
{
2023-12-12 11:26:44 +00:00
$dbForProject = $getProjectDB($project);
$duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
2023-12-12 11:26:44 +00:00
$expired = DateTime::addSeconds(new \DateTime(), -1 * $duration);
2022-12-14 08:44:40 +00:00
2023-12-12 11:26:44 +00:00
// Delete Sessions
$this->deleteByGroup('sessions', [
2025-03-28 06:16:25 +00:00
Query::select([...$this->selects, '$createdAt']),
2025-03-14 02:29:52 +00:00
Query::lessThan('$createdAt', $expired),
Query::orderDesc('$createdAt'),
2025-03-17 15:05:40 +00:00
Query::orderDesc(),
2023-12-12 11:26:44 +00:00
], $dbForProject);
}
/**
* @param Database $dbForPlatform
2022-07-11 15:12:41 +00:00
* @param string $datetime
2023-10-01 17:39:26 +00:00
* @return void
* @throws Exception
*/
private function deleteRealtimeUsage(Database $dbForPlatform, string $datetime): void
{
2023-10-17 18:32:38 +00:00
// Delete Dead Realtime Logs
$this->deleteByGroup('realtime', [
2025-03-14 02:29:52 +00:00
Query::lessThan('timestamp', $datetime),
Query::orderDesc('timestamp'),
2025-03-25 02:51:47 +00:00
Query::orderAsc(),
], $dbForPlatform);
}
2021-07-13 19:24:52 +00:00
/**
* @param Database $dbForPlatform
2023-06-05 16:13:00 +00:00
* @param callable $getProjectDB
* @param string $auditRetention
2023-10-01 17:39:26 +00:00
* @return void
2022-07-11 15:12:41 +00:00
* @throws Exception
2021-07-13 19:24:52 +00:00
*/
2023-12-12 11:26:44 +00:00
private function deleteAuditLogs(Document $project, callable $getProjectDB, string $auditRetention): void
{
2023-12-12 11:26:44 +00:00
$projectId = $project->getId();
$dbForProject = $getProjectDB($project);
2024-05-17 04:38:40 +00:00
try {
2025-03-12 03:53:08 +00:00
$this->deleteByGroup(Audit::COLLECTION, [
2025-03-28 06:16:25 +00:00
Query::select([...$this->selects, 'time']),
2025-03-12 03:53:08 +00:00
Query::lessThan('time', $auditRetention),
Query::orderDesc('time'),
2025-03-25 02:51:47 +00:00
Query::orderAsc(),
2025-03-12 03:53:08 +00:00
], $dbForProject);
2024-05-17 04:38:40 +00:00
} catch (DatabaseException $e) {
Console::error('Failed to delete audit logs for project ' . $projectId . ': ' . $e->getMessage());
2020-12-18 14:05:15 +00:00
}
}
/**
* @param callable $getProjectDB
* @param Device $deviceForSites
* @param Device $deviceForBuilds
* @param Document $document function document
* @param Document $project
* @return void
* @throws Exception
*/
2025-02-25 15:42:25 +00:00
private function deleteSite(Database $dbForPlatform, callable $getProjectDB, Device $deviceForSites, Device $deviceForBuilds, Device $deviceForFiles, Document $document, CertificatesAdapter $certificates, Document $project): void
{
$dbForProject = $getProjectDB($project);
$siteId = $document->getId();
$siteInternalId = $document->getSequence();
/**
* Delete rules for site
*/
Console::info("Deleting rules for site " . $siteId);
$this->deleteByGroup('rules', [
2025-02-23 20:34:14 +00:00
Query::equal('type', ['deployment']),
Query::equal('deploymentResourceType', ['site']),
Query::equal('deploymentResourceInternalId', [$siteInternalId]),
Query::equal('projectInternalId', [$project->getSequence()])
], $dbForPlatform, function (Document $document) use ($dbForPlatform, $certificates) {
$this->deleteRule($dbForPlatform, $document, $certificates);
});
/**
* Delete Variables
*/
Console::info("Deleting variables for site " . $siteId);
$this->deleteByGroup('variables', [
Query::equal('resourceType', ['site']),
Query::equal('resourceInternalId', [$siteInternalId])
], $dbForProject);
/**
* Delete Deployments
*/
Console::info("Deleting deployments for site " . $siteId);
$deploymentInternalIds = [];
2025-02-23 20:34:14 +00:00
$deploymentIds = [];
$this->deleteByGroup('deployments', [
Query::equal('resourceInternalId', [$siteInternalId]),
Query::equal('resourceType', ['site']),
Query::orderAsc()
], $dbForProject, function (Document $document) use ($project, $certificates, $deviceForSites, $deviceForBuilds, $deviceForFiles, $dbForPlatform, &$deploymentInternalIds) {
$deploymentInternalIds[] = $document->getSequence();
2025-02-23 20:34:14 +00:00
$deploymentIds[] = $document->getId();
$this->deleteBuildFiles($deviceForBuilds, $document);
2025-02-25 15:42:25 +00:00
$this->deleteDeploymentFiles($deviceForSites, $document);
2025-02-17 12:03:56 +00:00
$this->deleteDeploymentScreenshots($deviceForFiles, $dbForPlatform, $document);
});
/**
2025-04-17 10:14:58 +00:00
* Delete Logs
*/
2025-04-17 10:14:58 +00:00
Console::info("Deleting logs for site " . $siteId);
$this->deleteByGroup('executions', [
Query::select($this->selects),
Query::equal('resourceInternalId', [$siteInternalId]),
Query::equal('resourceType', ['sites']),
Query::orderAsc()
], $dbForProject);
/**
* Delete VCS Repositories and VCS Comments
*/
Console::info("Deleting VCS repositories and comments linked to site " . $siteId);
$this->deleteByGroup('repositories', [
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('resourceInternalId', [$siteInternalId]),
Query::equal('resourceType', ['site']),
], $dbForPlatform, function (Document $document) use ($dbForPlatform) {
$providerRepositoryId = $document->getAttribute('providerRepositoryId', '');
$projectInternalId = $document->getAttribute('projectInternalId', '');
$this->deleteByGroup('vcsComments', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::equal('projectInternalId', [$projectInternalId]),
], $dbForPlatform);
});
}
2021-07-13 19:24:52 +00:00
/**
2023-06-05 16:13:00 +00:00
* @param callable $getProjectDB
2024-02-20 14:10:51 +00:00
* @param Device $deviceForFunctions
* @param Device $deviceForBuilds
2021-07-13 19:24:52 +00:00
* @param Document $document function document
2022-08-13 07:57:04 +00:00
* @param Document $project
* @param Executor $executor
2023-10-01 17:39:26 +00:00
* @return void
2023-06-05 16:13:00 +00:00
* @throws Exception
2021-07-13 19:24:52 +00:00
*/
private function deleteFunction(Database $dbForPlatform, callable $getProjectDB, Device $deviceForFunctions, Device $deviceForBuilds, CertificatesAdapter $certificates, Document $document, Document $project, Executor $executor): void
{
2022-08-13 07:57:04 +00:00
$projectId = $project->getId();
2023-06-05 16:13:00 +00:00
$dbForProject = $getProjectDB($project);
2022-02-12 22:34:16 +00:00
$functionId = $document->getId();
2025-05-26 05:42:11 +00:00
$functionInternalId = $document->getSequence();
2022-01-28 00:25:28 +00:00
2023-02-22 15:07:34 +00:00
/**
2023-07-31 06:47:47 +00:00
* Delete rules
2023-02-22 15:07:34 +00:00
*/
2023-07-31 06:47:47 +00:00
Console::info("Deleting rules for function " . $functionId);
2023-03-08 18:30:01 +00:00
$this->deleteByGroup('rules', [
2025-02-23 20:34:14 +00:00
Query::equal('type', ['deployment']),
2025-04-02 14:47:37 +00:00
Query::equal('deploymentResourceType', ['function']),
Query::equal('deploymentResourceInternalId', [$functionInternalId]),
2025-05-26 05:42:11 +00:00
Query::equal('projectInternalId', [$project->getSequence()]),
2025-03-17 15:05:40 +00:00
Query::orderAsc()
], $dbForPlatform, function (Document $document) use ($project, $dbForPlatform, $certificates) {
$this->deleteRule($dbForPlatform, $document, $certificates);
2023-03-10 12:20:24 +00:00
});
2023-02-22 15:07:34 +00:00
2022-09-19 11:53:34 +00:00
/**
* Delete Variables
*/
Console::info("Deleting variables for function " . $functionId);
$this->deleteByGroup('variables', [
2025-03-17 15:05:40 +00:00
Query::equal('resourceInternalId', [$functionInternalId]),
2023-03-11 16:06:02 +00:00
Query::equal('resourceType', ['function']),
2025-03-17 15:05:40 +00:00
Query::orderAsc()
2022-09-19 11:53:34 +00:00
], $dbForProject);
/**
* Delete Deployments
*/
2022-02-12 22:34:16 +00:00
Console::info("Deleting deployments for function " . $functionId);
2024-02-20 11:40:55 +00:00
2023-06-23 11:52:19 +00:00
$deploymentInternalIds = [];
$this->deleteByGroup('deployments', [
2025-03-17 15:05:40 +00:00
Query::equal('resourceInternalId', [$functionInternalId]),
Query::equal('resourceType', ['function']),
2025-03-17 15:05:40 +00:00
Query::orderAsc()
], $dbForProject, function (Document $document) use ($dbForPlatform, $project, $certificates, $deviceForFunctions, $deviceForBuilds, &$deploymentInternalIds) {
2025-05-26 05:42:11 +00:00
$deploymentInternalIds[] = $document->getSequence();
2024-02-20 14:10:51 +00:00
$this->deleteDeploymentFiles($deviceForFunctions, $document);
$this->deleteBuildFiles($deviceForBuilds, $document);
});
2022-05-23 14:54:50 +00:00
/**
2022-02-12 22:34:16 +00:00
* Delete Executions
2022-05-16 09:58:17 +00:00
*/
2022-02-12 22:34:16 +00:00
Console::info("Deleting executions for function " . $functionId);
$this->deleteByGroup('executions', [
2025-03-26 07:20:44 +00:00
Query::select($this->selects),
2025-04-17 10:14:58 +00:00
Query::equal('resourceInternalId', [$functionInternalId]),
Query::equal('resourceType', ['functions']),
2025-03-17 15:05:40 +00:00
Query::orderAsc()
], $dbForProject);
/**
* Delete VCS Repositories and VCS Comments
*/
Console::info("Deleting VCS repositories and comments linked to function " . $functionId);
$this->deleteByGroup('repositories', [
2025-05-26 05:42:11 +00:00
Query::equal('projectInternalId', [$project->getSequence()]),
Query::equal('resourceInternalId', [$functionInternalId]),
Query::equal('resourceType', ['function']),
2025-03-17 15:05:40 +00:00
Query::orderAsc()
], $dbForPlatform, function (Document $document) use ($dbForPlatform) {
$providerRepositoryId = $document->getAttribute('providerRepositoryId', '');
2023-11-14 13:11:54 +00:00
$projectInternalId = $document->getAttribute('projectInternalId', '');
2025-03-17 15:05:40 +00:00
$this->deleteByGroup('vcsComments', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
2023-11-14 13:11:54 +00:00
Query::equal('projectInternalId', [$projectInternalId]),
2025-03-17 15:05:40 +00:00
Query::orderAsc()
], $dbForPlatform);
});
2023-03-14 11:13:03 +00:00
/**
* Request executor to delete all deployment containers
*/
Console::info("Requesting executor to delete all deployment containers for function " . $functionId);
$this->deleteRuntimes($getProjectDB, $document, $project, $executor);
2025-02-23 20:34:14 +00:00
}
2025-02-17 12:03:56 +00:00
private function deleteDeploymentScreenshots(Device $deviceForFiles, Database $dbForPlatform, Document $deployment): void
{
$screenshotIds = [];
2025-02-21 09:09:35 +00:00
if (!empty($deployment->getAttribute('screenshotLight', ''))) {
$screenshotIds[] = $deployment->getAttribute('screenshotLight', '');
2025-02-17 12:03:56 +00:00
}
if (!empty($deployment->getAttribute('screenshotDark', ''))) {
$screenshotIds[] = $deployment->getAttribute('screenshotDark', '');
}
if (empty($screenshotIds)) {
return;
}
Console::info("Deleting screenshots for deployment " . $deployment->getId());
$bucket = ValidatorAuthorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots'));
if ($bucket->isEmpty()) {
Console::error('Failed to get bucket for deployment screenshots');
return;
}
foreach ($screenshotIds as $id) {
$file = ValidatorAuthorization::skip(fn () => $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $id));
2025-02-17 12:03:56 +00:00
if ($file->isEmpty()) {
Console::error('Failed to get deployment screenshot: ' . $id);
continue;
}
$path = $file->getAttribute('path', '');
try {
if ($deviceForFiles->delete($path, true)) {
Console::success('Deleted deployment screenshot: ' . $path);
} else {
Console::error('Failed to delete deployment screenshot: ' . $path);
}
} catch (\Throwable $th) {
Console::error('Failed to delete deployment screenshot: ' . $path);
Console::error('[Error] Type: ' . get_class($th));
Console::error('[Error] Message: ' . $th->getMessage());
Console::error('[Error] File: ' . $th->getFile());
Console::error('[Error] Line: ' . $th->getLine());
}
}
}
2023-10-17 18:32:38 +00:00
/**
* @param Device $device
* @param Document $deployment
* @return void
*/
private function deleteDeploymentFiles(Device $device, Document $deployment): void
{
$deploymentId = $deployment->getId();
2025-03-07 20:04:24 +00:00
$deploymentPath = $deployment->getAttribute('sourcePath', '');
if (empty($deploymentPath)) {
Console::info("No deployment files for deployment " . $deploymentId);
return;
}
Console::info("Deleting deployment files for deployment " . $deploymentId);
try {
if ($device->delete($deploymentPath, true)) {
Console::success('Deleted deployment files: ' . $deploymentPath);
} else {
Console::error('Failed to delete deployment files: ' . $deploymentPath);
}
2025-03-07 14:04:52 +00:00
} catch (Throwable $th) {
Console::error('Failed to delete deployment files: ' . $deploymentPath);
Console::error('[Error] Type: ' . get_class($th));
Console::error('[Error] Message: ' . $th->getMessage());
Console::error('[Error] File: ' . $th->getFile());
Console::error('[Error] Line: ' . $th->getLine());
}
}
2023-10-17 18:32:38 +00:00
/**
* @param Device $device
* @param Document $build
* @return void
*/
private function deleteBuildFiles(Device $device, Document $deployment): void
{
$deploymentId = $deployment->getId();
$buildPath = $deployment->getAttribute('buildPath', '');
if (empty($buildPath)) {
Console::info("No build files for deployment " . $deploymentId);
return;
}
try {
if ($device->delete($buildPath, true)) {
Console::success('Deleted build files: ' . $buildPath);
} else {
Console::error('Failed to delete build files: ' . $buildPath);
}
2025-03-07 14:04:52 +00:00
} catch (Throwable $th) {
Console::error('Failed to delete deployment files: ' . $buildPath);
Console::error('[Error] Type: ' . get_class($th));
Console::error('[Error] Message: ' . $th->getMessage());
Console::error('[Error] File: ' . $th->getFile());
Console::error('[Error] Line: ' . $th->getLine());
}
}
/**
2023-06-05 16:13:00 +00:00
* @param callable $getProjectDB
2024-02-20 14:10:51 +00:00
* @param Device $deviceForFunctions
2025-02-25 15:42:25 +00:00
* @param Device $deviceForSites
2024-02-20 14:10:51 +00:00
* @param Device $deviceForBuilds
2023-10-01 08:04:12 +00:00
* @param Document $document
2022-08-13 07:57:04 +00:00
* @param Document $project
* @param Executor $executor
2023-10-01 17:39:26 +00:00
* @return void
2023-06-05 16:13:00 +00:00
* @throws Exception
*/
2025-04-08 08:41:39 +00:00
private function deleteDeployment(Database $dbForPlatform, callable $getProjectDB, Device $deviceForFunctions, Device $deviceForSites, Device $deviceForBuilds, Device $deviceForFiles, Document $document, CertificatesAdapter $certificates, Document $project, Executor $executor): void
{
2022-08-13 07:57:04 +00:00
$projectId = $project->getId();
2023-06-05 16:13:00 +00:00
$dbForProject = $getProjectDB($project);
$deploymentId = $document->getId();
2025-05-26 05:42:11 +00:00
$deploymentInternalId = $document->getSequence();
/**
2022-01-29 00:00:25 +00:00
* Delete deployment files
*/
2025-02-25 15:42:25 +00:00
match ($document->getAttribute('resourceType')) {
'functions' => $this->deleteDeploymentFiles($deviceForFunctions, $document),
'sites' => $this->deleteDeploymentFiles($deviceForSites, $document),
default => throw new Exception('Invalid resource type')
};
2025-02-20 15:49:20 +00:00
/**
* Delete deployment screenshots
*/
$this->deleteDeploymentScreenshots($deviceForFiles, $dbForPlatform, $document);
/**
* Delete deployment build
*/
$this->deleteBuildFiles($deviceForBuilds, $document);
/**
* Delete rules associated with the deployment
*/
Console::info("Deleting rules for deployment " . $deploymentId);
$this->deleteByGroup('rules', [
2025-04-02 14:47:37 +00:00
Query::equal('trigger', ['deployment']),
2025-02-23 20:34:14 +00:00
Query::equal('type', ['deployment']),
2025-03-17 15:05:40 +00:00
Query::equal('deploymentInternalId', [$deploymentInternalId]),
Query::equal('projectInternalId', [$project->getSequence()])
], $dbForPlatform, function (Document $document) use ($dbForPlatform, $certificates) {
$this->deleteRule($dbForPlatform, $document, $certificates);
});
2023-03-14 11:13:03 +00:00
/**
* Request executor to delete all deployment containers
*/
Console::info("Requesting executor to delete deployment container for deployment " . $deploymentId);
$this->deleteRuntimes($getProjectDB, $document, $project, $executor);
}
/**
* @param string $collection collectionID
* @param array $queries
* @param Database $database
2025-02-17 09:36:55 +00:00
* @param ?callable $callback
2023-10-01 17:39:26 +00:00
* @return void
2025-03-12 03:53:08 +00:00
* @throws DatabaseException
*/
2025-02-17 09:36:55 +00:00
protected function deleteByGroup(
string $collection,
array $queries,
Database $database,
?callable $callback = null
2025-02-17 10:14:17 +00:00
): void {
2025-02-17 09:36:55 +00:00
$start = \microtime(true);
2025-03-17 15:05:40 +00:00
/**
* deleteDocuments uses a cursor, we need to add a unique order by field or use default
*/
2025-02-17 09:36:55 +00:00
try {
2025-04-14 07:05:42 +00:00
$count = $database->deleteDocuments(
2025-04-14 06:50:33 +00:00
$collection,
$queries,
2025-09-02 13:31:53 +00:00
onNext: $callback
2025-04-14 06:50:33 +00:00
);
2025-03-07 14:04:52 +00:00
} catch (Throwable $th) {
2025-09-02 13:31:53 +00:00
$tenant = $database->getSharedTables() ? 'Tenant:' . $database->getTenant() : '';
2025-04-03 15:31:50 +00:00
Console::error("Failed to delete documents for collection:{$database->getNamespace()}_{$collection} {$tenant} :{$th->getMessage()}");
2025-02-17 09:36:55 +00:00
return;
}
2025-02-17 09:36:55 +00:00
$end = \microtime(true);
Console::info("Deleted {$count} documents by group in " . ($end - $start) . " seconds");
}
2021-07-12 21:57:37 +00:00
/**
* @param string $collection collectionID
* @param Query[] $queries
2021-07-13 19:24:52 +00:00
* @param Database $database
2023-06-05 16:13:00 +00:00
* @param callable|null $callback
2023-10-01 17:39:26 +00:00
* @return void
2023-06-05 16:13:00 +00:00
* @throws Exception
2021-07-12 21:57:37 +00:00
*/
2024-08-12 14:44:05 +00:00
protected function listByGroup(string $collection, array $queries, Database $database, callable $callback = null): void
{
$count = 0;
2025-02-17 09:36:55 +00:00
$limit = 1000;
$sum = $limit;
$cursor = null;
2025-02-17 09:36:55 +00:00
$start = \microtime(true);
2021-09-01 09:13:23 +00:00
while ($sum === $limit) {
2025-02-17 09:36:55 +00:00
$queries = \array_merge([Query::limit($limit)], $queries);
if ($cursor !== null) {
$queries[] = Query::cursorAfter($cursor);
}
2025-02-17 09:36:55 +00:00
$results = $database->find($collection, $queries);
2022-01-29 00:52:06 +00:00
2025-02-17 09:36:55 +00:00
$sum = \count($results);
if ($sum > 0) {
$cursor = $results[$sum - 1];
}
foreach ($results as $document) {
2022-12-14 08:44:40 +00:00
if (is_callable($callback)) {
$callback($document);
}
$count++;
}
}
2025-02-17 09:36:55 +00:00
$end = \microtime(true);
2025-02-17 09:36:55 +00:00
Console::info("Listed {$count} documents by group in " . ($end - $start) . " seconds");
}
2021-07-13 19:24:52 +00:00
/**
* @param Database $dbForPlatform
2023-03-10 07:42:52 +00:00
* @param Document $document rule document
2023-10-01 17:39:26 +00:00
* @return void
2021-07-13 19:24:52 +00:00
*/
2025-09-08 12:56:02 +00:00
protected function deleteRule(Database $dbForPlatform, Document $document, CertificatesAdapter $certificates): void
2021-02-05 09:05:26 +00:00
{
2021-02-05 10:57:43 +00:00
$domain = $document->getAttribute('domain');
2024-11-20 10:10:57 +00:00
$certificates->deleteCertificate($domain);
2023-03-10 07:42:52 +00:00
// Delete certificate document, so Appwrite is aware of change
if (isset($document['certificateId'])) {
$dbForPlatform->deleteDocument('certificates', $document['certificateId']);
2023-03-10 07:42:52 +00:00
}
2021-02-05 09:05:26 +00:00
}
2021-07-18 07:03:48 +00:00
2023-06-05 16:13:00 +00:00
/**
* @param callable $getProjectDB
2024-02-20 14:10:51 +00:00
* @param Device $deviceForFiles
2023-06-05 16:13:00 +00:00
* @param Document $document
* @param Document $project
* @return void
*/
2024-02-20 14:10:51 +00:00
private function deleteBucket(callable $getProjectDB, Device $deviceForFiles, Document $document, Document $project): void
2021-07-18 07:03:48 +00:00
{
2023-06-05 16:13:00 +00:00
$dbForProject = $getProjectDB($project);
2025-05-26 05:42:11 +00:00
$dbForProject->deleteCollection('bucket_' . $document->getSequence());
2021-07-28 10:14:47 +00:00
2024-02-20 14:10:51 +00:00
$deviceForFiles->deletePath($document->getId());
2021-07-18 07:03:48 +00:00
}
2023-03-14 11:13:03 +00:00
2023-10-01 08:04:12 +00:00
/**
* @param Database $dbForPlatform
2023-10-01 08:04:12 +00:00
* @param callable $getProjectDB
* @param Document $document
* @param Document $project
* @return void
2023-10-01 17:39:26 +00:00
* @throws Exception
2023-10-01 08:04:12 +00:00
*/
private function deleteInstallation(Database $dbForPlatform, callable $getProjectDB, Document $document, Document $project): void
{
2023-10-01 08:04:12 +00:00
$dbForProject = $getProjectDB($project);
$this->listByGroup('functions', [
2025-05-26 05:42:11 +00:00
Query::equal('installationInternalId', [$document->getSequence()])
], $dbForProject, function ($function) use ($dbForProject, $dbForPlatform) {
$dbForPlatform->deleteDocument('repositories', $function->getAttribute('repositoryId'));
$function = $function
->setAttribute('installationId', '')
->setAttribute('installationInternalId', '')
->setAttribute('providerRepositoryId', '')
->setAttribute('providerBranch', '')
->setAttribute('providerSilentMode', false)
->setAttribute('providerRootDirectory', '')
->setAttribute('repositoryId', '')
->setAttribute('repositoryInternalId', '');
$dbForProject->updateDocument('functions', $function->getId(), $function);
});
}
2023-10-01 08:04:12 +00:00
/**
* @param callable $getProjectDB
* @param ?Document $function
* @param Document $project
* @param Executor $executor
2023-10-01 17:39:26 +00:00
* @return void
2023-10-01 08:04:12 +00:00
* @throws Exception
*/
private function deleteRuntimes(callable $getProjectDB, ?Document $function, Document $project, Executor $executor): void
2023-03-14 19:31:23 +00:00
{
2023-10-01 08:04:12 +00:00
$deleteByFunction = function (Document $function) use ($getProjectDB, $project, $executor) {
2023-03-14 11:13:03 +00:00
$this->listByGroup(
'deployments',
[
2025-05-26 05:42:11 +00:00
Query::equal('resourceInternalId', [$function->getSequence()]),
2023-03-14 11:13:03 +00:00
Query::equal('resourceType', ['functions']),
],
2023-10-01 08:04:12 +00:00
$getProjectDB($project),
2023-03-14 11:13:03 +00:00
function (Document $deployment) use ($project, $executor) {
$deploymentId = $deployment->getId();
try {
$executor->deleteRuntime($project->getId(), $deploymentId);
Console::info("Runtime for deployment {$deploymentId} deleted.");
} catch (Throwable $th) {
Console::warning("Runtime for deployment {$deploymentId} skipped:");
Console::error('[Error] Type: ' . get_class($th));
Console::error('[Error] Message: ' . $th->getMessage());
Console::error('[Error] File: ' . $th->getFile());
Console::error('[Error] Line: ' . $th->getLine());
}
}
);
};
2023-03-14 19:31:23 +00:00
if ($function !== null) {
2023-03-14 11:13:03 +00:00
// Delete function runtimes
$deleteByFunction($function);
} else {
// Delete all project runtimes
$this->listByGroup(
'functions',
[],
2023-10-01 08:04:12 +00:00
$getProjectDB($project),
2023-03-14 11:13:03 +00:00
function (Document $function) use ($deleteByFunction) {
$deleteByFunction($function);
}
);
}
}
2025-08-14 12:32:03 +00:00
private function deleteTransactionLogs(callable $getProjectDB, Document $document, Document $project): void
{
$dbForProject = $getProjectDB($project);
$transactionId = $document->getId();
$transactionInternalId = $document->getSequence();
try {
$dbForProject->deleteDocuments('transactionLogs', [
Query::equal('transactionInternalId', [$transactionInternalId]),
]);
2025-09-11 13:16:23 +00:00
Console::info("Transaction logs for transaction {$transactionId} deleted.");
2025-08-14 12:32:03 +00:00
} catch (Throwable $th) {
2025-09-11 13:16:23 +00:00
Console::error("Failed to delete transaction logs for transaction {$transactionId}: " . $th->getMessage());
2025-08-14 12:32:03 +00:00
}
}
2025-09-02 13:31:53 +00:00
private function deleteExpiredTransactions(Document $project, callable $getProjectDB): void
{
$dbForProject = $getProjectDB($project);
$transactionInternalIds = [];
try {
$dbForProject->deleteDocuments('transactions', [
Query::lessThan('expiresAt', DateTime::format(new \DateTime())),
], onNext: function (Document $transaction) use ($dbForProject, $project, &$transactionInternalIds) {
$transactionInternalIds[] = $transaction->getSequence();
}, onError: function (Throwable $th) use ($project) {
// Swallow errors to avoid breaking the cleanup process
});
} catch (Throwable $th) {
Console::error("Failed to find expired transactions for project {$project->getId()}: " . $th->getMessage());
}
if (empty($transactionInternalIds)) {
return;
}
$dbForProject->deleteDocuments('transactionLogs', [
Query::equal('transactionInternalId', $transactionInternalIds),
], onError: function (Throwable $th) use ($project) {
// Swallow errors to avoid breaking the cleanup process
});
}
2021-09-01 09:13:23 +00:00
}