refactor: integrate EventProcessor for handling function and webhook events; streamline event triggering in database actions

This commit is contained in:
shimon
2026-01-07 16:57:57 +02:00
parent 0582cdf394
commit db4dcd164e
14 changed files with 387 additions and 331 deletions
+106
View File
@@ -0,0 +1,106 @@
<?php
namespace Appwrite\Functions;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
class EventProcessor
{
/**
* Get function events for a project, using Redis cache
* @param Document|null $project
* @param Database $dbForProject
* @return array<string, bool>
*/
public function getFunctionsEvents(?Document $project, Database $dbForProject): array
{
if ($project === null ||
$project->isEmpty() ||
$project->getId() === 'console') {
return [];
}
$hostname = $dbForProject->getAdapter()->getHostname();
$cacheKey = \sprintf(
'%s-cache-%s:%s:%s:project:%s:functions:events',
$dbForProject->getCacheName(),
$hostname ?? '',
$dbForProject->getNamespace(),
$dbForProject->getTenant(),
$project->getId()
);
$ttl = 3600; // 1 hour cache TTL
$cachedFunctionEvents = $dbForProject->getCache()->load($cacheKey, $ttl);
if ($cachedFunctionEvents !== false) {
return \json_decode($cachedFunctionEvents, true) ?? [];
}
try {
$events = [];
$limit = 100;
$sum = 100;
$offset = 0;
while ($sum >= $limit) {
$functions = $dbForProject->find('functions', [
Query::select(['$id', 'events']),
Query::limit($limit),
Query::offset($offset),
Query::orderAsc('$sequence'),
]);
$sum = \count($functions);
$offset = $offset + $limit;
foreach ($functions as $function) {
$functionEvents = $function->getAttribute('events', []);
if (!empty($functionEvents)) {
$events = array_merge($events, $functionEvents);
}
}
}
$uniqueEvents = \array_flip(\array_unique($events));
$dbForProject->getCache()->save($cacheKey, \json_encode($uniqueEvents));
return $uniqueEvents;
} catch (\Throwable $e) {
return [];
}
}
/**
* Get webhook events for a project from the project's webhooks attribute
* @param Document|null $project
* @return array<string, bool>
*/
public function getWebhooksEvents(?Document $project): array
{
if ($project === null || $project->isEmpty() || $project->getId() === 'console') {
return [];
}
$webhooks = $project->getAttribute('webhooks', []);
if (empty($webhooks)) {
return [];
}
$events = [];
foreach ($webhooks as $webhook) {
if ($webhook->getAttribute('enabled', false) !== true) {
continue;
}
$webhookEvents = $webhook->getAttribute('events', []);
if (!empty($webhookEvents)) {
$events = array_merge($events, $webhookEvents);
}
}
return \array_flip(\array_unique($events));
}
}
@@ -7,7 +7,6 @@ use Appwrite\Platform\Action as AppwriteAction;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Operator;
use Utopia\Database\Query;
class Action extends AppwriteAction
{
@@ -96,100 +95,4 @@ class Action extends AppwriteAction
return $data;
}
/**
* Get function events for a project, using Redis cache
* @param Document|null $project
* @param Database $dbForProject
* @return array<string, bool>
*/
protected function getFunctionsEvents(?Document $project, Database $dbForProject): array
{
if ($project === null ||
$project->isEmpty() ||
$project->getId() === 'console') {
return [];
}
$hostname = $dbForProject->getAdapter()->getHostname();
$cacheKey = \sprintf(
'%s-cache-%s:%s:%s:project:%s:functions:events',
$dbForProject->getCacheName(),
$hostname ?? '',
$dbForProject->getNamespace(),
$dbForProject->getTenant(),
$project->getId()
);
$ttl = 3600; // 1 hour cache TTL
$cachedFunctionEvents = $dbForProject->getCache()->load($cacheKey, $ttl);
if ($cachedFunctionEvents !== false) {
return \json_decode($cachedFunctionEvents, true) ?? [];
}
try {
$events = [];
$limit = 100;
$sum = 100;
$offset = 0;
while ($sum >= $limit) {
$functions = $dbForProject->find('functions', [
Query::select(['$id', 'events']),
Query::limit($limit),
Query::offset($offset),
Query::orderAsc('$sequence'),
]);
$sum = \count($functions);
$offset = $offset + $limit;
foreach ($functions as $function) {
$functionEvents = $function->getAttribute('events', []);
if (!empty($functionEvents)) {
$events = array_merge($events, $functionEvents);
}
}
}
$uniqueEvents = \array_flip(\array_unique($events));
$dbForProject->getCache()->save($cacheKey, \json_encode($uniqueEvents));
return $uniqueEvents;
} catch (\Throwable $e) {
return [];
}
}
/**
* Get webhook events for a project from the project's webhooks attribute
* @param Document|null $project
* @return array<string, bool>
*/
protected function getWebhooksEvents(?Document $project): array
{
if ($project === null || $project->isEmpty() || $project->getId() === 'console') {
return [];
}
$webhooks = $project->getAttribute('webhooks', []);
if (empty($webhooks)) {
return [];
}
$events = [];
foreach ($webhooks as $webhook) {
if ($webhook->getAttribute('enabled', false) !== true) {
continue;
}
$webhookEvents = $webhook->getAttribute('events', []);
if (!empty($webhookEvents)) {
$events = array_merge($events, $webhookEvents);
}
}
return \array_flip(\array_unique($events));
}
}
@@ -4,6 +4,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documen
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Functions\EventProcessor;
use Appwrite\Platform\Modules\Databases\Http\Databases\Action as DatabasesAction;
use Utopia\Database\Database;
use Utopia\Database\Document;
@@ -349,6 +350,7 @@ abstract class Action extends DatabasesAction
* @param Event $queueForFunctions
* @param Event $queueForWebhooks
* @param Database $dbForProject
* @param EventProcessor $eventProcessor
* @return void
*/
protected function triggerBulk(
@@ -360,7 +362,8 @@ abstract class Action extends DatabasesAction
Event $queueForRealtime,
Event $queueForFunctions,
Event $queueForWebhooks,
Database $dbForProject
Database $dbForProject,
EventProcessor $eventProcessor
): void {
$queueForEvents
->setEvent($event)
@@ -372,8 +375,8 @@ abstract class Action extends DatabasesAction
// Get project and function events (cached)
$project = $queueForEvents->getProject();
$functionsEvents = $this->getFunctionsEvents($project, $dbForProject);
$webhooksEvents = $this->getWebhooksEvents($project);
$functionsEvents = $eventProcessor->getFunctionsEvents($project, $dbForProject);
$webhooksEvents = $eventProcessor->getWebhooksEvents($project);
foreach ($documents as $document) {
$queueForEvents
@@ -391,6 +394,7 @@ abstract class Action extends DatabasesAction
$queueForEvents->getParams()
);
if (!empty($functionsEvents)) {
foreach ($generatedEvents as $event) {
if (isset($functionsEvents[$event])) {
@@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documen
use Appwrite\Event\Event;
use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception;
use Appwrite\Functions\EventProcessor;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
@@ -81,10 +82,11 @@ class Delete extends Action
->inject('queueForFunctions')
->inject('queueForWebhooks')
->inject('plan')
->inject('eventProcessor')
->callback($this->action(...));
}
public function action(string $databaseId, string $collectionId, array $queries, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void
public function action(string $databaseId, string $collectionId, array $queries, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan, EventProcessor $eventProcessor): void
{
$database = $dbForProject->getDocument('databases', $databaseId);
if ($database->isEmpty()) {
@@ -204,7 +206,8 @@ class Delete extends Action
$queueForRealtime,
$queueForFunctions,
$queueForWebhooks,
$dbForProject
$dbForProject,
$eventProcessor
);
}
}
@@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documen
use Appwrite\Event\Event;
use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception;
use Appwrite\Functions\EventProcessor;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
@@ -85,10 +86,11 @@ class Update extends Action
->inject('queueForFunctions')
->inject('queueForWebhooks')
->inject('plan')
->inject('eventProcessor')
->callback($this->action(...));
}
public function action(string $databaseId, string $collectionId, string|array $data, array $queries, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void
public function action(string $databaseId, string $collectionId, string|array $data, array $queries, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan, EventProcessor $eventProcessor): void
{
$data = \is_string($data)
? \json_decode($data, true)
@@ -235,7 +237,8 @@ class Update extends Action
$queueForRealtime,
$queueForFunctions,
$queueForWebhooks,
$dbForProject
$dbForProject,
$eventProcessor
);
}
}
@@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documen
use Appwrite\Event\Event;
use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception;
use Appwrite\Functions\EventProcessor;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
@@ -83,10 +84,11 @@ class Upsert extends Action
->inject('queueForFunctions')
->inject('queueForWebhooks')
->inject('plan')
->inject('eventProcessor')
->callback($this->action(...));
}
public function action(string $databaseId, string $collectionId, array $documents, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void
public function action(string $databaseId, string $collectionId, array $documents, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan, EventProcessor $eventProcessor): void
{
$database = $dbForProject->getDocument('databases', $databaseId);
if ($database->isEmpty()) {
@@ -210,7 +212,8 @@ class Upsert extends Action
$queueForRealtime,
$queueForFunctions,
$queueForWebhooks,
$dbForProject
$dbForProject,
$eventProcessor
);
}
}
@@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documen
use Appwrite\Event\Event;
use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception;
use Appwrite\Functions\EventProcessor;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Deprecated;
@@ -132,9 +133,10 @@ class Create extends Action
->inject('queueForFunctions')
->inject('queueForWebhooks')
->inject('plan')
->inject('eventProcessor')
->callback($this->action(...));
}
public function action(string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, ?array $documents, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void
public function action(string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, ?array $documents, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan, EventProcessor $eventProcessor): void
{
$data = \is_string($data)
? \json_decode($data, true)
@@ -492,7 +494,8 @@ class Create extends Action
$queueForRealtime,
$queueForFunctions,
$queueForWebhooks,
$dbForProject
$dbForProject,
$eventProcessor
);
return;
}
@@ -7,6 +7,7 @@ use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception;
use Appwrite\Functions\EventProcessor;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
@@ -76,6 +77,7 @@ class Update extends Action
->inject('queueForRealtime')
->inject('queueForFunctions')
->inject('queueForWebhooks')
->inject('eventProcessor')
->callback($this->action(...));
}
@@ -93,6 +95,7 @@ class Update extends Action
* @param Event $queueForRealtime
* @param Event $queueForFunctions
* @param Event $queueForWebhooks
* @param EventProcessor $eventProcessor
* @return void
* @throws ConflictException
* @throws Exception
@@ -102,7 +105,7 @@ class Update extends Action
* @throws Structure
* @throws \Utopia\Exception
*/
public function action(string $transactionId, bool $commit, bool $rollback, UtopiaResponse $response, Database $dbForProject, Document $user, TransactionState $transactionState, Delete $queueForDeletes, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks): void
public function action(string $transactionId, bool $commit, bool $rollback, UtopiaResponse $response, Database $dbForProject, Document $user, TransactionState $transactionState, Delete $queueForDeletes, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, EventProcessor $eventProcessor): void
{
if (!$commit && !$rollback) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Either commit or rollback must be true');
@@ -372,8 +375,8 @@ class Update extends Action
// Get project and function/webhook events (cached)
$project = $queueForEvents->getProject();
$functionsEvents = $this->getFunctionsEvents($project, $dbForProject);
$webhooksEvents = $this->getWebhooksEvents($project);
$functionsEvents = $eventProcessor->getFunctionsEvents($project, $dbForProject);
$webhooksEvents = $eventProcessor->getWebhooksEvents($project);
foreach ($documentsToTrigger as $doc) {
$payload = $doc->getArrayCopy();
@@ -66,6 +66,7 @@ class Delete extends DocumentsDelete
->inject('queueForFunctions')
->inject('queueForWebhooks')
->inject('plan')
->inject('eventProcessor')
->callback($this->action(...));
}
}
@@ -68,6 +68,7 @@ class Update extends DocumentsUpdate
->inject('queueForFunctions')
->inject('queueForWebhooks')
->inject('plan')
->inject('eventProcessor')
->callback($this->action(...));
}
}
@@ -68,6 +68,7 @@ class Upsert extends DocumentsUpsert
->inject('queueForFunctions')
->inject('queueForWebhooks')
->inject('plan')
->inject('eventProcessor')
->callback($this->action(...));
}
}
@@ -111,6 +111,7 @@ class Create extends DocumentCreate
->inject('queueForFunctions')
->inject('queueForWebhooks')
->inject('plan')
->inject('eventProcessor')
->callback($this->action(...));
}
}