Merge pull request #9325 from appwrite/pla-1883

refactor: migrate Realtime::send calls to queueForRealtime
This commit is contained in:
Jake Barnby 2025-03-03 21:23:55 +13:00 committed by GitHub
commit d85b28aabc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 271 additions and 308 deletions

View file

@ -6,13 +6,14 @@ use Appwrite\Event\Build;
use Appwrite\Event\Delete; use Appwrite\Event\Delete;
use Appwrite\Event\Event; use Appwrite\Event\Event;
use Appwrite\Event\Func; use Appwrite\Event\Func;
use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage; use Appwrite\Event\StatsUsage;
use Appwrite\Event\Validator\FunctionEvent; use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception; use Appwrite\Extend\Exception;
use Appwrite\Extend\Exception as AppwriteException; use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Functions\Validator\Headers; use Appwrite\Functions\Validator\Headers;
use Appwrite\Functions\Validator\RuntimeSpecification; use Appwrite\Functions\Validator\RuntimeSpecification;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Platform\Tasks\ScheduleExecutions; use Appwrite\Platform\Tasks\ScheduleExecutions;
use Appwrite\SDK\AuthType; use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType; use Appwrite\SDK\ContentType;
@ -194,9 +195,12 @@ App::post('/v1/functions')
->inject('user') ->inject('user')
->inject('queueForEvents') ->inject('queueForEvents')
->inject('queueForBuilds') ->inject('queueForBuilds')
->inject('queueForWebhooks')
->inject('queueForFunctions')
->inject('queueForRealtime')
->inject('dbForPlatform') ->inject('dbForPlatform')
->inject('gitHub') ->inject('gitHub')
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateVersion, string $specification, Request $request, Response $response, Database $dbForProject, callable $timelimit, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForPlatform, GitHub $github) use ($redeployVcs) { ->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateVersion, string $specification, Request $request, Response $response, Database $dbForProject, callable $timelimit, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Database $dbForPlatform, GitHub $github) use ($redeployVcs) {
$functionId = ($functionId == 'unique()') ? ID::unique() : $functionId; $functionId = ($functionId == 'unique()') ? ID::unique() : $functionId;
// Temporary abuse check // Temporary abuse check
@ -386,51 +390,29 @@ App::post('/v1/functions')
])) ]))
); );
/** Trigger Webhook */
$ruleModel = new Rule(); $ruleModel = new Rule();
$ruleCreate = $ruleCreate =
$queueForEvents $queueForEvents
->setClass(Event::WEBHOOK_CLASS_NAME) ->setProject($project)
->setQueue(Event::WEBHOOK_QUEUE_NAME); ->setEvent('rules.[ruleId].create')
->setParam('ruleId', $rule->getId())
->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())));
$ruleCreate /** Trigger Webhook */
->setProject($project) $queueForWebhooks
->setEvent('rules.[ruleId].create') ->from($ruleCreate)
->setParam('ruleId', $rule->getId())
->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())))
->trigger(); ->trigger();
/** Trigger Functions */ /** Trigger Functions */
$ruleCreate $queueForFunctions
->setClass(Event::FUNCTIONS_CLASS_NAME) ->from($ruleCreate)
->setQueue(Event::FUNCTIONS_QUEUE_NAME)
->trigger(); ->trigger();
/** Trigger realtime event */ /** Trigger Realtime Events */
$allEvents = Event::generateEvents('rules.[ruleId].create', [ $queueForRealtime
'ruleId' => $rule->getId(), ->from($ruleCreate)
]); ->setSubscribers(['console', $project->getId()])
->trigger();
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $rule,
project: $project
);
Realtime::send(
projectId: 'console',
payload: $rule->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
Realtime::send(
projectId: $project->getId(),
payload: $rule->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
} }
$queueForEvents->setParam('functionId', $function->getId()); $queueForEvents->setParam('functionId', $function->getId());

View file

@ -13,12 +13,14 @@ use Appwrite\Event\Func;
use Appwrite\Event\Mail; use Appwrite\Event\Mail;
use Appwrite\Event\Messaging; use Appwrite\Event\Messaging;
use Appwrite\Event\Migration; use Appwrite\Event\Migration;
use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage; use Appwrite\Event\StatsUsage;
use Appwrite\Event\StatsUsageDump; use Appwrite\Event\StatsUsageDump;
/** remove */ /** remove */
use Appwrite\Event\Usage; use Appwrite\Event\Usage;
use Appwrite\Event\UsageDump; use Appwrite\Event\UsageDump;
/** /remove */ /** /remove */
use Appwrite\Event\Webhook;
use Appwrite\Platform\Appwrite; use Appwrite\Platform\Appwrite;
use Swoole\Runtime; use Swoole\Runtime;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis; use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
@ -313,10 +315,18 @@ Server::setResource('queueForAudits', function (Publisher $publisher) {
return new Audit($publisher); return new Audit($publisher);
}, ['publisher']); }, ['publisher']);
Server::setResource('queueForWebhooks', function (Publisher $publisher) {
return new Webhook($publisher);
}, ['publisher']);
Server::setResource('queueForFunctions', function (Publisher $publisher) { Server::setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher); return new Func($publisher);
}, ['publisher']); }, ['publisher']);
Server::setResource('queueForRealtime', function () {
return new Realtime();
}, []);
Server::setResource('queueForCertificates', function (Publisher $publisher) { Server::setResource('queueForCertificates', function (Publisher $publisher) {
return new Certificate($publisher); return new Certificate($publisher);
}, ['publisher']); }, ['publisher']);

View file

