From ef47618bf3dc7be6f9bb60881f051a3df9abd028 Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 21 Aug 2025 12:20:50 +0530 Subject: [PATCH 1/5] update: user create events logic. --- app/controllers/api/account.php | 6 +- app/controllers/api/users.php | 65 ++++++++++++++----- app/controllers/shared/api.php | 52 +-------------- .../Utopia/Response/Model/BaseList.php | 3 +- 4 files changed, 55 insertions(+), 71 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 4956e8b6a2..1eeb10b459 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -292,6 +292,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res App::post('/v1/account') ->desc('Create account') ->groups(['api', 'account', 'auth']) + ->label('event', 'users.[userId].create') ->label('scope', 'sessions.write') ->label('auth.type', 'email-password') ->label('audits.event', 'user.create') @@ -322,7 +323,8 @@ App::post('/v1/account') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Hooks $hooks) { + ->inject('queueForEvents') + ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { $email = \strtolower($email); if ('console' === $project->getId()) { @@ -432,6 +434,8 @@ App::post('/v1/account') Authorization::setRole(Role::user($user->getId())->toString()); Authorization::setRole(Role::users()->toString()); + $queueForEvents->setParam('userId', $user->getId()); + $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($user, Response::MODEL_ACCOUNT); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 993f8a03c9..7918cb43d8 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -59,8 +59,19 @@ use Utopia\Validator\Text; use Utopia\Validator\WhiteList; /** TODO: Remove function when we move to using utopia/platform */ -function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Hooks $hooks): Document -{ +function createUser( + string $hash, + mixed $hashOptions, + string $userId, + ?string $email, + ?string $password, + ?string $phone, + string $name, + Document $project, + Database $dbForProject, + Event $queueForEvents, + Hooks $hooks, +): Document { $plaintextPassword = $password; $hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array $passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; @@ -184,12 +195,15 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e throw new Exception(Exception::USER_ALREADY_EXISTS); } + $queueForEvents->setParam('userId', $user->getId()); + return $user; } App::post('/v1/users') ->desc('Create user') ->groups(['api', 'users']) + ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -215,8 +229,9 @@ App::post('/v1/users') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $hooks); + ->inject('queueForEvents') + ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { + $user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $queueForEvents, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($user, Response::MODEL_USER); @@ -225,6 +240,7 @@ App::post('/v1/users') App::post('/v1/users/bcrypt') ->desc('Create user with bcrypt password') ->groups(['api', 'users']) + ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -249,8 +265,9 @@ App::post('/v1/users/bcrypt') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + ->inject('queueForEvents') + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { + $user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -260,6 +277,7 @@ App::post('/v1/users/bcrypt') App::post('/v1/users/md5') ->desc('Create user with MD5 password') ->groups(['api', 'users']) + ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -284,8 +302,9 @@ App::post('/v1/users/md5') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + ->inject('queueForEvents') + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { + $user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -295,6 +314,7 @@ App::post('/v1/users/md5') App::post('/v1/users/argon2') ->desc('Create user with Argon2 password') ->groups(['api', 'users']) + ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -319,8 +339,9 @@ App::post('/v1/users/argon2') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + ->inject('queueForEvents') + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { + $user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -330,6 +351,7 @@ App::post('/v1/users/argon2') App::post('/v1/users/sha') ->desc('Create user with SHA password') ->groups(['api', 'users']) + ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -355,14 +377,15 @@ App::post('/v1/users/sha') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + ->inject('queueForEvents') + ->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { $options = '{}'; if (!empty($passwordVersion)) { $options = '{"version":"' . $passwordVersion . '"}'; } - $user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + $user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -372,6 +395,7 @@ App::post('/v1/users/sha') App::post('/v1/users/phpass') ->desc('Create user with PHPass password') ->groups(['api', 'users']) + ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -396,8 +420,9 @@ App::post('/v1/users/phpass') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + ->inject('queueForEvents') + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { + $user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -407,6 +432,7 @@ App::post('/v1/users/phpass') App::post('/v1/users/scrypt') ->desc('Create user with Scrypt password') ->groups(['api', 'users']) + ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -436,7 +462,8 @@ App::post('/v1/users/scrypt') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + ->inject('queueForEvents') + ->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { $options = [ 'salt' => $passwordSalt, 'costCpu' => $passwordCpu, @@ -445,7 +472,7 @@ App::post('/v1/users/scrypt') 'length' => $passwordLength ]; - $user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + $user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -455,6 +482,7 @@ App::post('/v1/users/scrypt') App::post('/v1/users/scrypt-modified') ->desc('Create user with Scrypt modified password') ->groups(['api', 'users']) + ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -482,8 +510,9 @@ App::post('/v1/users/scrypt-modified') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + ->inject('queueForEvents') + ->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { + $user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index c2f5c415a0..5cbaac4f34 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -60,38 +60,6 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar return $label; }; -$eventDatabaseListener = function (Document $project, Document $document, Response $response, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime) { - // Only trigger events for user creation with the database listener. - if ($document->getCollection() !== 'users') { - return; - } - - $queueForEvents - ->setEvent('users.[userId].create') - ->setParam('userId', $document->getId()) - ->setPayload($response->output($document, Response::MODEL_USER)); - - // Trigger functions, webhooks, and realtime events - $queueForFunctions - ->from($queueForEvents) - ->trigger(); - - - /** Trigger webhooks events only if a project has them enabled */ - if (!empty($project->getAttribute('webhooks'))) { - $queueForWebhooks - ->from($queueForEvents) - ->trigger(); - } - - /** Trigger realtime events only for non console events */ - if ($queueForEvents->getProject()->getId() !== 'console') { - $queueForRealtime - ->from($queueForEvents) - ->trigger(); - } -}; - $usageDatabaseListener = function (string $event, Document $document, StatsUsage $queueForStatsUsage) { $value = 1; @@ -423,7 +391,7 @@ App::init() ->inject('plan') ->inject('devKey') ->inject('telemetry') - ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry) use ($usageDatabaseListener, $eventDatabaseListener) { + ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry) use ($usageDatabaseListener) { $route = $utopia->getRoute(); @@ -532,28 +500,12 @@ App::init() $queueForBuilds->setProject($project); $queueForMessaging->setProject($project); - // Clone the queues, to prevent events triggered by the database listener - // from overwriting the events that are supposed to be triggered in the shutdown hook. - $queueForEventsClone = new Event($publisher); - $queueForFunctions = new Func($publisher); - $queueForWebhooks = new Webhook($publisher); - $queueForRealtime = new Realtime(); - $dbForProject ->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage)) ->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage)) ->on(Database::EVENT_DOCUMENTS_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage)) ->on(Database::EVENT_DOCUMENTS_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage)) - ->on(Database::EVENT_DOCUMENTS_UPSERT, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage)) - ->on(Database::EVENT_DOCUMENT_CREATE, 'create-trigger-events', fn ($event, $document) => $eventDatabaseListener( - $project, - $document, - $response, - $queueForEventsClone->from($queueForEvents), - $queueForFunctions->from($queueForEvents), - $queueForWebhooks->from($queueForEvents), - $queueForRealtime->from($queueForEvents) - )); + ->on(Database::EVENT_DOCUMENTS_UPSERT, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage)); $useCache = $route->getLabel('cache', false); $storageCacheOperationsCounter = $telemetry->createCounter('storage.cache.operations.load'); diff --git a/src/Appwrite/Utopia/Response/Model/BaseList.php b/src/Appwrite/Utopia/Response/Model/BaseList.php index f1da542c6c..447a4436e3 100644 --- a/src/Appwrite/Utopia/Response/Model/BaseList.php +++ b/src/Appwrite/Utopia/Response/Model/BaseList.php @@ -31,8 +31,7 @@ class BaseList extends Model string $model, bool $paging = true, bool $public = true - ) - { + ) { $this->name = $name; $this->type = $type; $this->public = $public; From 6df096da8b70bb091f10da2f47c80aa4e76f2189 Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 21 Aug 2025 18:08:12 +0530 Subject: [PATCH 2/5] bump: lock. --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 82f43a85a0..715ce2e3e5 100644 --- a/composer.lock +++ b/composer.lock @@ -4926,16 +4926,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "1.1.3", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "bbd0fd9f222e606a0a25ff553e53ff92f0bdd0b3" + "reference": "4196a50d4cd847e2d7f8dd1136fc0ee96e931043" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/bbd0fd9f222e606a0a25ff553e53ff92f0bdd0b3", - "reference": "bbd0fd9f222e606a0a25ff553e53ff92f0bdd0b3", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/4196a50d4cd847e2d7f8dd1136fc0ee96e931043", + "reference": "4196a50d4cd847e2d7f8dd1136fc0ee96e931043", "shasum": "" }, "require": { @@ -4971,9 +4971,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/1.1.3" + "source": "https://github.com/appwrite/sdk-generator/tree/1.1.4" }, - "time": "2025-08-21T09:31:14+00:00" + "time": "2025-08-21T09:46:01+00:00" }, { "name": "doctrine/annotations", From 60093bf3a0ce8eea9c67349ff7446a1c54124a76 Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 22 Aug 2025 10:09:55 +0530 Subject: [PATCH 3/5] fix: publisher consumers for functions. --- app/cli.php | 3 +++ app/controllers/api/health.php | 6 +++--- app/init/resources.php | 6 ++++++ app/worker.php | 4 ++++ composer.lock | 12 ++++++------ src/Appwrite/Platform/Tasks/ScheduleBase.php | 5 ++++- src/Appwrite/Platform/Tasks/ScheduleExecutions.php | 2 +- src/Appwrite/Platform/Tasks/ScheduleFunctions.php | 2 +- 8 files changed, 28 insertions(+), 12 deletions(-) diff --git a/app/cli.php b/app/cli.php index 504e4fb5e6..2ad37c0ce2 100644 --- a/app/cli.php +++ b/app/cli.php @@ -194,6 +194,9 @@ CLI::setResource('publisher', function (Group $pools) { CLI::setResource('publisherDatabases', function (BrokerPool $publisher) { return $publisher; }, ['publisher']); +CLI::setResource('publisherFunctions', function (BrokerPool $publisher) { + return $publisher; +}, ['publisher']); CLI::setResource('publisherMigrations', function (BrokerPool $publisher) { return $publisher; }, ['publisher']); diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index 1dc6ce917b..975db909c6 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -688,12 +688,12 @@ App::get('/v1/health/queue/functions') contentType: ContentType::JSON )) ->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true) - ->inject('publisher') + ->inject('publisherFunctions') ->inject('response') - ->action(function (int|string $threshold, Publisher $publisher, Response $response) { + ->action(function (int|string $threshold, Publisher $publisherFunctions, Response $response) { $threshold = \intval($threshold); - $size = $publisher->getQueueSize(new Queue(Event::FUNCTIONS_QUEUE_NAME)); + $size = $publisherFunctions->getQueueSize(new Queue(Event::FUNCTIONS_QUEUE_NAME)); if ($size >= $threshold) { throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}."); diff --git a/app/init/resources.php b/app/init/resources.php index 468ca4fbd8..375c835ea9 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -87,6 +87,9 @@ App::setResource('publisher', function (Group $pools) { App::setResource('publisherDatabases', function (BrokerPool $publisher) { return $publisher; }, ['publisher']); +App::setResource('publisherFunctions', function (BrokerPool $publisher) { + return $publisher; +}, ['publisher']); App::setResource('publisherMigrations', function (BrokerPool $publisher) { return $publisher; }, ['publisher']); @@ -99,6 +102,9 @@ App::setResource('consumer', function (Group $pools) { App::setResource('consumerDatabases', function (BrokerPool $consumer) { return $consumer; }, ['consumer']); +App::setResource('consumerFunctions', function (BrokerPool $consumer) { + return $consumer; +}, ['consumer']); App::setResource('consumerMigrations', function (BrokerPool $consumer) { return $consumer; }, ['consumer']); diff --git a/app/worker.php b/app/worker.php index 90f3368fe7..d358f578ea 100644 --- a/app/worker.php +++ b/app/worker.php @@ -251,6 +251,10 @@ Server::setResource('publisherDatabases', function (BrokerPool $publisher) { return $publisher; }, ['publisher']); +Server::setResource('publisherFunctions', function (BrokerPool $publisher) { + return $publisher; +}, ['publisher']); + Server::setResource('publisherMigrations', function (BrokerPool $publisher) { return $publisher; }, ['publisher']); diff --git a/composer.lock b/composer.lock index ac332c4946..b7e9a76088 100644 --- a/composer.lock +++ b/composer.lock @@ -3861,16 +3861,16 @@ }, { "name": "utopia-php/framework", - "version": "0.33.20", + "version": "0.33.21", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "e1c7ab4e0b5b0a9a70256b1e00912e101e76a131" + "reference": "eb0e82e90b8fa493f99b8d131bdd25173422c493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/e1c7ab4e0b5b0a9a70256b1e00912e101e76a131", - "reference": "e1c7ab4e0b5b0a9a70256b1e00912e101e76a131", + "url": "https://api.github.com/repos/utopia-php/http/zipball/eb0e82e90b8fa493f99b8d131bdd25173422c493", + "reference": "eb0e82e90b8fa493f99b8d131bdd25173422c493", "shasum": "" }, "require": { @@ -3902,9 +3902,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.20" + "source": "https://github.com/utopia-php/http/tree/0.33.21" }, - "time": "2025-05-18T23:51:21+00:00" + "time": "2025-08-19T10:52:15+00:00" }, { "name": "utopia-php/image", diff --git a/src/Appwrite/Platform/Tasks/ScheduleBase.php b/src/Appwrite/Platform/Tasks/ScheduleBase.php index 222051a67f..1710f0163d 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleBase.php +++ b/src/Appwrite/Platform/Tasks/ScheduleBase.php @@ -26,6 +26,7 @@ abstract class ScheduleBase extends Action protected BrokerPool $publisher; protected BrokerPool $publisherMigrations; + protected BrokerPool $publisherFunctions; private ?Histogram $collectSchedulesTelemetryDuration = null; private ?Gauge $collectSchedulesTelemetryCount = null; @@ -45,6 +46,7 @@ abstract class ScheduleBase extends Action ->desc("Execute {$type}s scheduled in Appwrite") ->inject('publisher') ->inject('publisherMigrations') + ->inject('publisherFunctions') ->inject('dbForPlatform') ->inject('getProjectDB') ->inject('telemetry') @@ -67,13 +69,14 @@ abstract class ScheduleBase extends Action * 2. Create timer that sync all changes from 'schedules' collection to local copy. Only reading changes thanks to 'resourceUpdatedAt' attribute * 3. Create timer that prepares coroutines for soon-to-execute schedules. When it's ready, coroutine sleeps until exact time before sending request to worker. */ - public function action(BrokerPool $publisher, BrokerPool $publisherMigrations, Database $dbForPlatform, callable $getProjectDB, Telemetry $telemetry): void + public function action(BrokerPool $publisher, BrokerPool $publisherMigrations, BrokerPool $publisherFunctions, Database $dbForPlatform, callable $getProjectDB, Telemetry $telemetry): void { Console::title(\ucfirst(static::getSupportedResource()) . ' scheduler V1'); Console::success(APP_NAME . ' ' . \ucfirst(static::getSupportedResource()) . ' scheduler v1 has started'); $this->publisher = $publisher; $this->publisherMigrations = $publisherMigrations; + $this->publisherFunctions = $publisherFunctions; $this->scheduleTelemetryCount = $telemetry->createGauge('task.schedule.count'); $this->collectSchedulesTelemetryDuration = $telemetry->createHistogram('task.schedule.collect_schedules.duration', 's'); diff --git a/src/Appwrite/Platform/Tasks/ScheduleExecutions.php b/src/Appwrite/Platform/Tasks/ScheduleExecutions.php index 96a5a05f0e..14a4259e17 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleExecutions.php +++ b/src/Appwrite/Platform/Tasks/ScheduleExecutions.php @@ -30,7 +30,7 @@ class ScheduleExecutions extends ScheduleBase { $intervalEnd = (new \DateTime())->modify('+' . self::ENQUEUE_TIMER . ' seconds'); - $queueForFunctions = new Func($this->publisher); + $queueForFunctions = new Func($this->publisherFunctions); foreach ($this->schedules as $schedule) { if (!$schedule['active']) { diff --git a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php index 43f1025c08..6f072425e4 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php +++ b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php @@ -90,7 +90,7 @@ class ScheduleFunctions extends ScheduleBase $this->updateProjectAccess($schedule['project'], $dbForPlatform); - $queueForFunctions = new Func($this->publisher); + $queueForFunctions = new Func($this->publisherFunctions); $queueForFunctions ->setType('schedule') From 07edb4e756340fcced366619557695d9d67d3b5f Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 22 Aug 2025 11:40:32 +0530 Subject: [PATCH 4/5] revert: database listener changes. --- app/controllers/api/account.php | 6 +-- app/controllers/api/users.php | 65 +++++++++------------------------ app/controllers/shared/api.php | 60 +++++++++++++++++++++++++++++- composer.lock | 12 +++--- 4 files changed, 83 insertions(+), 60 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 3d1fbc67c0..7efd377c67 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -293,7 +293,6 @@ $createSession = function (string $userId, string $secret, Request $request, Res App::post('/v1/account') ->desc('Create account') ->groups(['api', 'account', 'auth']) - ->label('event', 'users.[userId].create') ->label('scope', 'sessions.write') ->label('auth.type', 'email-password') ->label('audits.event', 'user.create') @@ -324,8 +323,7 @@ App::post('/v1/account') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->inject('queueForEvents') - ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { + ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Hooks $hooks) { $email = \strtolower($email); if ('console' === $project->getId()) { @@ -435,8 +433,6 @@ App::post('/v1/account') Authorization::setRole(Role::user($user->getId())->toString()); Authorization::setRole(Role::users()->toString()); - $queueForEvents->setParam('userId', $user->getId()); - $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($user, Response::MODEL_ACCOUNT); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 72a2afbec1..ff1df36dc2 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -60,19 +60,8 @@ use Utopia\Validator\Text; use Utopia\Validator\WhiteList; /** TODO: Remove function when we move to using utopia/platform */ -function createUser( - string $hash, - mixed $hashOptions, - string $userId, - ?string $email, - ?string $password, - ?string $phone, - string $name, - Document $project, - Database $dbForProject, - Event $queueForEvents, - Hooks $hooks, -): Document { +function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Hooks $hooks): Document +{ $plaintextPassword = $password; $hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array $passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; @@ -196,15 +185,12 @@ function createUser( throw new Exception(Exception::USER_ALREADY_EXISTS); } - $queueForEvents->setParam('userId', $user->getId()); - return $user; } App::post('/v1/users') ->desc('Create user') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -230,9 +216,8 @@ App::post('/v1/users') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->inject('queueForEvents') - ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { - $user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $queueForEvents, $hooks); + ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + $user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($user, Response::MODEL_USER); @@ -241,7 +226,6 @@ App::post('/v1/users') App::post('/v1/users/bcrypt') ->desc('Create user with bcrypt password') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -266,9 +250,8 @@ App::post('/v1/users/bcrypt') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->inject('queueForEvents') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { - $user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + $user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -278,7 +261,6 @@ App::post('/v1/users/bcrypt') App::post('/v1/users/md5') ->desc('Create user with MD5 password') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -303,9 +285,8 @@ App::post('/v1/users/md5') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->inject('queueForEvents') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { - $user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + $user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -315,7 +296,6 @@ App::post('/v1/users/md5') App::post('/v1/users/argon2') ->desc('Create user with Argon2 password') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -340,9 +320,8 @@ App::post('/v1/users/argon2') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->inject('queueForEvents') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { - $user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + $user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -352,7 +331,6 @@ App::post('/v1/users/argon2') App::post('/v1/users/sha') ->desc('Create user with SHA password') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -378,15 +356,14 @@ App::post('/v1/users/sha') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->inject('queueForEvents') - ->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { + ->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { $options = '{}'; if (!empty($passwordVersion)) { $options = '{"version":"' . $passwordVersion . '"}'; } - $user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); + $user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -396,7 +373,6 @@ App::post('/v1/users/sha') App::post('/v1/users/phpass') ->desc('Create user with PHPass password') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -421,9 +397,8 @@ App::post('/v1/users/phpass') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->inject('queueForEvents') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { - $user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + $user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -433,7 +408,6 @@ App::post('/v1/users/phpass') App::post('/v1/users/scrypt') ->desc('Create user with Scrypt password') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -463,8 +437,7 @@ App::post('/v1/users/scrypt') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->inject('queueForEvents') - ->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { + ->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { $options = [ 'salt' => $passwordSalt, 'costCpu' => $passwordCpu, @@ -473,7 +446,7 @@ App::post('/v1/users/scrypt') 'length' => $passwordLength ]; - $user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); + $user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -483,7 +456,6 @@ App::post('/v1/users/scrypt') App::post('/v1/users/scrypt-modified') ->desc('Create user with Scrypt modified password') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -511,9 +483,8 @@ App::post('/v1/users/scrypt-modified') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->inject('queueForEvents') - ->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { - $user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); + ->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + $user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 5cbaac4f34..30238281fb 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -29,6 +29,7 @@ use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\Role; use Utopia\Database\Validator\Authorization; +use Utopia\Queue\Broker\Pool as BrokerPool; use Utopia\Queue\Publisher; use Utopia\System\System; use Utopia\Telemetry\Adapter as Telemetry; @@ -60,6 +61,44 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar return $label; }; +/** + * This single-event based on a `Database::EVENT_DOCUMENT_CREATE` listener may look odd, but it is **intentional**. + * + * Accounts can be created in many ways beyond `createAccount` + * (anonymous, OAuth, phone, etc.), and those flows are probably not covered in event tests; so we handle this here. + */ +$eventDatabaseListener = function (Document $project, Document $document, Response $response, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime) { + // Only trigger events for user creation with the database listener. + if ($document->getCollection() !== 'users') { + return; + } + + $queueForEvents + ->setEvent('users.[userId].create') + ->setParam('userId', $document->getId()) + ->setPayload($response->output($document, Response::MODEL_USER)); + + // Trigger functions, webhooks, and realtime events + $queueForFunctions + ->from($queueForEvents) + ->trigger(); + + + /** Trigger webhooks events only if a project has them enabled */ + if (!empty($project->getAttribute('webhooks'))) { + $queueForWebhooks + ->from($queueForEvents) + ->trigger(); + } + + /** Trigger realtime events only for non console events */ + if ($queueForEvents->getProject()->getId() !== 'console') { + $queueForRealtime + ->from($queueForEvents) + ->trigger(); + } +}; + $usageDatabaseListener = function (string $event, Document $document, StatsUsage $queueForStatsUsage) { $value = 1; @@ -376,6 +415,7 @@ App::init() ->inject('project') ->inject('user') ->inject('publisher') + ->inject('publisherFunctions') ->inject('queueForEvents') ->inject('queueForMessaging') ->inject('queueForAudits') @@ -391,7 +431,7 @@ App::init() ->inject('plan') ->inject('devKey') ->inject('telemetry') - ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry) use ($usageDatabaseListener) { + ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, BrokerPool $publisherFunctions, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry) use ($usageDatabaseListener, $eventDatabaseListener) { $route = $utopia->getRoute(); @@ -500,12 +540,28 @@ App::init() $queueForBuilds->setProject($project); $queueForMessaging->setProject($project); + // Clone the queues, to prevent events triggered by the database listener + // from overwriting the events that are supposed to be triggered in the shutdown hook. + $queueForEventsClone = new Event($publisher); + $queueForFunctions = new Func($publisherFunctions); + $queueForWebhooks = new Webhook($publisher); + $queueForRealtime = new Realtime(); + $dbForProject ->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage)) ->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage)) ->on(Database::EVENT_DOCUMENTS_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage)) ->on(Database::EVENT_DOCUMENTS_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage)) - ->on(Database::EVENT_DOCUMENTS_UPSERT, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage)); + ->on(Database::EVENT_DOCUMENTS_UPSERT, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage)) + ->on(Database::EVENT_DOCUMENT_CREATE, 'create-trigger-events', fn ($event, $document) => $eventDatabaseListener( + $project, + $document, + $response, + $queueForEventsClone->from($queueForEvents), + $queueForFunctions->from($queueForEvents), + $queueForWebhooks->from($queueForEvents), + $queueForRealtime->from($queueForEvents) + )); $useCache = $route->getLabel('cache', false); $storageCacheOperationsCounter = $telemetry->createCounter('storage.cache.operations.load'); diff --git a/composer.lock b/composer.lock index b7e9a76088..3b0d7e724a 100644 --- a/composer.lock +++ b/composer.lock @@ -3557,16 +3557,16 @@ }, { "name": "utopia-php/database", - "version": "1.0.3", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "6284aaa2726afdf837bb7aac57747831e21c904d" + "reference": "670e8efe7fb91f0fe43570caa5db97a1a5223357" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/6284aaa2726afdf837bb7aac57747831e21c904d", - "reference": "6284aaa2726afdf837bb7aac57747831e21c904d", + "url": "https://api.github.com/repos/utopia-php/database/zipball/670e8efe7fb91f0fe43570caa5db97a1a5223357", + "reference": "670e8efe7fb91f0fe43570caa5db97a1a5223357", "shasum": "" }, "require": { @@ -3607,9 +3607,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/1.0.3" + "source": "https://github.com/utopia-php/database/tree/1.1.0" }, - "time": "2025-08-20T07:39:30+00:00" + "time": "2025-08-21T15:37:11+00:00" }, { "name": "utopia-php/detector", From 4b6d25259f8bdbfc92ae463715c88573618ade85 Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 22 Aug 2025 11:49:54 +0530 Subject: [PATCH 5/5] update: comment. --- app/controllers/shared/api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 30238281fb..8fe9e747b2 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -62,7 +62,7 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar }; /** - * This single-event based on a `Database::EVENT_DOCUMENT_CREATE` listener may look odd, but it is **intentional**. + * This isolated event handling for `users.*.create` which is based on a `Database::EVENT_DOCUMENT_CREATE` listener may look odd, but it is **intentional**. * * Accounts can be created in many ways beyond `createAccount` * (anonymous, OAuth, phone, etc.), and those flows are probably not covered in event tests; so we handle this here.