1
0
Fork 0
mirror of synced 2024-06-27 18:50:47 +12:00

adds scheduling commit, still need to resolve pools error

This commit is contained in:
Prateek Banga 2023-12-07 11:25:19 +01:00
parent 396ff87fbc
commit b8aa2faa7b
15 changed files with 364 additions and 68 deletions

View file

@ -80,6 +80,7 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/migrate && \ chmod +x /usr/local/bin/migrate && \
chmod +x /usr/local/bin/realtime && \ chmod +x /usr/local/bin/realtime && \
chmod +x /usr/local/bin/schedule && \ chmod +x /usr/local/bin/schedule && \
chmod +x /usr/local/bin/schedule-message && \
chmod +x /usr/local/bin/sdks && \ chmod +x /usr/local/bin/sdks && \
chmod +x /usr/local/bin/specs && \ chmod +x /usr/local/bin/specs && \
chmod +x /usr/local/bin/ssl && \ chmod +x /usr/local/bin/ssl && \

View file

@ -1593,6 +1593,28 @@ $commonCollections = [
'array' => false, 'array' => false,
'filters' => ['datetime'], 'filters' => ['datetime'],
], ],
[
'$id' => ID::custom('scheduleInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('scheduleId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[ [
'$id' => ID::custom('deliveredAt'), '$id' => ID::custom('deliveredAt'),
'type' => Database::VAR_DATETIME, 'type' => Database::VAR_DATETIME,
@ -4166,6 +4188,13 @@ $consoleCollections = array_merge([
'lengths' => [], 'lengths' => [],
'orders' => [], 'orders' => [],
], ],
[
'$id' => ID::custom('_key_schedule_resourceType_active_resourceUpdatedAt'),
'type' => Database::INDEX_KEY,
'attributes' => ['schedule', 'resourceType', 'active', 'resourceUpdatedAt'],
'lengths' => [],
'orders' => [],
]
], ],
], ],

View file