@ -7,10 +7,17 @@ use Utopia\Database\Document;
class Realtime extends Event class Realtime extends Event
{ {
protected array $subscribers = [];
public function __construct() public function __construct()
{ {
} }
/**
* Get Realtime payload for this event.
*
* @return array
*/
public function getRealtimePayload(): array public function getRealtimePayload(): array
{ {
$payload = []; $payload = [];
@ -24,6 +31,28 @@ class Realtime extends Event
return $payload; return $payload;
} }
/**
* Set subscribers for this realtime event.
*
* @param array $subscribers
* @return array
*/
public function setSubscribers(array $subscribers): self
{
$this->subscribers = $subscribers;
return $this;
}
/**
* Get subscribers for this realtime event.
*
* @return array
*/
public function getSubscribers(): array
{
return $this->subscribers;
}
/** /**
* Execute Event. * Execute Event.
* *
@ -53,17 +82,23 @@ class Realtime extends Event
bucket: $bucket, bucket: $bucket,
); );
RealtimeAdapter::send( $projectIds = !empty($this->getSubscribers())
projectId: $target['projectId'] ?? $this->getProject()->getId(), ? $this->getSubscribers()
payload: $this->getRealtimePayload(), : [$target['projectId'] ?? $this->getProject()->getId()];
events: $allEvents,
channels: $target['channels'], foreach ($projectIds as $projectId) {
roles: $target['roles'], RealtimeAdapter::send(
options: [ projectId: $projectId,
'permissionsChanged' => $target['permissionsChanged'], payload: $this->getRealtimePayload(),
'userId' => $this->getParam('userId') events: $allEvents,
] channels: $target['channels'],
); roles: $target['roles'],
options: [
'permissionsChanged' => $target['permissionsChanged'],
'userId' => $this->getParam('userId')
]
);
}
return true; return true;
} }

View file

@ -5,8 +5,9 @@ namespace Appwrite\Platform\Workers;
use Ahc\Jwt\JWT; use Ahc\Jwt\JWT;
use Appwrite\Event\Event; use Appwrite\Event\Event;
use Appwrite\Event\Func; use Appwrite\Event\Func;
use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage; use Appwrite\Event\StatsUsage;
use Appwrite\Messaging\Adapter\Realtime; use Appwrite\Event\Webhook;
use Appwrite\Utopia\Response\Model\Deployment; use Appwrite\Utopia\Response\Model\Deployment;
use Appwrite\Vcs\Comment; use Appwrite\Vcs\Comment;
use Exception; use Exception;
@ -49,15 +50,17 @@ class Builds extends Action
->inject('project') ->inject('project')
->inject('dbForPlatform') ->inject('dbForPlatform')
->inject('queueForEvents') ->inject('queueForEvents')
->inject('queueForWebhooks')
->inject('queueForFunctions') ->inject('queueForFunctions')
->inject('queueForRealtime')
->inject('queueForStatsUsage') ->inject('queueForStatsUsage')
->inject('cache') ->inject('cache')
->inject('dbForProject') ->inject('dbForProject')
->inject('deviceForFunctions') ->inject('deviceForFunctions')
->inject('isResourceBlocked') ->inject('isResourceBlocked')
->inject('log') ->inject('log')
->callback(fn ($message, Document $project, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions, StatsUsage $usage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log) => ->callback(fn ($message, Document $project, Database $dbForPlatform, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, StatsUsage $usage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log) =>
$this->action($message, $project, $dbForPlatform, $queueForEvents, $queueForFunctions, $usage, $cache, $dbForProject, $deviceForFunctions, $isResourceBlocked, $log)); $this->action($message, $project, $dbForPlatform, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $usage, $cache, $dbForProject, $deviceForFunctions, $isResourceBlocked, $log));
} }
/** /**
@ -65,7 +68,9 @@ class Builds extends Action
* @param Document $project * @param Document $project
* @param Database $dbForPlatform * @param Database $dbForPlatform
* @param Event $queueForEvents * @param Event $queueForEvents
* @param Webhook $queueForWebhooks
* @param Func $queueForFunctions * @param Func $queueForFunctions
* @param Realtime $queueForRealtime
* @param StatsUsage $queueForStatsUsage * @param StatsUsage $queueForStatsUsage
* @param Cache $cache * @param Cache $cache
* @param Database $dbForProject * @param Database $dbForProject
@ -74,7 +79,7 @@ class Builds extends Action
* @return void * @return void
* @throws \Utopia\Database\Exception * @throws \Utopia\Database\Exception
*/ */
public function action(Message $message, Document $project, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions, StatsUsage $queueForStatsUsage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log): void public function action(Message $message, Document $project, Database $dbForPlatform, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, StatsUsage $queueForStatsUsage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, callable $isResourceBlocked, Log $log): void
{ {
$payload = $message->getPayload() ?? []; $payload = $message->getPayload() ?? [];
@ -95,7 +100,7 @@ class Builds extends Action
case BUILD_TYPE_RETRY: case BUILD_TYPE_RETRY:
Console::info('Creating build for deployment: ' . $deployment->getId()); Console::info('Creating build for deployment: ' . $deployment->getId());
$github = new GitHub($cache); $github = new GitHub($cache);
$this->buildDeployment($deviceForFunctions, $queueForFunctions, $queueForEvents, $queueForStatsUsage, $dbForPlatform, $dbForProject, $github, $project, $resource, $deployment, $template, $isResourceBlocked, $log); $this->buildDeployment($deviceForFunctions, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $queueForEvents, $queueForStatsUsage, $dbForPlatform, $dbForProject, $github, $project, $resource, $deployment, $template, $isResourceBlocked, $log);
break; break;
default: default:
@ -105,7 +110,9 @@ class Builds extends Action
/** /**
* @param Device $deviceForFunctions * @param Device $deviceForFunctions
* @param Webhook $queueForWebhooks
* @param Func $queueForFunctions * @param Func $queueForFunctions
* @param Realtime $queueForRealtime
* @param Event $queueForEvents * @param Event $queueForEvents
* @param StatsUsage $queueForStatsUsage * @param StatsUsage $queueForStatsUsage
* @param Database $dbForPlatform * @param Database $dbForPlatform
@ -120,7 +127,7 @@ class Builds extends Action
* @throws \Utopia\Database\Exception * @throws \Utopia\Database\Exception
* @throws Exception * @throws Exception
*/ */
protected function buildDeployment(Device $deviceForFunctions, Func $queueForFunctions, Event $queueForEvents, StatsUsage $queueForStatsUsage, Database $dbForPlatform, Database $dbForProject, GitHub $github, Document $project, Document $function, Document $deployment, Document $template, callable $isResourceBlocked, Log $log): void protected function buildDeployment(Device $deviceForFunctions, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Event $queueForEvents, StatsUsage $queueForStatsUsage, Database $dbForPlatform, Database $dbForProject, GitHub $github, Document $project, Document $function, Document $deployment, Document $template, callable $isResourceBlocked, Log $log): void
{ {
$executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST')); $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST'));
@ -158,10 +165,7 @@ class Builds extends Action
} }
// Realtime preparation // Realtime preparation
$allEvents = Event::generateEvents('functions.[functionId].deployments.[deploymentId].update', [ $event = "functions.[functionId].deployments.[deploymentId].update";
'functionId' => $function->getId(),
'deploymentId' => $deployment->getId()
]);
$startTime = DateTime::now(); $startTime = DateTime::now();
$durationStart = \microtime(true); $durationStart = \microtime(true);
@ -375,21 +379,16 @@ class Builds extends Action
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment); $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
/** /**
* Send realtime Event * Trigger Realtime Event
*/ */
$target = Realtime::fromPayload( $queueForRealtime
// Pass first, most verbose event pattern ->setProject($project)
event: $allEvents[0], ->setSubscribers(['console'])
payload: $build, ->setEvent($event)
project: $project ->setParam('functionId', $function->getId())
); ->setParam('deploymentId', $deployment->getId())
Realtime::send( ->setPayload($build->getArrayCopy())
projectId: 'console', ->trigger();
payload: $build->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
} }
$tmpPath = '/tmp/builds/' . $buildId; $tmpPath = '/tmp/builds/' . $buildId;
@ -436,40 +435,34 @@ class Builds extends Action
$this->runGitAction('building', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForPlatform); $this->runGitAction('building', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForPlatform);
} }
/** Trigger Webhook */
$deploymentModel = new Deployment(); $deploymentModel = new Deployment();
$deploymentUpdate = $deploymentUpdate =
$queueForEvents $queueForEvents
->setQueue(Event::WEBHOOK_QUEUE_NAME)
->setClass(Event::WEBHOOK_CLASS_NAME)
->setProject($project) ->setProject($project)
->setEvent('functions.[functionId].deployments.[deploymentId].update') ->setEvent('functions.[functionId].deployments.[deploymentId].update')
->setParam('functionId', $function->getId()) ->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId()) ->setParam('deploymentId', $deployment->getId())
->setPayload($deployment->getArrayCopy(array_keys($deploymentModel->getRules()))); ->setPayload($deployment->getArrayCopy(array_keys($deploymentModel->getRules())));
$deploymentUpdate->trigger(); /** Trigger Webhook */
$queueForWebhooks
->from($deploymentUpdate)
->trigger();
/** Trigger Functions */ /** Trigger Functions */
$queueForFunctions $queueForFunctions
->from($deploymentUpdate) ->from($deploymentUpdate)
->trigger(); ->trigger();
/** Trigger Realtime */ /** Trigger Realtime Event */
$target = Realtime::fromPayload( $queueForRealtime
// Pass first, most verbose event pattern ->setProject($project)
event: $allEvents[0], ->setSubscribers(['console'])
payload: $build, ->setEvent($event)
project: $project ->setParam('functionId', $function->getId())
); ->setParam('deploymentId', $deployment->getId())
->setPayload($build->getArrayCopy())
Realtime::send( ->trigger();
projectId: 'console',
payload: $build->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
$vars = []; $vars = [];
@ -562,12 +555,12 @@ class Builds extends Action
$err = $error; $err = $error;
} }
}), }),
Co\go(function () use ($executor, $project, $deployment, &$response, &$build, $dbForProject, $allEvents, &$err, &$isCanceled) { Co\go(function () use ($executor, $project, $function, $deployment, &$response, &$build, $dbForProject, $event, &$err, $queueForRealtime, &$isCanceled) {
try { try {
$executor->getLogs( $executor->getLogs(
deploymentId: $deployment->getId(), deploymentId: $deployment->getId(),
projectId: $project->getId(), projectId: $project->getId(),
callback: function ($logs) use (&$response, &$err, &$build, $dbForProject, $allEvents, $project, &$isCanceled) { callback: function ($logs) use (&$response, &$err, &$build, $dbForProject, $event, $project, $function, $deployment, $queueForRealtime, &$isCanceled) {
if ($isCanceled) { if ($isCanceled) {
return; return;
} }
@ -592,21 +585,16 @@ class Builds extends Action
$build = $dbForProject->updateDocument('builds', $build->getId(), $build); $build = $dbForProject->updateDocument('builds', $build->getId(), $build);
/** /**
* Send realtime Event * Trigger Realtime Event
*/ */
$target = Realtime::fromPayload( $queueForRealtime
// Pass first, most verbose event pattern ->setProject($project)
event: $allEvents[0], ->setSubscribers(['console'])
payload: $build, ->setEvent($event)
project: $project ->setParam('functionId', $function->getId())
); ->setParam('deploymentId', $deployment->getId())
Realtime::send( ->setPayload($build->getArrayCopy())
projectId: 'console', ->trigger();
payload: $build->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
} }
} }
); );
@ -694,21 +682,16 @@ class Builds extends Action
} }
} finally { } finally {
/** /**
* Send realtime Event * Trigger Realtime Event
*/ */
$target = Realtime::fromPayload( $queueForRealtime
// Pass first, most verbose event pattern ->setProject($project)
event: $allEvents[0], ->setSubscribers(['console'])
payload: $build, ->setEvent($event)
project: $project ->setParam('functionId', $function->getId())
); ->setParam('deploymentId', $deployment->getId())
Realtime::send( ->setPayload($build->getArrayCopy())
projectId: 'console', ->trigger();
payload: $build->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
/** Trigger usage queue */ /** Trigger usage queue */
if ($build->getAttribute('status') === 'ready') { if ($build->getAttribute('status') === 'ready') {

View file

@ -6,7 +6,8 @@ use Appwrite\Certificates\Adapter as CertificatesAdapter;
use Appwrite\Event\Event; use Appwrite\Event\Event;
use Appwrite\Event\Func; use Appwrite\Event\Func;
use Appwrite\Event\Mail; use Appwrite\Event\Mail;
use Appwrite\Messaging\Adapter\Realtime; use Appwrite\Event\Realtime;
use Appwrite\Event\Webhook;
use Appwrite\Network\Validator\CNAME; use Appwrite\Network\Validator\CNAME;
use Appwrite\Template\Template; use Appwrite\Template\Template;
use Appwrite\Utopia\Response\Model\Rule; use Appwrite\Utopia\Response\Model\Rule;
@ -46,12 +47,14 @@ class Certificates extends Action
->inject('dbForPlatform') ->inject('dbForPlatform')
->inject('queueForMails') ->inject('queueForMails')
->inject('queueForEvents') ->inject('queueForEvents')
->inject('queueForWebhooks')
->inject('queueForFunctions') ->inject('queueForFunctions')
->inject('queueForRealtime')
->inject('log') ->inject('log')
->inject('certificates') ->inject('certificates')
->callback( ->callback(
fn (Message $message, Database $dbForPlatform, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, Log $log, CertificatesAdapter $certificates) => fn (Message $message, Database $dbForPlatform, Mail $queueForMails, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Log $log, CertificatesAdapter $certificates) =>
$this->action($message, $dbForPlatform, $queueForMails, $queueForEvents, $queueForFunctions, $log, $certificates) $this->action($message, $dbForPlatform, $queueForMails, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $log, $certificates)
); );
} }
@ -60,14 +63,16 @@ class Certificates extends Action
* @param Database $dbForPlatform * @param Database $dbForPlatform
* @param Mail $queueForMails * @param Mail $queueForMails
* @param Event $queueForEvents * @param Event $queueForEvents
* @param Webhook $queueForWebhooks
* @param Func $queueForFunctions * @param Func $queueForFunctions
* @param Realtime $queueForRealtime
* @param Log $log * @param Log $log
* @param CertificatesAdapter $certificates * @param CertificatesAdapter $certificates
* @return void * @return void
* @throws Throwable * @throws Throwable
* @throws \Utopia\Database\Exception * @throws \Utopia\Database\Exception
*/ */
public function action(Message $message, Database $dbForPlatform, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, Log $log, CertificatesAdapter $certificates): void public function action(Message $message, Database $dbForPlatform, Mail $queueForMails, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Log $log, CertificatesAdapter $certificates): void
{ {
$payload = $message->getPayload() ?? []; $payload = $message->getPayload() ?? [];
@ -81,7 +86,7 @@ class Certificates extends Action
$log->addTag('domain', $domain->get()); $log->addTag('domain', $domain->get());
$this->execute($domain, $dbForPlatform, $queueForMails, $queueForEvents, $queueForFunctions, $log, $certificates, $skipRenewCheck); $this->execute($domain, $dbForPlatform, $queueForMails, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $log, $certificates, $skipRenewCheck);
} }
/** /**
@ -90,13 +95,14 @@ class Certificates extends Action
* @param Mail $queueForMails * @param Mail $queueForMails
* @param Event $queueForEvents * @param Event $queueForEvents
* @param Func $queueForFunctions * @param Func $queueForFunctions
* @param Realtime $queueForRealtime
* @param CertificatesAdapter $certificates * @param CertificatesAdapter $certificates
* @param bool $skipRenewCheck * @param bool $skipRenewCheck
* @return void * @return void
* @throws Throwable * @throws Throwable
* @throws \Utopia\Database\Exception * @throws \Utopia\Database\Exception
*/ */
private function execute(Domain $domain, Database $dbForPlatform, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, Log $log, CertificatesAdapter $certificates, bool $skipRenewCheck = false): void private function execute(Domain $domain, Database $dbForPlatform, Mail $queueForMails, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Log $log, CertificatesAdapter $certificates, bool $skipRenewCheck = false): void
{ {
/** /**
* 1. Read arguments and validate domain * 1. Read arguments and validate domain
@ -186,7 +192,7 @@ class Certificates extends Action
$certificate->setAttribute('updated', DateTime::now()); $certificate->setAttribute('updated', DateTime::now());
// Save all changes we made to certificate document into database // Save all changes we made to certificate document into database
$this->saveCertificateDocument($domain->get(), $certificate, $success, $dbForPlatform, $queueForEvents, $queueForFunctions); $this->saveCertificateDocument($domain->get(), $certificate, $success, $dbForPlatform, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime);
} }
} }
@ -199,13 +205,14 @@ class Certificates extends Action
* @param Database $dbForPlatform Database connection for console * @param Database $dbForPlatform Database connection for console
* @param Event $queueForEvents * @param Event $queueForEvents
* @param Func $queueForFunctions * @param Func $queueForFunctions
* @param Realtime $queueForRealtime
* @return void * @return void
* @throws \Utopia\Database\Exception * @throws \Utopia\Database\Exception
* @throws Authorization * @throws Authorization
* @throws Conflict * @throws Conflict
* @throws Structure * @throws Structure
*/ */
private function saveCertificateDocument(string $domain, Document $certificate, bool $success, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions): void private function saveCertificateDocument(string $domain, Document $certificate, bool $success, Database $dbForPlatform, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime): void
{ {
// Check if update or insert required // Check if update or insert required
$certificateDocument = $dbForPlatform->findOne('certificates', [Query::equal('domain', [$domain])]); $certificateDocument = $dbForPlatform->findOne('certificates', [Query::equal('domain', [$domain])]);
@ -219,7 +226,7 @@ class Certificates extends Action
} }
$certificateId = $certificate->getId(); $certificateId = $certificate->getId();
$this->updateDomainDocuments($certificateId, $domain, $success, $dbForPlatform, $queueForEvents, $queueForFunctions); $this->updateDomainDocuments($certificateId, $domain, $success, $dbForPlatform, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime);
} }
/** /**
@ -338,7 +345,7 @@ class Certificates extends Action
* *
* @return void * @return void
*/ */
private function updateDomainDocuments(string $certificateId, string $domain, bool $success, Database $dbForPlatform, Event $queueForEvents, Func $queueForFunctions): void private function updateDomainDocuments(string $certificateId, string $domain, bool $success, Database $dbForPlatform, Event $queueForEvents, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime): void
{ {
// TODO: @christyjacob remove once we migrate the rules in 1.7.x // TODO: @christyjacob remove once we migrate the rules in 1.7.x
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') { if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
@ -367,50 +374,28 @@ class Certificates extends Action
return; return;
} }
/** Trigger Webhook */
$ruleModel = new Rule(); $ruleModel = new Rule();
$queueForEvents $queueForEvents
->setQueue(Event::WEBHOOK_QUEUE_NAME)
->setClass(Event::WEBHOOK_CLASS_NAME)
->setProject($project) ->setProject($project)
->setEvent('rules.[ruleId].update') ->setEvent('rules.[ruleId].update')
->setParam('ruleId', $rule->getId()) ->setParam('ruleId', $rule->getId())
->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules()))) ->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())));
->trigger();
/** Trigger Webhook */
$queueForWebhooks
->from($queueForEvents)
->trigger();
/** Trigger Functions */ /** Trigger Functions */
$queueForFunctions $queueForFunctions
->setProject($project) ->from($queueForEvents)
->setEvent('rules.[ruleId].update')
->setParam('ruleId', $rule->getId())
->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())))
->trigger(); ->trigger();
/** Trigger realtime event */ /** Trigger Realtime Events */
$allEvents = Event::generateEvents('rules.[ruleId].update', [ $queueForRealtime
'ruleId' => $rule->getId(), ->from($queueForEvents)
]); ->setSubscribers(['console', $projectId])
$target = Realtime::fromPayload( ->trigger();
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $rule,
project: $project
);
Realtime::send(
projectId: 'console',
payload: $rule->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
Realtime::send(
projectId: $project->getId(),
payload: $rule->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
} }
} }
} }

