From 9601860247f214b985185a8646b643cda21213a4 Mon Sep 17 00:00:00 2001 From: Fabian Gruber Date: Fri, 25 Apr 2025 15:52:19 +0200 Subject: [PATCH 1/2] feat(users): synchronously delete identities and targets in controller --- app/controllers/api/users.php | 2 + src/Appwrite/Deletes/Identities.php | 22 ++++++ src/Appwrite/Deletes/Targets.php | 56 +++++++++++++++ src/Appwrite/Platform/Workers/Deletes.php | 84 ++--------------------- 4 files changed, 85 insertions(+), 79 deletions(-) create mode 100644 src/Appwrite/Deletes/Identities.php create mode 100644 src/Appwrite/Deletes/Targets.php diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index a212b23448..13ab26ac15 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -2275,6 +2275,8 @@ App::delete('/v1/users/:userId') $clone = clone $user; $dbForProject->deleteDocument('users', $userId); + \Appwrite\Deletes\Identities::delete($dbForProject, Query::equal('userInternalId', [$user->getInternalId()])); + \Appwrite\Deletes\Targets::delete($dbForProject, Query::equal('userInternalId', [$user->getInternalId()])); $queueForDeletes ->setType(DELETE_TYPE_DOCUMENT) diff --git a/src/Appwrite/Deletes/Identities.php b/src/Appwrite/Deletes/Identities.php new file mode 100644 index 0000000000..09dac185a1 --- /dev/null +++ b/src/Appwrite/Deletes/Identities.php @@ -0,0 +1,22 @@ +deleteDocuments( + 'identities', + [ + $query, + Query::orderAsc() + ], + Database::DELETE_BATCH_SIZE + ); + } + +} diff --git a/src/Appwrite/Deletes/Targets.php b/src/Appwrite/Deletes/Targets.php new file mode 100644 index 0000000000..95e744ddf1 --- /dev/null +++ b/src/Appwrite/Deletes/Targets.php @@ -0,0 +1,56 @@ +deleteDocuments( + 'targets', + [ + $query, + Query::orderAsc() + ], + Database::DELETE_BATCH_SIZE, + fn (Document $target) => self::deleteSubscribers($database, $target) + ); + } + + public static function deleteSubscribers(Database $database, Document $target): void + { + $database->deleteDocuments( + 'subscribers', + [ + Query::equal('targetInternalId', [$target->getInternalId()]), + Query::orderAsc(), + ], + Database::DELETE_BATCH_SIZE, + function (Document $subscriber) use ($database, $target) { + $topicId = $subscriber->getAttribute('topicId'); + $topicInternalId = $subscriber->getAttribute('topicInternalId'); + $topic = $database->getDocument('topics', $topicId); + if (!$topic->isEmpty() && $topic->getInternalId() === $topicInternalId) { + $totalAttribute = match ($target->getAttribute('providerType')) { + MESSAGE_TYPE_EMAIL => 'emailTotal', + MESSAGE_TYPE_SMS => 'smsTotal', + MESSAGE_TYPE_PUSH => 'pushTotal', + default => throw new Exception('Invalid target provider type'), + }; + $database->decreaseDocumentAttribute( + 'topics', + $topicId, + $totalAttribute, + min: 0 + ); + } + } + ); + } + +} diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 4a2cb60d32..0f2bf19a54 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -149,7 +149,7 @@ class Deletes extends Action $this->deleteTopic($project, $getProjectDB, $document); break; case DELETE_TYPE_TARGET: - $this->deleteTargetSubscribers($project, $getProjectDB, $document); + \Appwrite\Deletes\Targets::deleteSubscribers($getProjectDB($project), $document); break; case DELETE_TYPE_EXPIRED_TARGETS: $this->deleteExpiredTargets($project, $getProjectDB); @@ -265,47 +265,6 @@ class Deletes extends Action ); } - /** - * @param Document $project - * @param callable $getProjectDB - * @param Document $target - * @throws Exception - */ - private function deleteTargetSubscribers(Document $project, callable $getProjectDB, Document $target): void - { - /** @var Database */ - $dbForProject = $getProjectDB($project); - - // Delete subscribers and decrement topic counts - $this->deleteByGroup( - 'subscribers', - [ - Query::equal('targetInternalId', [$target->getInternalId()]), - Query::orderAsc(), - ], - $dbForProject, - function (Document $subscriber) use ($dbForProject, $target) { - $topicId = $subscriber->getAttribute('topicId'); - $topicInternalId = $subscriber->getAttribute('topicInternalId'); - $topic = $dbForProject->getDocument('topics', $topicId); - if (!$topic->isEmpty() && $topic->getInternalId() === $topicInternalId) { - $totalAttribute = match ($target->getAttribute('providerType')) { - MESSAGE_TYPE_EMAIL => 'emailTotal', - MESSAGE_TYPE_SMS => 'smsTotal', - MESSAGE_TYPE_PUSH => 'pushTotal', - default => throw new Exception('Invalid target CertificatesAdapter type'), - }; - $dbForProject->decreaseDocumentAttribute( - 'topics', - $topicId, - $totalAttribute, - min: 0 - ); - } - } - ); - } - /** * @param Document $project * @param callable $getProjectDB @@ -315,32 +274,12 @@ class Deletes extends Action */ private function deleteExpiredTargets(Document $project, callable $getProjectDB): void { - $this->deleteByGroup( - 'targets', - [ - Query::equal('expired', [true]), - Query::orderAsc(), - ], - $getProjectDB($project), - function (Document $target) use ($getProjectDB, $project) { - $this->deleteTargetSubscribers($project, $getProjectDB, $target); - } - ); + \Appwrite\Deletes\Targets::delete($getProjectDB($project), Query::equal('expired', [true])); } private function deleteSessionTargets(Document $project, callable $getProjectDB, Document $session): void { - $this->deleteByGroup( - 'targets', - [ - Query::equal('sessionInternalId', [$session->getInternalId()]), - Query::orderAsc(), - ], - $getProjectDB($project), - function (Document $target) use ($getProjectDB, $project) { - $this->deleteTargetSubscribers($project, $getProjectDB, $target); - } - ); + \Appwrite\Deletes\Targets::delete($getProjectDB($project), Query::equal('sessionInternalId', [$session->getInternalId()])); } /** @@ -723,23 +662,10 @@ class Deletes extends Action ], $dbForProject); // Delete identities - $this->deleteByGroup('identities', [ - Query::equal('userInternalId', [$userInternalId]), - Query::orderAsc() - ], $dbForProject); + \Appwrite\Deletes\Identities::delete($dbForProject, Query::equal('userInternalId', [$userInternalId])); // Delete targets - $this->deleteByGroup( - 'targets', - [ - Query::equal('userInternalId', [$userInternalId]), - Query::orderAsc() - ], - $dbForProject, - function (Document $target) use ($getProjectDB, $project) { - $this->deleteTargetSubscribers($project, $getProjectDB, $target); - } - ); + \Appwrite\Deletes\Targets::delete($dbForProject, Query::equal('userInternalId', [$userInternalId])); } /** From 270250a563df620d77551699af13a57faa936801 Mon Sep 17 00:00:00 2001 From: Fabian Gruber Date: Fri, 25 Apr 2025 16:25:26 +0200 Subject: [PATCH 2/2] review feedback --- app/controllers/api/users.php | 6 ++++-- src/Appwrite/Platform/Workers/Deletes.php | 12 +++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 13ab26ac15..bf2435936a 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -9,6 +9,8 @@ use Appwrite\Auth\Validator\PasswordDictionary; use Appwrite\Auth\Validator\PasswordHistory; use Appwrite\Auth\Validator\PersonalData; use Appwrite\Auth\Validator\Phone; +use Appwrite\Deletes\Identities as DeleteIdentities; +use Appwrite\Deletes\Targets as DeleteTargets; use Appwrite\Detector\Detector; use Appwrite\Event\Delete; use Appwrite\Event\Event; @@ -2275,8 +2277,8 @@ App::delete('/v1/users/:userId') $clone = clone $user; $dbForProject->deleteDocument('users', $userId); - \Appwrite\Deletes\Identities::delete($dbForProject, Query::equal('userInternalId', [$user->getInternalId()])); - \Appwrite\Deletes\Targets::delete($dbForProject, Query::equal('userInternalId', [$user->getInternalId()])); + DeleteIdentities::delete($dbForProject, Query::equal('userInternalId', [$user->getInternalId()])); + DeleteTargets::delete($dbForProject, Query::equal('userInternalId', [$user->getInternalId()])); $queueForDeletes ->setType(DELETE_TYPE_DOCUMENT) diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 0f2bf19a54..a61db63de6 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -4,6 +4,8 @@ namespace Appwrite\Platform\Workers; use Appwrite\Auth\Auth; use Appwrite\Certificates\Adapter as CertificatesAdapter; +use Appwrite\Deletes\Identities; +use Appwrite\Deletes\Targets; use Appwrite\Extend\Exception; use Executor\Executor; use Throwable; @@ -149,7 +151,7 @@ class Deletes extends Action $this->deleteTopic($project, $getProjectDB, $document); break; case DELETE_TYPE_TARGET: - \Appwrite\Deletes\Targets::deleteSubscribers($getProjectDB($project), $document); + Targets::deleteSubscribers($getProjectDB($project), $document); break; case DELETE_TYPE_EXPIRED_TARGETS: $this->deleteExpiredTargets($project, $getProjectDB); @@ -274,12 +276,12 @@ class Deletes extends Action */ private function deleteExpiredTargets(Document $project, callable $getProjectDB): void { - \Appwrite\Deletes\Targets::delete($getProjectDB($project), Query::equal('expired', [true])); + Targets::delete($getProjectDB($project), Query::equal('expired', [true])); } private function deleteSessionTargets(Document $project, callable $getProjectDB, Document $session): void { - \Appwrite\Deletes\Targets::delete($getProjectDB($project), Query::equal('sessionInternalId', [$session->getInternalId()])); + Targets::delete($getProjectDB($project), Query::equal('sessionInternalId', [$session->getInternalId()])); } /** @@ -662,10 +664,10 @@ class Deletes extends Action ], $dbForProject); // Delete identities - \Appwrite\Deletes\Identities::delete($dbForProject, Query::equal('userInternalId', [$userInternalId])); + Identities::delete($dbForProject, Query::equal('userInternalId', [$userInternalId])); // Delete targets - \Appwrite\Deletes\Targets::delete($dbForProject, Query::equal('userInternalId', [$userInternalId])); + Targets::delete($dbForProject, Query::equal('userInternalId', [$userInternalId])); } /**