Compare commits

...
Author SHA1 Message Date
shimon 39b8d5c196 cert failure message override 2025-04-28 17:48:14 +03:00
Christy JacobandGitHub 5578e199e4 Merge pull request #9689 from appwrite/PLA-2808
feat(users): synchronously delete identities and targets in controller
2025-04-28 12:56:31 +04:00
Jake BarnbyandGitHub 8623767926 Merge pull request #9677 from ArnabChatterjee20k/chore--utopia-migration
Chore: updated utopia migration version
2025-04-28 03:51:57 +00:00
Jake BarnbyandGitHub d39fce55c1 Merge branch '1.6.x' into chore--utopia-migration 2025-04-28 03:38:30 +00:00
Steven NguyenandGitHub 70f31bb48c Merge pull request #9691 from appwrite/update-flutter-sdk
chore: update flutter sdk
2025-04-25 12:48:50 -07:00
Chirag Aggarwal b65c375655 chore: update flutter sdk 2025-04-25 19:24:12 +00:00
Fabian Gruber 270250a563 review feedback 2025-04-25 16:25:26 +02:00
Fabian Gruber 9601860247 feat(users): synchronously delete identities and targets in controller 2025-04-25 15:55:10 +02:00
Christy JacobandGitHub 09ca143231 Merge pull request #9687 from appwrite/PLA-2807
feat(dispatch): add contextual dispatch logic
2025-04-25 17:52:43 +04:00
Christy JacobandGitHub 2afbfea0d0 Merge pull request #9638 from appwrite/fix-zero-specifications
fix(functions): treat 0 as unlimited for CPUs and memory
2025-04-25 14:49:04 +04:00
Steven Nguyen 928f535800 chore(functions): update desc for CPU and mem env vars
Clarify that 0 for _APP_FUNCTIONS_CPUS and _APP_FUNCTIONS_MEMORY means
unlimited.
2025-04-24 17:30:03 -07:00
Fabian Gruber 391e045775 feat(dispatch): add contextual dispatch logic 2025-04-24 23:15:00 +02:00
ArnabChatterjee20k 26edc75455 updated utopia migration version 2025-04-23 15:09:44 +05:30
Steven Nguyen b431eb4a51 fix(functions): treat 0 as unlimited for CPUs and memory
Prior versions of Appwrite treated 0 as unlimited for
_APP_FUNCTIONS_CPUS and _APP_FUNCTIONS_MEMORY. As such, this PR updates
the specification validator and the list specifications endpoint to also
treat the default 0 value as unlimited, allowing any specification.
2025-04-11 12:13:06 -07:00
13 changed files with 224 additions and 195 deletions
+1 -1
View File
@@ -59,7 +59,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '15.0.1',
'version' => '15.0.2',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
+2 -2
View File
@@ -800,7 +800,7 @@ return [
],
[
'name' => '_APP_FUNCTIONS_CPUS',
'description' => 'The maximum number of CPU core a single cloud function is allowed to use. Please note that setting a value higher than available cores will result in a function error, which might result in an error. The default value is empty. When it\'s empty, CPU limit will be disabled.',
'description' => 'The maximum number of CPU core a single cloud function is allowed to use. Please note that setting a value higher than available cores will result in a function error, which might result in an error. The default value is empty. When it\'s empty or 0, CPU limit will be disabled.',
'introduction' => '0.7.0',
'default' => '0',
'required' => false,
@@ -809,7 +809,7 @@ return [
],
[
'name' => '_APP_FUNCTIONS_MEMORY',
'description' => 'The maximum amount of memory a single cloud function is allowed to use in megabytes. The default value is empty. When it\'s empty, memory limit will be disabled.',
'description' => 'The maximum amount of memory a single cloud function is allowed to use in megabytes. The default value is empty. When it\'s empty or 0, memory limit will be disabled.',
'introduction' => '0.7.0',
'default' => '0',
'required' => false,
+9 -5
View File
@@ -186,8 +186,8 @@ App::post('/v1/functions')
->param('specification', APP_FUNCTION_SPECIFICATION_DEFAULT, fn (array $plan) => new RuntimeSpecification(
$plan,
Config::getParam('runtime-specifications', []),
App::getEnv('_APP_FUNCTIONS_CPUS', APP_FUNCTION_CPUS_DEFAULT),
App::getEnv('_APP_FUNCTIONS_MEMORY', APP_FUNCTION_MEMORY_DEFAULT)
System::getEnv('_APP_FUNCTIONS_CPUS', 0),
System::getEnv('_APP_FUNCTIONS_MEMORY', 0)
), 'Runtime specification for the function and builds.', true, ['plan'])
->inject('request')
->inject('response')
@@ -569,8 +569,12 @@ App::get('/v1/functions/specifications')
$spec['enabled'] = in_array($spec['slug'], $plan['runtimeSpecifications']);
}
$maxCpus = System::getEnv('_APP_FUNCTIONS_CPUS', 0);
$maxMemory = System::getEnv('_APP_FUNCTIONS_MEMORY', 0);
// Only add specs that are within the limits set by environment variables
if ($spec['cpus'] <= System::getEnv('_APP_FUNCTIONS_CPUS', 1) && $spec['memory'] <= System::getEnv('_APP_FUNCTIONS_MEMORY', 512)) {
// Treat 0 as no limit
if ((empty($maxCpus) || $spec['cpus'] <= $maxCpus) && (empty($maxMemory) || $spec['memory'] <= $maxMemory)) {
$runtimeSpecs[] = $spec;
}
}
@@ -872,8 +876,8 @@ App::put('/v1/functions/:functionId')
->param('specification', APP_FUNCTION_SPECIFICATION_DEFAULT, fn (array $plan) => new RuntimeSpecification(
$plan,
Config::getParam('runtime-specifications', []),
App::getEnv('_APP_FUNCTIONS_CPUS', APP_FUNCTION_CPUS_DEFAULT),
App::getEnv('_APP_FUNCTIONS_MEMORY', APP_FUNCTION_MEMORY_DEFAULT)
System::getEnv('_APP_FUNCTIONS_CPUS', 0),
System::getEnv('_APP_FUNCTIONS_MEMORY', 0)
), 'Runtime specification for the function and builds.', true, ['plan'])
->inject('request')
->inject('response')
+4
View File
@@ -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,6 +2277,8 @@ App::delete('/v1/users/:userId')
$clone = clone $user;
$dbForProject->deleteDocument('users', $userId);
DeleteIdentities::delete($dbForProject, Query::equal('userInternalId', [$user->getInternalId()]));
DeleteTargets::delete($dbForProject, Query::equal('userInternalId', [$user->getInternalId()]));
$queueForDeletes
->setType(DELETE_TYPE_DOCUMENT)
+89 -83
View File
@@ -43,29 +43,6 @@ $http = new Server(
$payloadSize = 12 * (1024 * 1024); // 12MB - adding slight buffer for headers and other data that might be sent with the payload - update later with valid testing
$totalWorkers = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
$http
->set([
'worker_num' => $totalWorkers,
'dispatch_func' => 'dispatch',
'open_http2_protocol' => true,
'http_compression' => false,
'package_max_length' => $payloadSize,
'buffer_output_size' => $payloadSize,
'task_worker_num' => 1, // required for the task to fetch domains background
]);
$http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) {
Console::success('Worker ' . ++$workerId . ' started successfully');
});
$http->on(Constant::EVENT_BEFORE_RELOAD, function ($server, $workerId) {
Console::success('Starting reload...');
});
$http->on(Constant::EVENT_AFTER_RELOAD, function ($server, $workerId) {
Console::success('Reload completed...');
});
/**
* Assigns HTTP requests to worker threads by analyzing its payload/content.
*
@@ -83,76 +60,105 @@ $http->on(Constant::EVENT_AFTER_RELOAD, function ($server, $workerId) {
*/
function dispatch(Server $server, int $fd, int $type, $data = null): int
{
global $totalWorkers, $domains;
$resolveWorkerId = function (Server $server, $data = null) {
global $totalWorkers, $domains;
// If data is not set we can send request to any worker
// first we try to pick idle worker, if not we randomly pick a worker
if ($data === null) {
// If data is not set we can send request to any worker
// first we try to pick idle worker, if not we randomly pick a worker
if ($data === null) {
for ($i = 0; $i < $totalWorkers; $i++) {
if ($server->getWorkerStatus($i) === SWOOLE_WORKER_IDLE) {
return $i;
}
}
return rand(0, $totalWorkers - 1);
}
$riskyWorkersPercent = intval(System::getEnv('_APP_RISKY_WORKERS_PERCENT', 80)) / 100; // Decimal form 0 to 1
// Each worker has numeric ID, starting from 0 and incrementing
// From 0 to riskyWorkers, we consider safe workers
// From riskyWorkers to totalWorkers, we consider risky workers
$riskyWorkers = (int)floor($totalWorkers * $riskyWorkersPercent); // Absolute amount of risky workers
$domain = '';
// max up to 3 as first line has request details and second line has host
$lines = explode("\n", $data, 3);
$request = $lines[0];
if (count($lines) > 1) {
$domain = trim(explode('Host: ', $lines[1])[1]);
}
// Sync executions are considered risky
$risky = false;
if (str_starts_with($request, 'POST') && str_contains($request, '/executions')) {
$risky = true;
} elseif (str_ends_with($domain, System::getEnv('_APP_DOMAIN_FUNCTIONS'))) {
$risky = true;
} elseif ($domains->get(md5($domain), 'value') === 1) {
// executions request coming from custom domain
$risky = true;
}
if ($risky) {
// If risky request, only consider risky workers
for ($j = $riskyWorkers; $j < $totalWorkers; $j++) {
/** Reference https://openswoole.com/docs/modules/swoole-server-getWorkerStatus#description */
if ($server->getWorkerStatus($j) === SWOOLE_WORKER_IDLE) {
// If idle worker found, give to him
return $j;
}
}
// If no idle workers, give to random risky worker
$worker = rand($riskyWorkers, $totalWorkers - 1);
Console::warning("swoole_dispatch: Risky branch: did not find a idle worker, picking random worker {$worker}");
return $worker;
}
// If safe request, give to any idle worker
// Its fine to pick risky worker here, because it's idle. Idle is never actually risky
for ($i = 0; $i < $totalWorkers; $i++) {
if ($server->getWorkerStatus($i) === SWOOLE_WORKER_IDLE) {
return $i;
}
}
return rand(0, $totalWorkers - 1);
}
$riskyWorkersPercent = intval(System::getEnv('_APP_RISKY_WORKERS_PERCENT', 80)) / 100; // Decimal form 0 to 1
// Each worker has numeric ID, starting from 0 and incrementing
// From 0 to riskyWorkers, we consider safe workers
// From riskyWorkers to totalWorkers, we consider risky workers
$riskyWorkers = (int) floor($totalWorkers * $riskyWorkersPercent); // Absolute amount of risky workers
$domain = '';
// max up to 3 as first line has request details and second line has host
$lines = explode("\n", $data, 3);
$request = $lines[0];
if (count($lines) > 1) {
$domain = trim(explode('Host: ', $lines[1])[1]);
}
// Sync executions are considered risky
$risky = false;
if (str_starts_with($request, 'POST') && str_contains($request, '/executions')) {
$risky = true;
} elseif (str_ends_with($domain, System::getEnv('_APP_DOMAIN_FUNCTIONS'))) {
$risky = true;
} elseif ($domains->get(md5($domain), 'value') === 1) {
// executions request coming from custom domain
$risky = true;
}
if ($risky) {
// If risky request, only consider risky workers
for ($j = $riskyWorkers; $j < $totalWorkers; $j++) {
/** Reference https://openswoole.com/docs/modules/swoole-server-getWorkerStatus#description */
if ($server->getWorkerStatus($j) === SWOOLE_WORKER_IDLE) {
// If idle worker found, give to him
return $j;
}
}
// If no idle workers, give to random risky worker
$worker = rand($riskyWorkers, $totalWorkers - 1);
Console::warning("swoole_dispatch: Risky branch: did not find a idle worker, picking random worker {$worker}");
// If no idle worker found, give to random safe worker
// We avoid risky workers here, as it could be in work - not idle. Thats exactly when they are risky.
$worker = rand(0, $riskyWorkers - 1);
Console::warning("swoole_dispatch: Non-risky branch: did not find a idle worker, picking random worker {$worker}");
return $worker;
}
// If safe request, give to any idle worker
// Its fine to pick risky worker here, because it's idle. Idle is never actually risky
for ($i = 0; $i < $totalWorkers; $i++) {
if ($server->getWorkerStatus($i) === SWOOLE_WORKER_IDLE) {
return $i;
}
}
// If no idle worker found, give to random safe worker
// We avoid risky workers here, as it could be in work - not idle. Thats exactly when they are risky.
$worker = rand(0, $riskyWorkers - 1);
Console::warning("swoole_dispatch: Non-risky branch: did not find a idle worker, picking random worker {$worker}");
return $worker;
};
$workerId = $resolveWorkerId($server, $data);
$server->bind($fd, $workerId);
return $workerId;
}
$http
->set([
Constant::OPTION_WORKER_NUM => $totalWorkers,
Constant::OPTION_DISPATCH_FUNC => dispatch(...),
Constant::OPTION_DISPATCH_MODE => SWOOLE_DISPATCH_UIDMOD,
Constant::OPTION_HTTP_COMPRESSION => false,
Constant::OPTION_PACKAGE_MAX_LENGTH => $payloadSize,
Constant::OPTION_OUTPUT_BUFFER_SIZE => $payloadSize,
Constant::OPTION_TASK_WORKER_NUM => 1, // required for the task to fetch domains background
]);
$http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) {
Console::success('Worker ' . ++$workerId . ' started successfully');
});
$http->on(Constant::EVENT_BEFORE_RELOAD, function ($server, $workerId) {
Console::success('Starting reload...');
});
$http->on(Constant::EVENT_AFTER_RELOAD, function ($server, $workerId) {
Console::success('Reload completed...');
});
include __DIR__ . '/controllers/general.php';
function createDatabase(App $app, string $resourceKey, string $dbName, array $collections, mixed $pools, callable $extraSetup = null): void
+1 -1
View File
@@ -60,7 +60,7 @@
"utopia-php/locale": "0.4.*",
"utopia-php/logger": "0.6.*",
"utopia-php/messaging": "0.16.*",
"utopia-php/migration": "0.8.*",
"utopia-php/migration": "0.9.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.7.*",
"utopia-php/pools": "0.8.*",
Generated
+19 -19
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "35fd85e8d566d20d8177266469c5ebcb",
"content-hash": "f8a8967327763c5b85721583bd78c1de",
"packages": [
{
"name": "adhocore/jwt",
@@ -3660,16 +3660,16 @@
},
{
"name": "utopia-php/fetch",
"version": "0.4.1",
"version": "0.4.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/fetch.git",
"reference": "65095dac14037db0c822fb5e209e5bd3187a0303"
"reference": "83986d1be75a2fae4e684107fe70dd78a8e19b77"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/fetch/zipball/65095dac14037db0c822fb5e209e5bd3187a0303",
"reference": "65095dac14037db0c822fb5e209e5bd3187a0303",
"url": "https://api.github.com/repos/utopia-php/fetch/zipball/83986d1be75a2fae4e684107fe70dd78a8e19b77",
"reference": "83986d1be75a2fae4e684107fe70dd78a8e19b77",
"shasum": ""
},
"require": {
@@ -3693,9 +3693,9 @@
"description": "A simple library that provides an interface for making HTTP Requests.",
"support": {
"issues": "https://github.com/utopia-php/fetch/issues",
"source": "https://github.com/utopia-php/fetch/tree/0.4.1"
"source": "https://github.com/utopia-php/fetch/tree/0.4.2"
},
"time": "2025-04-14T07:34:27+00:00"
"time": "2025-04-25T13:48:02+00:00"
},
{
"name": "utopia-php/framework",
@@ -3951,16 +3951,16 @@
},
{
"name": "utopia-php/migration",
"version": "0.8.6",
"version": "0.9.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/migration.git",
"reference": "84163e16edc0b2e64c34ad7b7c4cc5f05d762daf"
"reference": "8a56e928d6ccb5958a5b80ac27286d969f11a6b0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/84163e16edc0b2e64c34ad7b7c4cc5f05d762daf",
"reference": "84163e16edc0b2e64c34ad7b7c4cc5f05d762daf",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/8a56e928d6ccb5958a5b80ac27286d969f11a6b0",
"reference": "8a56e928d6ccb5958a5b80ac27286d969f11a6b0",
"shasum": ""
},
"require": {
@@ -4001,9 +4001,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/migration/issues",
"source": "https://github.com/utopia-php/migration/tree/0.8.6"
"source": "https://github.com/utopia-php/migration/tree/0.9.2"
},
"time": "2025-04-14T08:22:09+00:00"
"time": "2025-04-22T16:09:28+00:00"
},
{
"name": "utopia-php/orchestration",
@@ -4767,16 +4767,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "0.40.14",
"version": "0.40.15",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "bf3bf29b24d2dce9f49e6849e3af2a1567592d02"
"reference": "65c708b931b29b3e01c5cc7504a734ce2cc3dc95"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/bf3bf29b24d2dce9f49e6849e3af2a1567592d02",
"reference": "bf3bf29b24d2dce9f49e6849e3af2a1567592d02",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/65c708b931b29b3e01c5cc7504a734ce2cc3dc95",
"reference": "65c708b931b29b3e01c5cc7504a734ce2cc3dc95",
"shasum": ""
},
"require": {
@@ -4812,9 +4812,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/0.40.14"
"source": "https://github.com/appwrite/sdk-generator/tree/0.40.15"
},
"time": "2025-04-24T14:36:08+00:00"
"time": "2025-04-25T08:50:44+00:00"
},
{
"name": "doctrine/annotations",
+12
View File
@@ -1,5 +1,17 @@
## 15.0.2
* Avoid setting empty `User-Agent` header and only encode it when present.
* Update doc examples to use new multi-region endpoint: `https://<REGION>.cloud.appwrite.io/v1`.
## 15.0.1
* Removed `Content-Type` header from GET and HEAD requests.
* Add validation for setting endpoint in `setEndpoint` and `setEndPointRealtime` methods.
* Include Figma in list of available OAuth providers.
## 15.0.0
* Encode `User-Agent` header to fix invalid HTTP header field value error.
* Breaking changes:
* Changed the typing of `AppwriteException`'s response parameter from a `dynamic` object to an optional string (`?String`).
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace Appwrite\Deletes;
use Utopia\Database\Database;
use Utopia\Database\Query;
class Identities
{
public static function delete(Database $database, Query $query): void
{
$database->deleteDocuments(
'identities',
[
$query,
Query::orderAsc()
],
Database::DELETE_BATCH_SIZE
);
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
namespace Appwrite\Deletes;
use Appwrite\Extend\Exception;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
class Targets
{
public static function delete(Database $database, Query $query): void
{
$database->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
);
}
}
);
}
}
@@ -34,7 +34,7 @@ class RuntimeSpecification extends Validator
$allowedSpecifications = [];
foreach ($this->specifications as $size => $values) {
if ($values['cpus'] <= $this->maxCpus && $values['memory'] <= $this->maxMemory) {
if ((empty($this->maxCpus) || $values['cpus'] <= $this->maxCpus) && (empty($this->maxMemory) || $values['memory'] <= $this->maxMemory)) {
if (!empty($this->plan) && array_key_exists('runtimeSpecifications', $this->plan)) {
if (!\in_array($size, $this->plan['runtimeSpecifications'])) {
continue;
@@ -171,10 +171,7 @@ class Certificates extends Action
$certificate->setAttribute('issueDate', DateTime::now());
$success = true;
} catch (Throwable $e) {
$logs = $e->getMessage();
// Set exception as log in certificate document
$certificate->setAttribute('logs', \mb_strcut($logs, 0, 1000000));// Limit to 1MB
$certificate->setAttribute('logs', 'Failed to create custom domain');
// Increase attempts count
$attempts = $certificate->getAttribute('attempts', 0) + 1;
+7 -79
View File
@@ -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:
$this->deleteTargetSubscribers($project, $getProjectDB, $document);
Targets::deleteSubscribers($getProjectDB($project), $document);
break;
case DELETE_TYPE_EXPIRED_TARGETS:
$this->deleteExpiredTargets($project, $getProjectDB);
@@ -265,47 +267,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 +276,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);
}
);
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);
}
);
Targets::delete($getProjectDB($project), Query::equal('sessionInternalId', [$session->getInternalId()]));
}
/**
@@ -723,23 +664,10 @@ class Deletes extends Action
], $dbForProject);
// Delete identities
$this->deleteByGroup('identities', [
Query::equal('userInternalId', [$userInternalId]),
Query::orderAsc()
], $dbForProject);
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);
}
);
Targets::delete($dbForProject, Query::equal('userInternalId', [$userInternalId]));
}
/**