@ -1375,7 +1375,6 @@ App::post('/v1/account/sessions/phone')
->setMessage($messageDoc) ->setMessage($messageDoc)
->setRecipients([$phone]) ->setRecipients([$phone])
->setProviderType(MESSAGE_TYPE_SMS) ->setProviderType(MESSAGE_TYPE_SMS)
->setProject($project)
->trigger(); ->trigger();
$queueForEvents->setPayload( $queueForEvents->setPayload(
@ -3101,7 +3100,6 @@ App::post('/v1/account/verification/phone')
->setMessage($messageDoc) ->setMessage($messageDoc)
->setRecipients([$user->getAttribute('phone')]) ->setRecipients([$user->getAttribute('phone')])
->setProviderType(MESSAGE_TYPE_SMS) ->setProviderType(MESSAGE_TYPE_SMS)
->setProject($project)
->trigger(); ->trigger();
$queueForEvents $queueForEvents

View file

@ -34,6 +34,7 @@ use Utopia\Validator\Boolean;
use Utopia\Validator\JSON; use Utopia\Validator\JSON;
use Utopia\Validator\Text; use Utopia\Validator\Text;
use MaxMind\Db\Reader; use MaxMind\Db\Reader;
use Utopia\Database\DateTime;
use Utopia\Validator\WhiteList; use Utopia\Validator\WhiteList;
use function Swoole\Coroutine\batch; use function Swoole\Coroutine\batch;
@ -1441,7 +1442,6 @@ App::patch('/v1/messaging/providers/fcm/:providerId')
->label('audits.resource', 'provider/{response.$id}') ->label('audits.resource', 'provider/{response.$id}')
->label('event', 'providers.[providerId].update') ->label('event', 'providers.[providerId].update')
->label('scope', 'providers.write') ->label('scope', 'providers.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'messaging') ->label('sdk.namespace', 'messaging')
->label('sdk.method', 'updateFcmProvider') ->label('sdk.method', 'updateFcmProvider')
->label('sdk.description', '/docs/references/messaging/update-fcm-provider.md') ->label('sdk.description', '/docs/references/messaging/update-fcm-provider.md')
@ -2228,10 +2228,11 @@ App::post('/v1/messaging/messages/email')
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
->inject('queueForEvents') ->inject('queueForEvents')
->inject('dbForProject') ->inject('dbForProject')
->inject('dbForConsole')
->inject('project') ->inject('project')
->inject('queueForMessaging') ->inject('queueForMessaging')
->inject('response') ->inject('response')
->action(function (string $messageId, string $subject, string $content, array $topics, array $users, array $targets, string $description, string $status, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) { ->action(function (string $messageId, string $subject, string $content, array $topics, array $users, array $targets, string $description, string $status, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) {
$messageId = $messageId == 'unique()' ? ID::unique() : $messageId; $messageId = $messageId == 'unique()' ? ID::unique() : $messageId;
if (\count($topics) === 0 && \count($users) === 0 && \count($targets) === 0) { if (\count($topics) === 0 && \count($users) === 0 && \count($targets) === 0) {
@ -2245,6 +2246,7 @@ App::post('/v1/messaging/messages/email')
'users' => $users, 'users' => $users,
'targets' => $targets, 'targets' => $targets,
'description' => $description, 'description' => $description,
'scheduledAt' => $scheduledAt,
'data' => [ 'data' => [
'subject' => $subject, 'subject' => $subject,
'content' => $content, 'content' => $content,
@ -2253,11 +2255,24 @@ App::post('/v1/messaging/messages/email')
'status' => $status, 'status' => $status,
])); ]));
if ($status === 'processing') { if ($status === 'processing' && $scheduledAt === null) {
$queueForMessaging $queueForMessaging
->setMessageId($message->getId()) ->setMessageId($message->getId())
->setProject($project)
->trigger(); ->trigger();
} else if ($scheduledAt !== null) {
$schedule = $dbForConsole->createDocument('schedules', new Document([
'region' => App::getEnv('_APP_REGION', 'default'),
'resourceType' => 'message',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'schedule' => $message->getAttribute('scheduledAt'),
'active' => $status === 'processing' ? true : false,
]));
$message->setAttribute('scheduleId', $schedule->getId());
$dbForProject->updateDocument('messages', $message->getId(), $message);
} }
$queueForEvents $queueForEvents
@ -2292,10 +2307,11 @@ App::post('/v1/messaging/messages/sms')
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
->inject('queueForEvents') ->inject('queueForEvents')
->inject('dbForProject') ->inject('dbForProject')
->inject('dbForConsole')
->inject('project') ->inject('project')
->inject('queueForMessaging') ->inject('queueForMessaging')
->inject('response') ->inject('response')
->action(function (string $messageId, string $content, array $topics, array $users, array $targets, string $description, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) { ->action(function (string $messageId, string $content, array $topics, array $users, array $targets, string $description, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) {
$messageId = $messageId == 'unique()' ? ID::unique() : $messageId; $messageId = $messageId == 'unique()' ? ID::unique() : $messageId;
if (\count($topics) === 0 && \count($users) === 0 && \count($targets) === 0) { if (\count($topics) === 0 && \count($users) === 0 && \count($targets) === 0) {
@ -2315,11 +2331,24 @@ App::post('/v1/messaging/messages/sms')
'status' => $status, 'status' => $status,
])); ]));
if ($status === 'processing') { if ($status === 'processing' && $scheduledAt === null) {
$queueForMessaging $queueForMessaging
->setMessageId($message->getId()) ->setMessageId($message->getId())
->setProject($project)
->trigger(); ->trigger();
} else if ($status === 'processing' && $scheduledAt !== null) {
$schedule = $dbForConsole->createDocument('schedules', new Document([
'region' => App::getEnv('_APP_REGION', 'default'),
'resourceType' => 'message',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'schedule' => $message->getAttribute('scheduledAt'),
'active' => $status === 'processing' ? true : false,
]));
$message->setAttribute('scheduleId', $schedule->getId());
$dbForProject->updateDocument('messages', $message->getId(), $message);
} }
$queueForEvents $queueForEvents
@ -2362,10 +2391,11 @@ App::post('/v1/messaging/messages/push')
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
->inject('queueForEvents') ->inject('queueForEvents')
->inject('dbForProject') ->inject('dbForProject')
->inject('dbForConsole')
->inject('project') ->inject('project')
->inject('queueForMessaging') ->inject('queueForMessaging')
->inject('response') ->inject('response')
->action(function (string $messageId, string $title, string $body, array $topics, array $users, array $targets, string $description, ?array $data, string $action, string $icon, string $sound, string $color, string $tag, string $badge, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) { ->action(function (string $messageId, string $title, string $body, array $topics, array $users, array $targets, string $description, ?array $data, string $action, string $icon, string $sound, string $color, string $tag, string $badge, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) {
$messageId = $messageId == 'unique()' ? ID::unique() : $messageId; $messageId = $messageId == 'unique()' ? ID::unique() : $messageId;
if (\count($topics) === 0 && \count($users) === 0 && \count($targets) === 0) { if (\count($topics) === 0 && \count($users) === 0 && \count($targets) === 0) {
@ -2394,11 +2424,24 @@ App::post('/v1/messaging/messages/push')
'status' => $status, 'status' => $status,
])); ]));
if ($status === 'processing') { if ($status === 'processing' && $scheduledAt === null) {
$queueForMessaging $queueForMessaging
->setMessageId($message->getId()) ->setMessageId($message->getId())
->setProject($project)
->trigger(); ->trigger();
} else if ($status === 'processing' && $scheduledAt !== null) {
$schedule = $dbForConsole->createDocument('schedules', new Document([
'region' => App::getEnv('_APP_REGION', 'default'),
'resourceType' => 'message',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'schedule' => $message->getAttribute('scheduledAt'),
'active' => $status === 'processing' ? true : false,
]));
$message->setAttribute('scheduleId', $schedule->getId());
$dbForProject->updateDocument('messages', $message->getId(), $message);
} }
$queueForEvents $queueForEvents
@ -2586,10 +2629,11 @@ App::patch('/v1/messaging/messages/email/:messageId')
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
->inject('queueForEvents') ->inject('queueForEvents')
->inject('dbForProject') ->inject('dbForProject')
->inject('dbForConsole')
->inject('project') ->inject('project')
->inject('queueForMessaging') ->inject('queueForMessaging')
->inject('response') ->inject('response')
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, string $subject, string $description, string $content, string $status, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) { ->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, string $subject, string $description, string $content, string $status, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) {
$message = $dbForProject->getDocument('messages', $messageId); $message = $dbForProject->getDocument('messages', $messageId);
if ($message->isEmpty()) { if ($message->isEmpty()) {
@ -2642,14 +2686,25 @@ App::patch('/v1/messaging/messages/email/:messageId')
if (!is_null($scheduledAt)) { if (!is_null($scheduledAt)) {
$message->setAttribute('scheduledAt', $scheduledAt); $message->setAttribute('scheduledAt', $scheduledAt);
$schedule = $dbForConsole->getDocument('schedules', $message->getAttribute('scheduleId'));
$schedule
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('schedule', $message->getAttribute('schedule'));
if ($message->getAttribute('status') === 'processing') {
$schedule->setAttribute('active', true);
}
$dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule);
} }
$message = $dbForProject->updateDocument('messages', $message->getId(), $message); $message = $dbForProject->updateDocument('messages', $message->getId(), $message);
if ($status === 'processing') { if ($status === 'processing' && \is_null($message->getAttribute('scheduledAt'))) {
$queueForMessaging $queueForMessaging
->setMessageId($message->getId()) ->setMessageId($message->getId())
->setProject($project)
->trigger(); ->trigger();
} }
@ -2684,10 +2739,11 @@ App::patch('/v1/messaging/messages/sms/:messageId')
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
->inject('queueForEvents') ->inject('queueForEvents')
->inject('dbForProject') ->inject('dbForProject')
->inject('dbForConsole')
->inject('project') ->inject('project')
->inject('queueForMessaging') ->inject('queueForMessaging')
->inject('response') ->inject('response')
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, string $description, string $content, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) { ->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, string $description, string $content, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) {
$message = $dbForProject->getDocument('messages', $messageId); $message = $dbForProject->getDocument('messages', $messageId);
if ($message->isEmpty()) { if ($message->isEmpty()) {
@ -2732,14 +2788,25 @@ App::patch('/v1/messaging/messages/sms/:messageId')
if (!is_null($scheduledAt)) { if (!is_null($scheduledAt)) {
$message->setAttribute('scheduledAt', $scheduledAt); $message->setAttribute('scheduledAt', $scheduledAt);
$schedule = $dbForConsole->getDocument('schedules', $message->getAttribute('scheduleId'));
$schedule
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('schedule', $message->getAttribute('schedule'));
if ($message->getAttribute('status') === 'processing') {
$schedule->setAttribute('active', true);
}
$dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule);
} }
$message = $dbForProject->updateDocument('messages', $message->getId(), $message); $message = $dbForProject->updateDocument('messages', $message->getId(), $message);
if ($status === 'processing') { if ($status === 'processing' && \is_null($message->getAttribute('scheduledAt'))) {
$queueForMessaging $queueForMessaging
->setMessageId($message->getId()) ->setMessageId($message->getId())
->setProject($project)
->trigger(); ->trigger();
} }
@ -2781,10 +2848,11 @@ App::patch('/v1/messaging/messages/push/:messageId')
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true) ->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
->inject('queueForEvents') ->inject('queueForEvents')
->inject('dbForProject') ->inject('dbForProject')
->inject('dbForConsole')
->inject('project') ->inject('project')
->inject('queueForMessaging') ->inject('queueForMessaging')
->inject('response') ->inject('response')
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, string $description, string $title, string $body, ?array $data, string $action, string $icon, string $sound, string $color, string $tag, string $badge, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) { ->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, string $description, string $title, string $body, ?array $data, string $action, string $icon, string $sound, string $color, string $tag, string $badge, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) {
$message = $dbForProject->getDocument('messages', $messageId); $message = $dbForProject->getDocument('messages', $messageId);
if ($message->isEmpty()) { if ($message->isEmpty()) {
@ -2861,16 +2929,27 @@ App::patch('/v1/messaging/messages/push/:messageId')
if (!is_null($scheduledAt)) { if (!is_null($scheduledAt)) {
$message->setAttribute('scheduledAt', $scheduledAt); $message->setAttribute('scheduledAt', $scheduledAt);
$schedule = $dbForConsole->getDocument('schedules', $message->getAttribute('scheduleId'));
$schedule
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('schedule', $message->getAttribute('schedule'));
if ($message->getAttribute('status') === 'processing') {
$schedule->setAttribute('active', true);
}
$dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule);
} }
$message = $dbForProject->updateDocument('messages', $message->getId(), $message); $message = $dbForProject->updateDocument('messages', $message->getId(), $message);
if ($status === 'processing') { if ($status === 'processing' && \is_null($message->getAttribute('scheduledAt'))) {
$queueForMessaging $queueForMessaging
->setMessageId($message->getId()) ->setMessageId($message->getId())
->setProject($project)
->trigger(); ->trigger();
} }
$queueForEvents $queueForEvents
->setParam('messageId', $message->getId()); ->setParam('messageId', $message->getId());

View file

@ -653,7 +653,6 @@ App::post('/v1/teams/:teamId/memberships')
->setMessage($messageDoc) ->setMessage($messageDoc)
->setRecipients([$phone]) ->setRecipients([$phone])
->setProviderType('SMS') ->setProviderType('SMS')
->setProject($project)
->trigger(); ->trigger();
} }
} }

View file

@ -7,6 +7,7 @@ use Appwrite\Event\Delete;
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\Event\Messaging;
use Appwrite\Extend\Exception; use Appwrite\Extend\Exception;
use Appwrite\Messaging\Adapter\Realtime; use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Usage\Stats; use Appwrite\Usage\Stats;
@ -97,6 +98,7 @@ App::init()
->inject('project') ->inject('project')
->inject('user') ->inject('user')
->inject('queueForEvents') ->inject('queueForEvents')
->inject('queueForMessaging')
->inject('queueForAudits') ->inject('queueForAudits')
->inject('queueForDeletes') ->inject('queueForDeletes')
->inject('queueForDatabase') ->inject('queueForDatabase')
@ -104,7 +106,7 @@ App::init()
->inject('mode') ->inject('mode')
->inject('queueForMails') ->inject('queueForMails')
->inject('usage') ->inject('usage')
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Database $dbForProject, string $mode, Mail $queueForMails, Stats $usage) use ($databaseListener) { ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Database $dbForProject, string $mode, Mail $queueForMails, Stats $usage) use ($databaseListener) {
$route = $utopia->getRoute(); $route = $utopia->getRoute();
@ -178,6 +180,9 @@ App::init()
->setProject($project) ->setProject($project)
->setUser($user); ->setUser($user);
$queueForMessaging
->setProject($project);
$queueForAudits $queueForAudits
->setMode($mode) ->setMode($mode)
->setUserAgent($request->getUserAgent('')) ->setUserAgent($request->getUserAgent(''))

3
bin/schedule-message Normal file
View file

@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php schedule-message $@

50
composer.lock generated
View file

@ -402,16 +402,16 @@
}, },
{ {
"name": "guzzlehttp/guzzle", "name": "guzzlehttp/guzzle",
"version": "7.8.0", "version": "7.8.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/guzzle.git", "url": "https://github.com/guzzle/guzzle.git",
"reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9" "reference": "41042bc7ab002487b876a0683fc8dce04ddce104"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/1110f66a6530a40fe7aea0378fe608ee2b2248f9", "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104",
"reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9", "reference": "41042bc7ab002487b876a0683fc8dce04ddce104",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -426,11 +426,11 @@
"psr/http-client-implementation": "1.0" "psr/http-client-implementation": "1.0"
}, },
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.8.1", "bamarni/composer-bin-plugin": "^1.8.2",
"ext-curl": "*", "ext-curl": "*",
"php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
"php-http/message-factory": "^1.1", "php-http/message-factory": "^1.1",
"phpunit/phpunit": "^8.5.29 || ^9.5.23", "phpunit/phpunit": "^8.5.36 || ^9.6.15",
"psr/log": "^1.1 || ^2.0 || ^3.0" "psr/log": "^1.1 || ^2.0 || ^3.0"
}, },
"suggest": { "suggest": {
@ -508,7 +508,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/guzzle/issues", "issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.8.0" "source": "https://github.com/guzzle/guzzle/tree/7.8.1"
}, },
"funding": [ "funding": [
{ {
@ -524,28 +524,28 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-08-27T10:20:53+00:00" "time": "2023-12-03T20:35:24+00:00"
}, },
{ {
"name": "guzzlehttp/promises", "name": "guzzlehttp/promises",
"version": "2.0.1", "version": "2.0.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/promises.git", "url": "https://github.com/guzzle/promises.git",
"reference": "111166291a0f8130081195ac4556a5587d7f1b5d" "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/111166291a0f8130081195ac4556a5587d7f1b5d", "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223",
"reference": "111166291a0f8130081195ac4556a5587d7f1b5d", "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.2.5 || ^8.0" "php": "^7.2.5 || ^8.0"
}, },
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.8.1", "bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^8.5.29 || ^9.5.23" "phpunit/phpunit": "^8.5.36 || ^9.6.15"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -591,7 +591,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/promises/issues", "issues": "https://github.com/guzzle/promises/issues",
"source": "https://github.com/guzzle/promises/tree/2.0.1" "source": "https://github.com/guzzle/promises/tree/2.0.2"
}, },
"funding": [ "funding": [
{ {
@ -607,20 +607,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-08-03T15:11:55+00:00" "time": "2023-12-03T20:19:20+00:00"
}, },
{ {
"name": "guzzlehttp/psr7", "name": "guzzlehttp/psr7",
"version": "2.6.1", "version": "2.6.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/psr7.git", "url": "https://github.com/guzzle/psr7.git",
"reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727" "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/be45764272e8873c72dbe3d2edcfdfcc3bc9f727", "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221",
"reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727", "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -634,9 +634,9 @@
"psr/http-message-implementation": "1.0" "psr/http-message-implementation": "1.0"
}, },
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.8.1", "bamarni/composer-bin-plugin": "^1.8.2",
"http-interop/http-factory-tests": "^0.9", "http-interop/http-factory-tests": "^0.9",
"phpunit/phpunit": "^8.5.29 || ^9.5.23" "phpunit/phpunit": "^8.5.36 || ^9.6.15"
}, },
"suggest": { "suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
@ -707,7 +707,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/psr7/issues", "issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/2.6.1" "source": "https://github.com/guzzle/psr7/tree/2.6.2"
}, },
"funding": [ "funding": [
{ {
@ -723,7 +723,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-08-27T10:13:57+00:00" "time": "2023-12-03T20:05:35+00:00"
}, },
{ {
"name": "influxdb/influxdb-php", "name": "influxdb/influxdb-php",
@ -5823,5 +5823,5 @@
"platform-overrides": { "platform-overrides": {
"php": "8.0" "php": "8.0"
}, },
"plugin-api-version": "2.3.0" "plugin-api-version": "2.6.0"
} }

View file

@ -717,6 +717,33 @@ services:
- _APP_DB_USER - _APP_DB_USER
- _APP_DB_PASS - _APP_DB_PASS
appwrite-schedule-message:
entrypoint: schedule-message
<<: *x-logging
container_name: appwrite-schedule-message
image: appwrite-dev
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- mariadb
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
appwrite-assistant: appwrite-assistant:
container_name: appwrite-assistant container_name: appwrite-assistant
image: appwrite/assistant:0.2.2 image: appwrite/assistant:0.2.2

View file

@ -20,6 +20,7 @@ use Appwrite\Platform\Tasks\CalcTierStats;
use Appwrite\Platform\Tasks\Upgrade; use Appwrite\Platform\Tasks\Upgrade;
use Appwrite\Platform\Tasks\DeleteOrphanedProjects; use Appwrite\Platform\Tasks\DeleteOrphanedProjects;
use Appwrite\Platform\Tasks\PatchRecreateRepositoriesDocuments; use Appwrite\Platform\Tasks\PatchRecreateRepositoriesDocuments;
use Appwrite\Platform\Tasks\ScheduleMessage;
class Tasks extends Service class Tasks extends Service
{ {
@ -36,6 +37,7 @@ class Tasks extends Service
->addAction(Install::getName(), new Install()) ->addAction(Install::getName(), new Install())
->addAction(Upgrade::getName(), new Upgrade()) ->addAction(Upgrade::getName(), new Upgrade())
->addAction(Maintenance::getName(), new Maintenance()) ->addAction(Maintenance::getName(), new Maintenance())
->addAction(ScheduleMessage::getName(), new ScheduleMessage())
->addAction(Schedule::getName(), new Schedule()) ->addAction(Schedule::getName(), new Schedule())
->addAction(Migrate::getName(), new Migrate()) ->addAction(Migrate::getName(), new Migrate())
->addAction(SDKs::getName(), new SDKs()) ->addAction(SDKs::getName(), new SDKs())

View file

@ -85,7 +85,7 @@ class Maintenance extends Action
private function notifyDeleteUsageStats(int $usageStatsRetentionHourly, Delete $queueForDeletes): void private function notifyDeleteUsageStats(int $usageStatsRetentionHourly, Delete $queueForDeletes): void
{ {
($queueForDeletes) ($queueFor)
->setType(DELETE_TYPE_USAGE) ->setType(DELETE_TYPE_USAGE)
->setUsageRetentionHourlyDateTime(DateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly)) ->setUsageRetentionHourlyDateTime(DateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly))
->trigger(); ->trigger();

View file

@ -0,0 +1,164 @@
<?php
namespace Appwrite\Platform\Tasks;
use Appwrite\Event\Delete;
use Swoole\Timer;
use Utopia\Platform\Action;
use Utopia\CLI\Console;
use Utopia\Database\DateTime;
use Utopia\Database\Query;
use Utopia\Database\Database;
use Utopia\Pools\Group;
use Appwrite\Event\Messaging;
use function Swoole\Coroutine\run;
class ScheduleMessage extends Action
{
public const MESSAGE_UPDATE_TIMER = 10; //seconds
public const MESSAGE_ENQUEUE_TIMER = 60; //seconds
public static function getName(): string
{
return 'schedule-message';
}
public function __construct()
{
$this
->desc('Execute functions scheduled in Appwrite')
->inject('pools')
->inject('dbForConsole')
->inject('getProjectDB')
->callback(fn (Group $pools, Database $dbForConsole, callable $getProjectDB) => $this->action($pools, $dbForConsole, $getProjectDB));
}
/**
* 1. Load all documents from 'schedules' collection to create local copy
* 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, coroutime sleeps until exact time before sending request to worker.
*/
public function action(Group $pools, Database $dbForConsole, callable $getProjectDB): void
{
Console::title('Scheduler V1');
Console::success(APP_NAME . ' Scheduler v1 has started');
$schedules = []; // Local copy of 'schedules' collection
$lastSyncUpdate = DateTime::now();
$limit = 10000;
$sum = $limit;
$total = 0;
$loadStart = \microtime(true);
$latestDocument = null;
while ($sum === $limit) {
$paginationQueries = [Query::limit($limit)];
if ($latestDocument !== null) {
$paginationQueries[] = Query::cursorAfter($latestDocument);
}
try{
$results = $dbForConsole->find('schedules', \array_merge($paginationQueries, [
Query::lessThanEqual('schedule', DateTime::formatTz(DateTime::now())),
Query::equal('resourceType', ['message']),
Query::equal('active', [true]),
]));
} catch (\Exception $e) {
var_dump($e->getTraceAsString());
}
$sum = count($results);
$total = $total + $sum;
foreach($results as $schedule) {
$schedules[$schedule->getId()] = $schedule;
}
$latestDocument = !empty(array_key_last($results)) ? $results[array_key_last($results)] : null;
}
$pools->reclaim();
Console::success("{$total} message were loaded in " . (microtime(true) - $loadStart) . " seconds");
Console::success("Starting timers at " . DateTime::now());
run(
function () use ($dbForConsole, &$schedules, &$lastSyncUpdate, $pools) {
/**
* The timer synchronize $schedules copy with database collection.
*/
Timer::tick(self::MESSAGE_UPDATE_TIMER * 1000, function () use ($dbForConsole, &$schedules, &$lastSyncUpdate, $pools) {
$time = DateTime::now();
$timerStart = \microtime(true);
$limit = 1000;
$sum = $limit;
$total = 0;
$latestDocument = null;
Console::log("Sync tick: Running at $time");
while ($sum === $limit) {
$paginationQueries = [Query::limit($limit)];
if ($latestDocument !== null) {
$paginationQueries[] = Query::cursorAfter($latestDocument);
}
$results = $dbForConsole->find('schedules', \array_merge($paginationQueries, [
Query::lessThanEqual('schedule', DateTime::formatTz(DateTime::now())),
Query::equal('resourceType', ['message']),
Query::equal('active', [true]),
]));
$sum = \count($results);
$total = $total + $sum;
foreach ($results as $schedule) {
$schedules[$schedule->getId()] = $schedule;
}
$latestDocument = !empty(array_key_last($results)) ? $results[array_key_last($results)] : null;
}
$lastSyncUpdate = $time;
$timerEnd = \microtime(true);
$pools->reclaim();
Console::log("Sync tick: {$total} schedules were updated in " . ($timerEnd - $timerStart) . " seconds");
});
/**
* The timer to prepare soon-to-execute schedules.
*/
$enqueueMessages = function () use (&$schedules, $pools, $dbForConsole) {
foreach ($schedules as $scheduleId => $schedule) {
\go(function () use ($schedules, $schedule, $pools, $dbForConsole) {
$queue = $pools->get('queue')->pop();
$connection = $queue->getResource();
$queueForMessaging = new Messaging($connection);
$queueForDeletes = new Delete($connection);
$project = $dbForConsole->getDocument('projects', $schedule->getAttribute('projectId'));
$queueForMessaging
->setMessageId($schedule->getAttribute('resourceId'))
->setProject($project)
->trigger();
$schedule->setAttribute('active', false);
$dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule);
$queueForDeletes
->setType(DELETE_TYPE_SCHEDULES)
->setDocument($schedule);
$queue->reclaim();
unset($schedules[$schedule->getId()]);
});
}
};
Timer::tick(self::MESSAGE_ENQUEUE_TIMER * 1000, fn () => $enqueueMessages());
$enqueueMessages();
}
);
}
}

View file

@ -148,7 +148,7 @@ class Deletes extends Action
$this->deleteCacheByDate($project, $getProjectDB, $datetime); $this->deleteCacheByDate($project, $getProjectDB, $datetime);
break; break;
case DELETE_TYPE_SCHEDULES: case DELETE_TYPE_SCHEDULES:
$this->deleteSchedules($dbForConsole, $getProjectDB, $datetime); $this->deleteSchedules($dbForConsole, $getProjectDB, $datetime, $document);
break; break;
case DELETE_TYPE_TOPIC: case DELETE_TYPE_TOPIC:
$this->deleteTopic($project, $getProjectDB, $document); $this->deleteTopic($project, $getProjectDB, $document);
@ -167,13 +167,13 @@ class Deletes extends Action
* @throws Authorization * @throws Authorization
* @throws Throwable * @throws Throwable
*/ */
private function deleteSchedules(Database $dbForConsole, callable $getProjectDB, string $datetime): void private function deleteSchedules(Database $dbForConsole, callable $getProjectDB, string $datetime, ?Document $document = null): void
{ {
$this->listByGroup( $this->listByGroup(
'schedules', 'schedules',
[ [
Query::equal('region', [App::getEnv('_APP_REGION', 'default')]), Query::equal('region', [App::getEnv('_APP_REGION', 'default')]),
Query::equal('resourceType', ['function']), Query::equal('resourceType', [$document ?? $document->getAttribute('resourceType')]),
Query::lessThanEqual('resourceUpdatedAt', $datetime), Query::lessThanEqual('resourceUpdatedAt', $datetime),
Query::equal('active', [false]), Query::equal('active', [false]),
], ],

View file

@ -106,7 +106,6 @@ class Messaging extends Action
$targets = $dbForProject->find('targets', [Query::equal('$id', $targetsId)]); $targets = $dbForProject->find('targets', [Query::equal('$id', $targetsId)]);
$recipients = \array_merge($recipients, $targets); $recipients = \array_merge($recipients, $targets);
} }
$primaryProvider = $dbForProject->findOne('providers', [ $primaryProvider = $dbForProject->findOne('providers', [
Query::equal('enabled', [true]), Query::equal('enabled', [true]),
Query::equal('type', [$recipients[0]->getAttribute('providerType')]), Query::equal('type', [$recipients[0]->getAttribute('providerType')]),
@ -155,7 +154,6 @@ class Messaging extends Action
$providers[] = $provider; $providers[] = $provider;
$identifiers = $identifiersByProviderId[$providerId]; $identifiers = $identifiersByProviderId[$providerId];
$adapter = match ($provider->getAttribute('type')) { $adapter = match ($provider->getAttribute('type')) {
MESSAGE_TYPE_SMS => $this->sms($provider), MESSAGE_TYPE_SMS => $this->sms($provider),
MESSAGE_TYPE_PUSH => $this->push($provider), MESSAGE_TYPE_PUSH => $this->push($provider),

View file

@ -572,7 +572,8 @@ trait MessagingBase
'apiKey' => $apiKey, 'apiKey' => $apiKey,
'domain' => $domain, 'domain' => $domain,
'isEuRegion' => filter_var($isEuRegion, FILTER_VALIDATE_BOOLEAN), 'isEuRegion' => filter_var($isEuRegion, FILTER_VALIDATE_BOOLEAN),
'from' => $from 'from' => $from,
'enabled' => true,
]); ]);
$this->assertEquals(201, $provider['headers']['status-code']); $this->assertEquals(201, $provider['headers']['status-code']);
@ -605,18 +606,8 @@ trait MessagingBase
$this->assertEquals(201, $user['headers']['status-code']); $this->assertEquals(201, $user['headers']['status-code']);
// Create Target // Create Target
$target = $this->client->call(Client::METHOD_POST, '/users/' . $user['body']['$id'] . '/targets', [ $target = $user['body']['targets'][0];
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'targetId' => ID::unique(),
'providerType' => 'email',
'providerId' => $provider['body']['$id'],
'identifier' => $to,
]);
$this->assertEquals(201, $target['headers']['status-code']);
// Create Subscriber // Create Subscriber
$subscriber = $this->client->call(Client::METHOD_POST, '/messaging/topics/' . $topic['body']['$id'] . '/subscribers', \array_merge([ $subscriber = $this->client->call(Client::METHOD_POST, '/messaging/topics/' . $topic['body']['$id'] . '/subscribers', \array_merge([
@ -624,7 +615,7 @@ trait MessagingBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [ ], $this->getHeaders()), [
'subscriberId' => ID::unique(), 'subscriberId' => ID::unique(),
'targetId' => $target['body']['$id'], 'targetId' => $target['$id'],
]); ]);
$this->assertEquals(201, $subscriber['headers']['status-code']); $this->assertEquals(201, $subscriber['headers']['status-code']);