View file

@ -2,8 +2,7 @@
namespace Appwrite\Platform\Workers; namespace Appwrite\Platform\Workers;
use Appwrite\Event\Event; use Appwrite\Event\Realtime;
use Appwrite\Messaging\Adapter\Realtime;
use Exception; use Exception;
use Utopia\CLI\Console; use Utopia\CLI\Console;
use Utopia\Database\Database; use Utopia\Database\Database;
@ -37,8 +36,9 @@ class Databases extends Action
->inject('project') ->inject('project')
->inject('dbForPlatform') ->inject('dbForPlatform')
->inject('dbForProject') ->inject('dbForProject')
->inject('queueForRealtime')
->inject('log') ->inject('log')
->callback(fn (Message $message, Document $project, Database $dbForPlatform, Database $dbForProject, Log $log) => $this->action($message, $project, $dbForPlatform, $dbForProject, $log)); ->callback(fn (Message $message, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime, Log $log) => $this->action($message, $project, $dbForPlatform, $dbForProject, $queueForRealtime, $log));
} }
/** /**
@ -46,16 +46,17 @@ class Databases extends Action
* @param Document $project * @param Document $project
* @param Database $dbForPlatform * @param Database $dbForPlatform
* @param Database $dbForProject * @param Database $dbForProject
* @param Realtime $queueForRealtime
* @param Log $log * @param Log $log
* @return void * @return void
* @throws \Exception * @throws \Exception
*/ */
public function action(Message $message, Document $project, Database $dbForPlatform, Database $dbForProject, Log $log): void public function action(Message $message, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime, Log $log): void
{ {
$payload = $message->getPayload() ?? []; $payload = $message->getPayload() ?? [];
if (empty($payload)) { if (empty($payload)) {
throw new \Exception('Missing payload'); throw new Exception('Missing payload');
} }
$type = $payload['type']; $type = $payload['type'];
@ -75,11 +76,11 @@ class Databases extends Action
match (\strval($type)) { match (\strval($type)) {
DATABASE_TYPE_DELETE_DATABASE => $this->deleteDatabase($database, $project, $dbForProject), DATABASE_TYPE_DELETE_DATABASE => $this->deleteDatabase($database, $project, $dbForProject),
DATABASE_TYPE_DELETE_COLLECTION => $this->deleteCollection($database, $collection, $project, $dbForProject), DATABASE_TYPE_DELETE_COLLECTION => $this->deleteCollection($database, $collection, $project, $dbForProject),
DATABASE_TYPE_CREATE_ATTRIBUTE => $this->createAttribute($database, $collection, $document, $project, $dbForPlatform, $dbForProject), DATABASE_TYPE_CREATE_ATTRIBUTE => $this->createAttribute($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $queueForRealtime),
DATABASE_TYPE_DELETE_ATTRIBUTE => $this->deleteAttribute($database, $collection, $document, $project, $dbForPlatform, $dbForProject), DATABASE_TYPE_DELETE_ATTRIBUTE => $this->deleteAttribute($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $queueForRealtime),
DATABASE_TYPE_CREATE_INDEX => $this->createIndex($database, $collection, $document, $project, $dbForPlatform, $dbForProject), DATABASE_TYPE_CREATE_INDEX => $this->createIndex($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $queueForRealtime),
DATABASE_TYPE_DELETE_INDEX => $this->deleteIndex($database, $collection, $document, $project, $dbForPlatform, $dbForProject), DATABASE_TYPE_DELETE_INDEX => $this->deleteIndex($database, $collection, $document, $project, $dbForPlatform, $dbForProject, $queueForRealtime),
default => throw new \Exception('No database operation for type: ' . \strval($type)), default => throw new Exception('No database operation for type: ' . \strval($type)),
}; };
} }
@ -90,13 +91,14 @@ class Databases extends Action
* @param Document $project * @param Document $project
* @param Database $dbForPlatform * @param Database $dbForPlatform
* @param Database $dbForProject * @param Database $dbForProject
* @param Realtime $queueForRealtime
* @return void * @return void
* @throws Authorization * @throws Authorization
* @throws Conflict * @throws Conflict
* @throws \Exception * @throws \Exception
* @throws \Throwable * @throws \Throwable
*/ */
private function createAttribute(Document $database, Document $collection, Document $attribute, Document $project, Database $dbForPlatform, Database $dbForProject): void private function createAttribute(Document $database, Document $collection, Document $attribute, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime): void
{ {
if ($collection->isEmpty()) { if ($collection->isEmpty()) {
throw new Exception('Missing collection'); throw new Exception('Missing collection');
@ -106,12 +108,7 @@ class Databases extends Action
} }
$projectId = $project->getId(); $projectId = $project->getId();
$event = "databases.[databaseId].collections.[collectionId].attributes.[attributeId].update";
$events = Event::generateEvents('databases.[databaseId].collections.[collectionId].attributes.[attributeId].update', [
'databaseId' => $database->getId(),
'collectionId' => $collection->getId(),
'attributeId' => $attribute->getId()
]);
/** /**
* TODO @christyjacob4 verify if this is still the case * TODO @christyjacob4 verify if this is still the case
* Fetch attribute from the database, since with Resque float values are loosing informations. * Fetch attribute from the database, since with Resque float values are loosing informations.
@ -169,7 +166,7 @@ class Databases extends Action
break; break;
default: default:
if (!$dbForProject->createAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key, $type, $size, $required, $default, $signed, $array, $format, $formatOptions, $filters)) { if (!$dbForProject->createAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key, $type, $size, $required, $default, $signed, $array, $format, $formatOptions, $filters)) {
throw new \Exception('Failed to create Attribute'); throw new Exception('Failed to create Attribute');
} }
} }
@ -200,7 +197,7 @@ class Databases extends Action
throw $e; throw $e;
} finally { } finally {
$this->trigger($database, $collection, $attribute, $project, $projectId, $events); $this->trigger($database, $collection, $project, $event, $queueForRealtime, $attribute);
if (! $relatedCollection->isEmpty()) { if (! $relatedCollection->isEmpty()) {
$dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId()); $dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId());
@ -217,13 +214,14 @@ class Databases extends Action
* @param Document $project * @param Document $project
* @param Database $dbForPlatform * @param Database $dbForPlatform
* @param Database $dbForProject * @param Database $dbForProject
* @param Realtime $queueForRealtime
* @return void * @return void
* @throws Authorization * @throws Authorization
* @throws Conflict * @throws Conflict
* @throws \Exception * @throws \Exception
* @throws \Throwable * @throws \Throwable
**/ **/
private function deleteAttribute(Document $database, Document $collection, Document $attribute, Document $project, Database $dbForPlatform, Database $dbForProject): void private function deleteAttribute(Document $database, Document $collection, Document $attribute, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime): void
{ {
if ($collection->isEmpty()) { if ($collection->isEmpty()) {
throw new Exception('Missing collection'); throw new Exception('Missing collection');
@ -233,15 +231,9 @@ class Databases extends Action
} }
$projectId = $project->getId(); $projectId = $project->getId();
$event = 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].delete';
$events = Event::generateEvents('databases.[databaseId].collections.[collectionId].attributes.[attributeId].delete', [
'databaseId' => $database->getId(),
'collectionId' => $collection->getId(),
'attributeId' => $attribute->getId()
]);
$collectionId = $collection->getId(); $collectionId = $collection->getId();
$key = $attribute->getAttribute('key', ''); $key = $attribute->getAttribute('key', '');
$status = $attribute->getAttribute('status', '');
$type = $attribute->getAttribute('type', ''); $type = $attribute->getAttribute('type', '');
$project = $dbForPlatform->getDocument('projects', $projectId); $project = $dbForPlatform->getDocument('projects', $projectId);
$options = $attribute->getAttribute('options', []); $options = $attribute->getAttribute('options', []);
@ -312,7 +304,7 @@ class Databases extends Action
throw $e; throw $e;
} finally { } finally {
$this->trigger($database, $collection, $attribute, $project, $projectId, $events); $this->trigger($database, $collection, $project, $event, $queueForRealtime, $attribute);
} }
// The underlying database removes/rebuilds indexes when attribute is removed // The underlying database removes/rebuilds indexes when attribute is removed
@ -358,7 +350,7 @@ class Databases extends Action
} }
if ($exists) { // Delete the duplicate if created, else update in db if ($exists) { // Delete the duplicate if created, else update in db
$this->deleteIndex($database, $collection, $index, $project, $dbForPlatform, $dbForProject); $this->deleteIndex($database, $collection, $index, $project, $dbForPlatform, $dbForProject, $queueForRealtime);
} else { } else {
$dbForProject->updateDocument('indexes', $index->getId(), $index); $dbForProject->updateDocument('indexes', $index->getId(), $index);
} }
@ -381,6 +373,7 @@ class Databases extends Action
* @param Document $project * @param Document $project
* @param Database $dbForPlatform * @param Database $dbForPlatform
* @param Database $dbForProject * @param Database $dbForProject
* @param Realtime $queueForRealtime
* @return void * @return void
* @throws Authorization * @throws Authorization
* @throws Conflict * @throws Conflict
@ -388,7 +381,7 @@ class Databases extends Action
* @throws DatabaseException * @throws DatabaseException
* @throws \Throwable * @throws \Throwable
*/ */
private function createIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject): void private function createIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime): void
{ {
if ($collection->isEmpty()) { if ($collection->isEmpty()) {
throw new Exception('Missing collection'); throw new Exception('Missing collection');
@ -398,12 +391,7 @@ class Databases extends Action
} }
$projectId = $project->getId(); $projectId = $project->getId();
$event = 'databases.[databaseId].collections.[collectionId].indexes.[indexId].update';
$events = Event::generateEvents('databases.[databaseId].collections.[collectionId].indexes.[indexId].update', [
'databaseId' => $database->getId(),
'collectionId' => $collection->getId(),
'indexId' => $index->getId()
]);
$collectionId = $collection->getId(); $collectionId = $collection->getId();
$key = $index->getAttribute('key', ''); $key = $index->getAttribute('key', '');
$type = $index->getAttribute('type', ''); $type = $index->getAttribute('type', '');
@ -430,7 +418,7 @@ class Databases extends Action
throw $e; throw $e;
} finally { } finally {
$this->trigger($database, $collection, $index, $project, $projectId, $events); $this->trigger($database, $collection, $project, $event, $queueForRealtime, null, $index);
$dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collectionId); $dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collectionId);
} }
} }
@ -442,6 +430,7 @@ class Databases extends Action
* @param Document $project * @param Document $project
* @param Database $dbForPlatform * @param Database $dbForPlatform
* @param Database $dbForProject * @param Database $dbForProject
* @param Realtime $queueForRealtime
* @return void * @return void
* @throws Authorization * @throws Authorization
* @throws Conflict * @throws Conflict
@ -449,7 +438,7 @@ class Databases extends Action
* @throws DatabaseException * @throws DatabaseException
* @throws \Throwable * @throws \Throwable
*/ */
private function deleteIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject): void private function deleteIndex(Document $database, Document $collection, Document $index, Document $project, Database $dbForPlatform, Database $dbForProject, Realtime $queueForRealtime): void
{ {
if ($collection->isEmpty()) { if ($collection->isEmpty()) {
throw new Exception('Missing collection'); throw new Exception('Missing collection');
@ -459,12 +448,7 @@ class Databases extends Action
} }
$projectId = $project->getId(); $projectId = $project->getId();
$event = 'databases.[databaseId].collections.[collectionId].indexes.[indexId].delete';
$events = Event::generateEvents('databases.[databaseId].collections.[collectionId].indexes.[indexId].delete', [
'databaseId' => $database->getId(),
'collectionId' => $collection->getId(),
'indexId' => $index->getId()
]);
$key = $index->getAttribute('key'); $key = $index->getAttribute('key');
$status = $index->getAttribute('status', ''); $status = $index->getAttribute('status', '');
$project = $dbForPlatform->getDocument('projects', $projectId); $project = $dbForPlatform->getDocument('projects', $projectId);
@ -490,7 +474,7 @@ class Databases extends Action
throw $e; throw $e;
} finally { } finally {
$this->trigger($database, $collection, $index, $project, $projectId, $events); $this->trigger($database, $collection, $project, $event, $queueForRealtime, null, $index);
$dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collection->getId()); $dbForProject->purgeCachedDocument('database_' . $database->getInternalId(), $collection->getId());
} }
} }
@ -532,7 +516,6 @@ class Databases extends Action
$collectionId = $collection->getId(); $collectionId = $collection->getId();
$collectionInternalId = $collection->getInternalId(); $collectionInternalId = $collection->getInternalId();
$databaseId = $database->getId();
$databaseInternalId = $database->getInternalId(); $databaseInternalId = $database->getInternalId();
$dbForProject->deleteCollection('database_' . $databaseInternalId . '_collection_' . $collection->getInternalId()); $dbForProject->deleteCollection('database_' . $databaseInternalId . '_collection_' . $collection->getInternalId());
@ -568,21 +551,21 @@ class Databases extends Action
/** /**
* @param string $collection collectionID * @param string $collectionId
* @param array $queries * @param array $queries
* @param Database $database * @param Database $database
* @param callable|null $callback * @param callable|null $callback
* @return void * @return void
* @throws Exception * @throws Exception
*/ */
protected function deleteByGroup(string $collection, array $queries, Database $database, callable $callback = null): void protected function deleteByGroup(string $collectionId, array $queries, Database $database, callable $callback = null): void
{ {
$start = \microtime(true); $start = \microtime(true);
try { try {
$documents = $database->deleteDocuments($collection, $queries); $documents = $database->deleteDocuments($collectionId, $queries);
} catch (\Throwable $th) { } catch (\Throwable $th) {
Console::error('Failed to delete documents for collection ' . $collection . ': ' . $th->getMessage()); Console::error('Failed to delete documents for collection ' . $collectionId . ': ' . $th->getMessage());
return; return;
} }
@ -598,31 +581,42 @@ class Databases extends Action
Console::info("Deleted {$count} documents by group in " . ($end - $start) . " seconds"); Console::info("Deleted {$count} documents by group in " . ($end - $start) . " seconds");
} }
/**
* @param Document $database
* @param Document $collection
* @param Document $project
* @param Realtime $queueForRealtime
* @param Document|null $attribute
* @param Document|null $index
* @return void
*/
protected function trigger( protected function trigger(
Document $database, Document $database,
Document $collection, Document $collection,
Document $attribute,
Document $project, Document $project,
string $projectId, string $event,
array $events Realtime $queueForRealtime,
Document|null $attribute = null,
Document|null $index = null,
): void { ): void {
$target = Realtime::fromPayload( $queueForRealtime
// Pass first, most verbose event pattern ->setProject($project)
event: $events[0], ->setSubscribers(['console'])
payload: $attribute, ->setEvent($event)
project: $project, ->setParam('databaseId', $database->getId())
); ->setParam('collectionId', $collection->getId());
Realtime::send(
projectId: 'console', if ($attribute !== null && !empty($attribute)) {
payload: $attribute->getArrayCopy(), $queueForRealtime
events: $events, ->setParam('attributeId', $attribute->getId())
channels: $target['channels'], ->setPayload($attribute->getArrayCopy());
roles: $target['roles'], }
options: [ if ($index !== null && !empty($index)) {
'projectId' => $projectId, $queueForRealtime
'databaseId' => $database->getId(), ->setParam('indexId', $index->getId())
'collectionId' => $collection->getId() ->setPayload($index->getArrayCopy());
] }
);
$queueForRealtime->trigger();
} }
} }

View file

@ -5,8 +5,9 @@ namespace Appwrite\Platform\Workers;
use Ahc\Jwt\JWT; use Ahc\Jwt\JWT;
use Appwrite\Event\Event; use Appwrite\Event\Event;
use Appwrite\Event\Func; use Appwrite\Event\Func;
use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage; use Appwrite\Event\StatsUsage;
use Appwrite\Messaging\Adapter\Realtime; use Appwrite\Event\Webhook;
use Appwrite\Utopia\Response\Model\Execution; use Appwrite\Utopia\Response\Model\Execution;
use Exception; use Exception;
use Executor\Executor; use Executor\Executor;
@ -44,15 +45,17 @@ class Functions extends Action
->inject('project') ->inject('project')
->inject('message') ->inject('message')
->inject('dbForProject') ->inject('dbForProject')
->inject('queueForWebhooks')
->inject('queueForFunctions') ->inject('queueForFunctions')
->inject('queueForRealtime')
->inject('queueForEvents') ->inject('queueForEvents')
->inject('queueForStatsUsage') ->inject('queueForStatsUsage')
->inject('log') ->inject('log')
->inject('isResourceBlocked') ->inject('isResourceBlocked')
->callback(fn (Document $project, Message $message, Database $dbForProject, Func $queueForFunctions, Event $queueForEvents, StatsUsage $queueForStatsUsage, Log $log, callable $isResourceBlocked) => $this->action($project, $message, $dbForProject, $queueForFunctions, $queueForEvents, $queueForStatsUsage, $log, $isResourceBlocked)); ->callback(fn (Document $project, Message $message, Database $dbForProject, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Event $queueForEvents, StatsUsage $queueForStatsUsage, Log $log, callable $isResourceBlocked) => $this->action($project, $message, $dbForProject, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $queueForEvents, $queueForStatsUsage, $log, $isResourceBlocked));
} }
public function action(Document $project, Message $message, Database $dbForProject, Func $queueForFunctions, Event $queueForEvents, StatsUsage $queueForStatsUsage, Log $log, callable $isResourceBlocked): void public function action(Document $project, Message $message, Database $dbForProject, Webhook $queueForWebhooks, Func $queueForFunctions, Realtime $queueForRealtime, Event $queueForEvents, StatsUsage $queueForStatsUsage, Log $log, callable $isResourceBlocked): void
{ {
$payload = $message->getPayload() ?? []; $payload = $message->getPayload() ?? [];
@ -136,7 +139,9 @@ class Functions extends Action
$this->execute( $this->execute(
log: $log, log: $log,
dbForProject: $dbForProject, dbForProject: $dbForProject,
queueForWebhooks: $queueForWebhooks,
queueForFunctions: $queueForFunctions, queueForFunctions: $queueForFunctions,
queueForRealtime: $queueForRealtime,
queueForStatsUsage: $queueForStatsUsage, queueForStatsUsage: $queueForStatsUsage,
queueForEvents: $queueForEvents, queueForEvents: $queueForEvents,
project: $project, project: $project,
@ -176,7 +181,9 @@ class Functions extends Action
$this->execute( $this->execute(
log: $log, log: $log,
dbForProject: $dbForProject, dbForProject: $dbForProject,
queueForWebhooks: $queueForWebhooks,
queueForFunctions: $queueForFunctions, queueForFunctions: $queueForFunctions,
queueForRealtime: $queueForRealtime,
queueForStatsUsage: $queueForStatsUsage, queueForStatsUsage: $queueForStatsUsage,
queueForEvents: $queueForEvents, queueForEvents: $queueForEvents,
project: $project, project: $project,
@ -198,7 +205,9 @@ class Functions extends Action
$this->execute( $this->execute(
log: $log, log: $log,
dbForProject: $dbForProject, dbForProject: $dbForProject,
queueForWebhooks: $queueForWebhooks,
queueForFunctions: $queueForFunctions, queueForFunctions: $queueForFunctions,
queueForRealtime: $queueForRealtime,
queueForStatsUsage: $queueForStatsUsage, queueForStatsUsage: $queueForStatsUsage,
queueForEvents: $queueForEvents, queueForEvents: $queueForEvents,
project: $project, project: $project,
@ -284,6 +293,7 @@ class Functions extends Action
* @param Log $log * @param Log $log
* @param Database $dbForProject * @param Database $dbForProject
* @param Func $queueForFunctions * @param Func $queueForFunctions
* @param Realtime $queueForRealtime
* @param StatsUsage $queueForStatsUsage * @param StatsUsage $queueForStatsUsage
* @param Event $queueForEvents * @param Event $queueForEvents
* @param Document $project * @param Document $project
@ -307,7 +317,9 @@ class Functions extends Action
private function execute( private function execute(
Log $log, Log $log,
Database $dbForProject, Database $dbForProject,
Webhook $queueForWebhooks,
Func $queueForFunctions, Func $queueForFunctions,
Realtime $queueForRealtime,
StatsUsage $queueForStatsUsage, StatsUsage $queueForStatsUsage,
Event $queueForEvents, Event $queueForEvents,
Document $project, Document $project,
@ -564,20 +576,20 @@ class Functions extends Action
; ;
} }
$execution = $dbForProject->updateDocument('executions', $executionId, $execution); $execution = $dbForProject->updateDocument('executions', $executionId, $execution);
/** Trigger Webhook */
$executionModel = new Execution(); $executionModel = new Execution();
$queueForEvents $queueForEvents
->setQueue(Event::WEBHOOK_QUEUE_NAME)
->setClass(Event::WEBHOOK_CLASS_NAME)
->setProject($project) ->setProject($project)
->setUser($user) ->setUser($user)
->setEvent('functions.[functionId].executions.[executionId].update') ->setEvent('functions.[functionId].executions.[executionId].update')
->setParam('functionId', $function->getId()) ->setParam('functionId', $function->getId())
->setParam('executionId', $execution->getId()) ->setParam('executionId', $execution->getId())
->setPayload($execution->getArrayCopy(array_keys($executionModel->getRules()))) ->setPayload($execution->getArrayCopy(array_keys($executionModel->getRules())));
/** Trigger Webhook */
$queueForWebhooks
->from($queueForEvents)
->trigger(); ->trigger();
/** Trigger Functions */ /** Trigger Functions */
@ -585,31 +597,11 @@ class Functions extends Action
->from($queueForEvents) ->from($queueForEvents)
->trigger(); ->trigger();
/** Trigger realtime event */ /** Trigger Realtime Events */
$allEvents = Event::generateEvents('functions.[functionId].executions.[executionId].update', [ $queueForRealtime
'functionId' => $function->getId(), ->from($queueForEvents)
'executionId' => $execution->getId() ->setSubscribers(['console', $project->getId()])
]); ->trigger();
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $execution,
project: $project
);
Realtime::send(
projectId: 'console',
payload: $execution->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
Realtime::send(
projectId: $project->getId(),
payload: $execution->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
if (!empty($error)) { if (!empty($error)) {
throw new Exception($error, $errorCode); throw new Exception($error, $errorCode);

View file

@ -3,8 +3,7 @@
namespace Appwrite\Platform\Workers; namespace Appwrite\Platform\Workers;
use Ahc\Jwt\JWT; use Ahc\Jwt\JWT;
use Appwrite\Event\Event; use Appwrite\Event\Realtime;
use Appwrite\Messaging\Adapter\Realtime;
use Exception; use Exception;
use Utopia\CLI\Console; use Utopia\CLI\Console;
use Utopia\Config\Config; use Utopia\Config\Config;
@ -54,13 +53,14 @@ class Migrations extends Action
->inject('dbForProject') ->inject('dbForProject')
->inject('dbForPlatform') ->inject('dbForPlatform')
->inject('logError') ->inject('logError')
->callback(fn (Message $message, Document $project, Database $dbForProject, Database $dbForPlatform, callable $logError) => $this->action($message, $project, $dbForProject, $dbForPlatform, $logError)); ->inject('queueForRealtime')
->callback(fn (Message $message, Document $project, Database $dbForProject, Database $dbForPlatform, callable $logError, Realtime $queueForRealtime) => $this->action($message, $project, $dbForProject, $dbForPlatform, $logError, $queueForRealtime));
} }
/** /**
* @throws Exception * @throws Exception
*/ */
public function action(Message $message, Document $project, Database $dbForProject, Database $dbForPlatform, callable $logError): void public function action(Message $message, Document $project, Database $dbForProject, Database $dbForPlatform, callable $logError, Realtime $queueForRealtime): void
{ {
$payload = $message->getPayload() ?? []; $payload = $message->getPayload() ?? [];
@ -87,7 +87,7 @@ class Migrations extends Action
return; return;
} }
$this->processMigration($migration); $this->processMigration($migration, $queueForRealtime);
} }
/** /**
@ -155,34 +155,16 @@ class Migrations extends Action
* @throws \Utopia\Database\Exception * @throws \Utopia\Database\Exception
* @throws Exception * @throws Exception
*/ */
protected function updateMigrationDocument(Document $migration, Document $project): Document protected function updateMigrationDocument(Document $migration, Document $project, Realtime $queueForRealtime): Document
{ {
/** Trigger Realtime */ /** Trigger Realtime Events */
$allEvents = Event::generateEvents('migrations.[migrationId].update', [ $queueForRealtime
'migrationId' => $migration->getId(), ->setProject($project)
]); ->setSubscribers(['console', $project->getId()])
->setEvent('migrations.[migrationId].update')
$target = Realtime::fromPayload( ->setParam('migrationId', $migration->getId())
event: $allEvents[0], ->setPayload($migration->getArrayCopy())
payload: $migration, ->trigger();
project: $project
);
Realtime::send(
projectId: 'console',
payload: $migration->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles'],
);
Realtime::send(
projectId: $project->getId(),
payload: $migration->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles'],
);
return $this->dbForProject->updateDocument('migrations', $migration->getId(), $migration); return $this->dbForProject->updateDocument('migrations', $migration->getId(), $migration);
} }
@ -231,7 +213,7 @@ class Migrations extends Action
* @throws \Utopia\Database\Exception * @throws \Utopia\Database\Exception
* @throws Exception * @throws Exception
*/ */
protected function processMigration(Document $migration): void protected function processMigration(Document $migration, Realtime $queueForRealtime): void
{ {
$project = $this->project; $project = $this->project;
$projectDocument = $this->dbForPlatform->getDocument('projects', $project->getId()); $projectDocument = $this->dbForPlatform->getDocument('projects', $project->getId());
@ -255,7 +237,7 @@ class Migrations extends Action
$migration->setAttribute('stage', 'processing'); $migration->setAttribute('stage', 'processing');
$migration->setAttribute('status', 'processing'); $migration->setAttribute('status', 'processing');
$this->updateMigrationDocument($migration, $projectDocument); $this->updateMigrationDocument($migration, $projectDocument, $queueForRealtime);
$source = $this->processSource($migration); $source = $this->processSource($migration);
$destination = $this->processDestination($migration, $tempAPIKey); $destination = $this->processDestination($migration, $tempAPIKey);
@ -269,14 +251,14 @@ class Migrations extends Action
/** Start Transfer */ /** Start Transfer */
$migration->setAttribute('stage', 'migrating'); $migration->setAttribute('stage', 'migrating');
$this->updateMigrationDocument($migration, $projectDocument); $this->updateMigrationDocument($migration, $projectDocument, $queueForRealtime);
$transfer->run( $transfer->run(
$migration->getAttribute('resources'), $migration->getAttribute('resources'),
function () use ($migration, $transfer, $projectDocument) { function () use ($migration, $transfer, $projectDocument, $queueForRealtime) {
$migration->setAttribute('resourceData', json_encode($transfer->getCache())); $migration->setAttribute('resourceData', json_encode($transfer->getCache()));
$migration->setAttribute('statusCounters', json_encode($transfer->getStatusCounters())); $migration->setAttribute('statusCounters', json_encode($transfer->getStatusCounters()));
$this->updateMigrationDocument($migration, $projectDocument); $this->updateMigrationDocument($migration, $projectDocument, $queueForRealtime);
}, },
$migration->getAttribute('resourceId'), $migration->getAttribute('resourceId'),
$migration->getAttribute('resourceType') $migration->getAttribute('resourceType')
@ -313,7 +295,7 @@ class Migrations extends Action
} }
$migration->setAttribute('errors', $errorMessages); $migration->setAttribute('errors', $errorMessages);
$this->updateMigrationDocument($migration, $projectDocument); $this->updateMigrationDocument($migration, $projectDocument, $queueForRealtime);
return; return;
} }
@ -354,7 +336,7 @@ class Migrations extends Action
$migration->setAttribute('errors', $errorMessages); $migration->setAttribute('errors', $errorMessages);
} }
} finally { } finally {
$this->updateMigrationDocument($migration, $projectDocument); $this->updateMigrationDocument($migration, $projectDocument, $queueForRealtime);
if ($migration->getAttribute('status', '') === 'failed') { if ($migration->getAttribute('status', '') === 'failed') {
Console::error('Migration('.$migration->getInternalId().':'.$migration->getId().') failed, Project('.$this->project->getInternalId().':'.$this->project->getId().')'); Console::error('Migration('.$migration->getInternalId().':'.$migration->getId().') failed, Project('.$this->project->getInternalId().':'.$this->project->getId().')');