1
0
Fork 0
mirror of synced 2024-07-03 05:31:38 +12:00

Merge branch '1.5.x' into fix-limit-failed-webhook-attempts

This commit is contained in:
Khushboo Verma 2023-12-12 00:17:21 +05:30
commit ff7cfc6602
25 changed files with 1091 additions and 442 deletions

2
.env
View file

@ -87,7 +87,7 @@ _APP_LOGGING_PROVIDER=
_APP_LOGGING_CONFIG=
_APP_GRAPHQL_MAX_BATCH_SIZE=10
_APP_GRAPHQL_MAX_COMPLEXITY=250
_APP_GRAPHQL_MAX_DEPTH=3
_APP_GRAPHQL_MAX_DEPTH=4
_APP_DOCKER_HUB_USERNAME=
_APP_DOCKER_HUB_PASSWORD=
_APP_VCS_GITHUB_APP_NAME=

View file

@ -466,7 +466,7 @@
## Features
- Added Phone Authentication by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3357
- Added Twilio Support
- Added TextMagic Support
- Added Textmagic Support
- Added Telesign Support
- Added Endpoint to create Phone Session (`POST:/v1/account/sessions/phone`)
- Added Endpoint to confirm Phone Session (`PUT:/v1/account/sessions/phone`)

View file

@ -1505,6 +1505,17 @@ $commonCollections = [
'$id' => ID::custom('messages'),
'name' => 'Messages',
'attributes' => [
[
'$id' => ID::custom('providerType'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('description'),
'type' => Database::VAR_STRING,
@ -1572,7 +1583,7 @@ $commonCollections = [
'filters' => [],
],
[
'$id' => ID::custom('deliveryTime'),
'$id' => ID::custom('scheduledAt'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
@ -1722,28 +1733,6 @@ $commonCollections = [
'$id' => ID::custom('subscribers'),
'name' => 'Subscribers',
'attributes' => [
[
'$id' => ID::custom('userId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('userInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('targetId'),
'type' => Database::VAR_STRING,
@ -1766,6 +1755,28 @@ $commonCollections = [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('userId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('userInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('topicId'),
'type' => Database::VAR_STRING,
@ -1788,22 +1799,19 @@ $commonCollections = [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('providerType'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => ID::custom('_key_userId'),
'type' => Database::INDEX_KEY,
'attributes' => ['userId'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_userInternalId'),
'type' => Database::INDEX_KEY,
'attributes' => ['userInternalId'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_targetId'),
'type' => Database::INDEX_KEY,
@ -1818,6 +1826,20 @@ $commonCollections = [
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_userId'),
'type' => Database::INDEX_KEY,
'attributes' => ['userId'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_userInternalId'),
'type' => Database::INDEX_KEY,
'attributes' => ['userInternalId'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_topicId'),
'type' => Database::INDEX_KEY,

View file

@ -103,6 +103,16 @@ return [
'description' => 'This method was not fully implemented yet. If you believe this is a mistake, please upgrade your Appwrite server version.',
'code' => 405,
],
Exception::GENERAL_INVALID_EMAIL => [
'name' => Exception::GENERAL_INVALID_EMAIL,
'description' => 'Value must be a valid email address.',
'code' => 400,
],
Exception::GENERAL_INVALID_PHONE => [
'name' => Exception::GENERAL_INVALID_PHONE,
'description' => 'Value must be a valid phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.',
'code' => 400,
],
/** User Errors */
Exception::USER_COUNT_EXCEEDED => [
@ -837,5 +847,20 @@ return [
'description' => 'Message with the requested ID has already been scheduled for delivery.',
'code' => 400,
],
Exception::MESSAGE_TARGET_NOT_EMAIL => [
'name' => Exception::MESSAGE_TARGET_NOT_EMAIL,
'description' => 'Message with the target ID is not an email target:',
'code' => 400,
],
Exception::MESSAGE_TARGET_NOT_SMS => [
'name' => Exception::MESSAGE_TARGET_NOT_SMS,
'description' => 'Message with the target ID is not an SMS target:',
'code' => 400,
],
Exception::MESSAGE_TARGET_NOT_PUSH => [
'name' => Exception::MESSAGE_TARGET_NOT_PUSH,
'description' => 'Message with the target ID is not a push target:',
'code' => 400,
],
];

View file

@ -440,7 +440,7 @@ return [
'variables' => [
[
'name' => '_APP_SMS_PROVIDER',
'description' => "Provider used for delivering SMS for Phone authentication. Use the following format: 'sms://[USER]:[SECRET]@[PROVIDER]'.\n\nEnsure `[USER]` and `[SECRET]` are URL encoded if they contain any non-alphanumeric characters.\n\nAvailable providers are twilio, textmagic, telesign, msg91, and vonage.",
'description' => "Provider used for delivering SMS for Phone authentication. Use the following format: 'sms://[USER]:[SECRET]@[PROVIDER]'.\n\nEnsure `[USER]` and `[SECRET]` are URL encoded if they contain any non-alphanumeric characters.\n\nAvailable providers are twilio, Textmagic, telesign, msg91, and vonage.",
'introduction' => '0.15.0',
'default' => '',
'required' => false,

View file

@ -149,18 +149,20 @@ App::post('/v1/account')
]);
$user->removeAttribute('$internalId');
$user = Authorization::skip(fn() => $dbForProject->createDocument('users', $user));
$target = Authorization::skip(fn() => $dbForProject->createDocument('targets', new Document([
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::user($userId)),
Permission::delete(Role::user($userId)),
],
'userId' => $user->getId(),
'userInternalId' => $user->getInternalId(),
'providerType' => 'email',
'identifier' => $email,
])));
$user->setAttribute('targets', [$target]);
try {
$target = Authorization::skip(fn() => $dbForProject->createDocument('targets', new Document([
'userId' => $user->getId(),
'userInternalId' => $user->getInternalId(),
'providerType' => MESSAGE_TYPE_EMAIL,
'identifier' => $email,
])));
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $target]);
} catch (Duplicate) {
$existingTarget = $dbForProject->findOne('targets', [
Query::equal('identifier', [$email]),
]);
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]);
}
$dbForProject->deleteCachedDocument('users', $user->getId());
} catch (Duplicate) {
throw new Exception(Exception::USER_ALREADY_EXISTS);
@ -678,7 +680,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
],
'userId' => $userDoc->getId(),
'userInternalId' => $userDoc->getInternalId(),
'providerType' => 'email',
'providerType' => MESSAGE_TYPE_EMAIL,
'identifier' => $email,
]));
} catch (Duplicate) {
@ -1309,6 +1311,21 @@ App::post('/v1/account/sessions/phone')
$user->removeAttribute('$internalId');
Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
try {
$target = Authorization::skip(fn() => $dbForProject->createDocument('targets', new Document([
'userId' => $user->getId(),
'userInternalId' => $user->getInternalId(),
'providerType' => MESSAGE_TYPE_SMS,
'identifier' => $phone,
])));
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $target]);
} catch (Duplicate) {
$existingTarget = $dbForProject->findOne('targets', [
Query::equal('identifier', [$phone]),
]);
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]);
}
$dbForProject->deleteCachedDocument('users', $user->getId());
}
$secret = Auth::codeGenerator();
@ -1357,7 +1374,7 @@ App::post('/v1/account/sessions/phone')
$queueForMessaging
->setMessage($messageDoc)
->setRecipients([$phone])
->setProviderType('SMS')
->setProviderType(MESSAGE_TYPE_SMS)
->setProject($project)
->trigger();
@ -1732,7 +1749,7 @@ App::post('/v1/account/targets/push')
],
'providerId' => $providerId ?? null,
'providerInternalId' => $provider->getInternalId() ?? null,
'providerType' => 'push',
'providerType' => MESSAGE_TYPE_PUSH,
'userId' => $user->getId(),
'userInternalId' => $user->getInternalId(),
'identifier' => $identifier,
@ -2102,25 +2119,25 @@ App::patch('/v1/account/email')
->setAttribute('passwordUpdate', DateTime::now());
}
$target = $dbForProject->findOne('targets', [
$target = Authorization::skip(fn () => $dbForProject->findOne('targets', [
Query::equal('identifier', [$email]),
]);
]));
if ($target && !$target->isEmpty()) {
if ($target instanceof Document && !$target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
}
/**
* @var Document $oldTarget
*/
$oldTarget = $user->find('identifier', $oldEmail, 'targets');
if ($oldTarget !== false && !$oldTarget->isEmpty()) {
$dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email));
}
try {
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
/**
* @var Document $oldTarget
*/
$oldTarget = $user->find('identifier', $oldEmail, 'targets');
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
Authorization::skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email)));
}
$dbForProject->deleteCachedDocument('users', $user->getId());
} catch (Duplicate) {
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
}
@ -2165,18 +2182,15 @@ App::patch('/v1/account/phone')
throw new Exception(Exception::USER_INVALID_CREDENTIALS);
}
$target = $dbForProject->findOne('targets', [
$target = Authorization::skip(fn () => $dbForProject->findOne('targets', [
Query::equal('identifier', [$phone]),
]);
]));
if ($target && !$target->isEmpty()) {
if ($target instanceof Document && !$target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
}
/**
* @var Document $oldTarget
*/
$oldTarget = $user->find('identifier', $user->getAttribute('phone'), 'targets');
$oldPhone = $user->getAttribute('phone');
$user
->setAttribute('phone', $phone)
@ -2191,12 +2205,17 @@ App::patch('/v1/account/phone')
->setAttribute('passwordUpdate', DateTime::now());
}
if ($oldTarget !== false && !$oldTarget->isEmpty()) {
$dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone));
}
try {
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
/**
* @var Document $oldTarget
*/
$oldTarget = $user->find('identifier', $oldPhone, 'targets');
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
Authorization::skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone)));
}
$dbForProject->deleteCachedDocument('users', $user->getId());
} catch (Duplicate $th) {
throw new Exception(Exception::USER_PHONE_ALREADY_EXISTS);
}
@ -3081,7 +3100,7 @@ App::post('/v1/account/verification/phone')
$queueForMessaging
->setMessage($messageDoc)
->setRecipients([$user->getAttribute('phone')])
->setProviderType('SMS')
->setProviderType(MESSAGE_TYPE_SMS)
->setProject($project)
->trigger();

File diff suppressed because it is too large Load diff

View file

@ -99,6 +99,42 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
'memberships' => null,
'search' => implode(' ', [$userId, $email, $phone, $name]),
]));
if ($email) {
try {
$target = $dbForProject->createDocument('targets', new Document([
'userId' => $user->getId(),
'userInternalId' => $user->getInternalId(),
'providerType' => 'email',
'identifier' => $email,
]));
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $target]);
} catch (Duplicate) {
$existingTarget = $dbForProject->findOne('targets', [
Query::equal('identifier', [$email]),
]);
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]);
}
}
if ($phone) {
try {
$target = $dbForProject->createDocument('targets', new Document([
'userId' => $user->getId(),
'userInternalId' => $user->getInternalId(),
'providerType' => 'sms',
'identifier' => $phone,
]));
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $target]);
} catch (Duplicate) {
$existingTarget = $dbForProject->findOne('targets', [
Query::equal('identifier', [$phone]),
]);
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]);
}
}
$dbForProject->deleteCachedDocument('users', $user->getId());
} catch (Duplicate $th) {
throw new Exception(Exception::USER_ALREADY_EXISTS);
}
@ -396,18 +432,19 @@ App::post('/v1/users/:userId/targets')
->label('sdk.response.model', Response::MODEL_TARGET)
->param('targetId', '', new CustomId(), 'Target ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('userId', '', new UID(), 'User ID.')
->param('providerType', '', new WhiteList(['email', 'sms', 'push']), 'The target provider type. Can be one of the following: `email`, `sms` or `push`.')
->param('providerType', '', new WhiteList([MESSAGE_TYPE_EMAIL, MESSAGE_TYPE_SMS, MESSAGE_TYPE_PUSH]), 'The target provider type. Can be one of the following: `email`, `sms` or `push`.')
->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)')
->param('providerId', '', new UID(), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true)
->param('name', '', new Text(128), 'Target name. Max length: 128 chars. For example: My Awesome App Galaxy S23.', true)
->inject('queueForEvents')
->inject('response')
->inject('dbForProject')
->action(function (string $targetId, string $userId, string $providerType, string $identifier, string $providerId, Event $queueForEvents, Response $response, Database $dbForProject) {
->action(function (string $targetId, string $userId, string $providerType, string $identifier, string $providerId, string $name, Event $queueForEvents, Response $response, Database $dbForProject) {
$targetId = $targetId == 'unique()' ? ID::unique() : $targetId;
$provider = new Document();
if ($providerType === 'push') {
if ($providerType === MESSAGE_TYPE_PUSH) {
$provider = $dbForProject->getDocument('providers', $providerId);
if ($provider->isEmpty()) {
@ -415,6 +452,25 @@ App::post('/v1/users/:userId/targets')
}
}
switch ($providerType) {
case 'email':
$validator = new Email();
if (!$validator->isValid($identifier)) {
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
}
break;
case MESSAGE_TYPE_SMS:
$validator = new Phone();
if (!$validator->isValid($identifier)) {
throw new Exception(Exception::GENERAL_INVALID_PHONE);
}
break;
case MESSAGE_TYPE_PUSH:
break;
default:
throw new Exception(Exception::PROVIDER_INCORRECT_TYPE);
}
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
@ -436,6 +492,7 @@ App::post('/v1/users/:userId/targets')
'userId' => $userId,
'userInternalId' => $user->getInternalId(),
'identifier' => $identifier,
'name' => ($name !== '') ? $name : null,
]));
} catch (Duplicate) {
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
@ -782,7 +839,7 @@ App::get('/v1/users/:userId/targets')
if ($cursor) {
$targetId = $cursor->getValue();
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
$cursorDocument = $dbForProject->getDocument('targets', $targetId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Target '{$targetId}' for the 'cursor' value not found.");
@ -1100,6 +1157,16 @@ App::patch('/v1/users/:userId/email')
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
}
$target = $dbForProject->findOne('targets', [
Query::equal('identifier', [$email]),
]);
if ($target instanceof Document && !$target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
}
$oldEmail = $user->getAttribute('email');
$user
->setAttribute('email', $email)
->setAttribute('emailVerification', false)
@ -1108,6 +1175,15 @@ App::patch('/v1/users/:userId/email')
try {
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
/**
* @var Document $oldTarget
*/
$oldTarget = $user->find('identifier', $oldEmail, 'targets');
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
$dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email));
}
$dbForProject->deleteCachedDocument('users', $user->getId());
} catch (Duplicate $th) {
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
}
@ -1145,13 +1221,32 @@ App::patch('/v1/users/:userId/phone')
throw new Exception(Exception::USER_NOT_FOUND);
}
$oldPhone = $user->getAttribute('phone');
$user
->setAttribute('phone', $number)
->setAttribute('phoneVerification', false)
;
$target = $dbForProject->findOne('targets', [
Query::equal('identifier', [$number]),
]);
if ($target instanceof Document && !$target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
}
try {
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
/**
* @var Document $oldTarget
*/
$oldTarget = $user->find('identifier', $oldPhone, 'targets');
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
$dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $number));
}
$dbForProject->deleteCachedDocument('users', $user->getId());
} catch (Duplicate $th) {
throw new Exception(Exception::USER_PHONE_ALREADY_EXISTS);
}
@ -1247,12 +1342,13 @@ App::patch('/v1/users/:userId/targets/:targetId')
->label('sdk.response.model', Response::MODEL_TARGET)
->param('userId', '', new UID(), 'User ID.')
->param('targetId', '', new UID(), 'Target ID.')
->param('providerId', '', new UID(), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true)
->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)', true)
->param('providerId', '', new UID(), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true)
->param('name', '', new Text(128), 'Target name. Max length: 128 chars. For example: My Awesome App Galaxy S23.', true)
->inject('queueForEvents')
->inject('response')
->inject('dbForProject')
->action(function (string $userId, string $targetId, string $providerId, string $identifier, Event $queueForEvents, Response $response, Database $dbForProject) {
->action(function (string $userId, string $targetId, string $identifier, string $providerId, string $name, Event $queueForEvents, Response $response, Database $dbForProject) {
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
@ -1270,6 +1366,27 @@ App::patch('/v1/users/:userId/targets/:targetId')
}
if ($identifier) {
$providerType = $target->getAttribute('providerType');
switch ($providerType) {
case 'email':
$validator = new Email();
if (!$validator->isValid($identifier)) {
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
}
break;
case MESSAGE_TYPE_SMS:
$validator = new Phone();
if (!$validator->isValid($identifier)) {
throw new Exception(Exception::GENERAL_INVALID_PHONE);
}
break;
case MESSAGE_TYPE_PUSH:
break;
default:
throw new Exception(Exception::PROVIDER_INCORRECT_TYPE);
}
$target->setAttribute('identifier', $identifier);
}
@ -1280,10 +1397,18 @@ App::patch('/v1/users/:userId/targets/:targetId')
throw new Exception(Exception::PROVIDER_NOT_FOUND);
}
if ($provider->getAttribute('type') !== $target->getAttribute('providerType')) {
throw new Exception(Exception::PROVIDER_INCORRECT_TYPE);
}
$target->setAttribute('providerId', $provider->getId());
$target->setAttribute('providerInternalId', $provider->getInternalId());
}
if ($name) {
$target->setAttribute('name', $name);
}
$target = $dbForProject->updateDocument('targets', $target->getId(), $target);
$dbForProject->deleteCachedDocument('users', $user->getId());

View file

@ -190,6 +190,10 @@ const MAX_OUTPUT_CHUNK_SIZE = 2 * 1024 * 1024; // 2MB
// Function headers
const FUNCTION_ALLOWLIST_HEADERS_REQUEST = ['content-type', 'agent', 'content-length', 'host'];
const FUNCTION_ALLOWLIST_HEADERS_RESPONSE = ['content-type', 'content-length'];
// Message types
const MESSAGE_TYPE_EMAIL = 'email';
const MESSAGE_TYPE_SMS = 'sms';
const MESSAGE_TYPE_PUSH = 'push';
// Usage metrics
const METRIC_TEAMS = 'teams';
const METRIC_USERS = 'users';
@ -605,13 +609,14 @@ Database::addFilter(
];
$data = \json_decode($message->getAttribute('data', []), true);
$providerType = $message->getAttribute('providerType', '');
if (\array_key_exists('subject', $data)) {
$searchValues = \array_merge($searchValues, [$data['subject'], 'email']);
} elseif (\array_key_exists('content', $data)) {
$searchValues = \array_merge($searchValues, [$data['content'], 'sms']);
if ($providerType === MESSAGE_TYPE_EMAIL) {
$searchValues = \array_merge($searchValues, [$data['subject'], MESSAGE_TYPE_EMAIL]);
} elseif ($providerType === MESSAGE_TYPE_SMS) {
$searchValues = \array_merge($searchValues, [$data['content'], MESSAGE_TYPE_SMS]);
} else {
$searchValues = \array_merge($searchValues, [$data['title'], 'push']);
$searchValues = \array_merge($searchValues, [$data['title'], MESSAGE_TYPE_PUSH]);
}
$search = \implode(' ', \array_filter($searchValues));

View file

@ -521,14 +521,20 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_SMS_PROVIDER
- _APP_SMS_FROM
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_SMS_FROM
- _APP_SMS_PROVIDER
appwrite-worker-migrations:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>

75
composer.lock generated
View file

@ -156,11 +156,11 @@
},
{
"name": "appwrite/php-runtimes",
"version": "0.13.1",
"version": "0.13.2",
"source": {
"type": "git",
"url": "https://github.com/appwrite/runtimes.git",
"reference": "b584d19cdcd82737d0ee5c34d23de791f5ed3610"
"reference": "214a37c2c66e0f2bc9c30fdfde66955d9fd084a1"
},
"require": {
"php": ">=8.0",
@ -195,7 +195,7 @@
"php",
"runtimes"
],
"time": "2023-10-16T15:39:53+00:00"
"time": "2023-11-22T15:36:00+00:00"
},
{
"name": "chillerlan/php-qrcode",
@ -1465,7 +1465,7 @@
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.3.0",
"version": "v3.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
@ -1512,7 +1512,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0"
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0"
},
"funding": [
{
@ -2217,16 +2217,16 @@
},
{
"name": "utopia-php/logger",
"version": "0.3.1",
"version": "0.3.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/logger.git",
"reference": "de623f1ec1c672c795d113dd25c5bf212f7ef4fc"
"reference": "ba763c10688fe2ed715ad2bed3f13d18dfec6253"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/logger/zipball/de623f1ec1c672c795d113dd25c5bf212f7ef4fc",
"reference": "de623f1ec1c672c795d113dd25c5bf212f7ef4fc",
"url": "https://api.github.com/repos/utopia-php/logger/zipball/ba763c10688fe2ed715ad2bed3f13d18dfec6253",
"reference": "ba763c10688fe2ed715ad2bed3f13d18dfec6253",
"shasum": ""
},
"require": {
@ -2264,9 +2264,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/logger/issues",
"source": "https://github.com/utopia-php/logger/tree/0.3.1"
"source": "https://github.com/utopia-php/logger/tree/0.3.2"
},
"time": "2023-02-10T15:52:50+00:00"
"time": "2023-11-22T14:45:43+00:00"
},
{
"name": "utopia-php/messaging",
@ -3136,16 +3136,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "0.35.2",
"version": "0.35.3",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "2dfe0430a64ffd2a07078d83b20144b871acac3b"
"reference": "4c431d5324a8f8cd2cab9a5515c170a5b427d44c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/2dfe0430a64ffd2a07078d83b20144b871acac3b",
"reference": "2dfe0430a64ffd2a07078d83b20144b871acac3b",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/4c431d5324a8f8cd2cab9a5515c170a5b427d44c",
"reference": "4c431d5324a8f8cd2cab9a5515c170a5b427d44c",
"shasum": ""
},
"require": {
@ -3181,9 +3181,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/0.35.2"
"source": "https://github.com/appwrite/sdk-generator/tree/0.35.3"
},
"time": "2023-09-14T14:59:50+00:00"
"time": "2023-11-12T05:56:27+00:00"
},
{
"name": "doctrine/deprecations",
@ -3890,16 +3890,16 @@
},
{
"name": "phpstan/phpdoc-parser",
"version": "1.24.2",
"version": "1.24.4",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
"reference": "bcad8d995980440892759db0c32acae7c8e79442"
"reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bcad8d995980440892759db0c32acae7c8e79442",
"reference": "bcad8d995980440892759db0c32acae7c8e79442",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6bd0c26f3786cd9b7c359675cb789e35a8e07496",
"reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496",
"shasum": ""
},
"require": {
@ -3931,9 +3931,9 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.2"
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.4"
},
"time": "2023-09-26T12:28:12+00:00"
"time": "2023-11-26T18:29:22+00:00"
},
{
"name": "phpunit/php-code-coverage",
@ -5676,16 +5676,16 @@
},
{
"name": "theseer/tokenizer",
"version": "1.2.1",
"version": "1.2.2",
"source": {
"type": "git",
"url": "https://github.com/theseer/tokenizer.git",
"reference": "34a41e998c2183e22995f158c581e7b5e755ab9e"
"reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e",
"reference": "34a41e998c2183e22995f158c581e7b5e755ab9e",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
"reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
"shasum": ""
},
"require": {
@ -5714,7 +5714,7 @@
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"support": {
"issues": "https://github.com/theseer/tokenizer/issues",
"source": "https://github.com/theseer/tokenizer/tree/1.2.1"
"source": "https://github.com/theseer/tokenizer/tree/1.2.2"
},
"funding": [
{
@ -5722,30 +5722,31 @@
"type": "github"
}
],
"time": "2021-07-28T10:34:58+00:00"
"time": "2023-11-20T00:12:19+00:00"
},
{
"name": "twig/twig",
"version": "v3.7.1",
"version": "v3.8.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554"
"reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/a0ce373a0ca3bf6c64b9e3e2124aca502ba39554",
"reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d",
"reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3"
"symfony/polyfill-mbstring": "^1.3",
"symfony/polyfill-php80": "^1.22"
},
"require-dev": {
"psr/container": "^1.0|^2.0",
"symfony/phpunit-bridge": "^5.4.9|^6.3"
"symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0"
},
"type": "library",
"autoload": {
@ -5781,7 +5782,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.7.1"
"source": "https://github.com/twigphp/Twig/tree/v3.8.0"
},
"funding": [
{
@ -5793,7 +5794,7 @@
"type": "tidelift"
}
],
"time": "2023-08-28T11:09:02+00:00"
"time": "2023-11-21T18:54:41+00:00"
}
],
"aliases": [],

View file

@ -11,7 +11,7 @@ class Messaging extends Event
protected ?string $messageId = null;
protected ?Document $message = null;
protected ?array $recipients = null;
protected ?string $deliveryTime = null;
protected ?string $scheduledAt = null;
protected ?string $providerType = null;
@ -117,14 +117,14 @@ class Messaging extends Event
}
/**
* Sets Delivery time for the messaging event.
* Sets Scheduled delivery time for the messaging event.
*
* @param string $deliveryTime
* @param string $scheduledAt
* @return self
*/
public function setDeliveryTime(string $deliveryTime): self
public function setScheduledAt(string $scheduledAt): self
{
$this->deliveryTime = $deliveryTime;
$this->scheduledAt = $scheduledAt;
return $this;
}
@ -134,9 +134,9 @@ class Messaging extends Event
*
* @return string
*/
public function getDeliveryTime(): string
public function getScheduledAt(): string
{
return $this->deliveryTime;
return $this->scheduledAt;
}
/**

View file

@ -55,6 +55,8 @@ class Exception extends \Exception
public const GENERAL_CODES_DISABLED = 'general_codes_disabled';
public const GENERAL_USAGE_DISABLED = 'general_usage_disabled';
public const GENERAL_NOT_IMPLEMENTED = 'general_not_implemented';
public const GENERAL_INVALID_EMAIL = 'general_invalid_email';
public const GENERAL_INVALID_PHONE = 'general_invalid_phone';
/** Users */
public const USER_COUNT_EXCEEDED = 'user_count_exceeded';
@ -254,6 +256,10 @@ class Exception extends \Exception
public const MESSAGE_MISSING_TARGET = 'message_missing_target';
public const MESSAGE_ALREADY_SENT = 'message_already_sent';
public const MESSAGE_ALREADY_SCHEDULED = 'message_already_scheduled';
public const MESSAGE_TARGET_NOT_EMAIL = 'message_target_not_email';
public const MESSAGE_TARGET_NOT_SMS = 'message_target_not_sms';
public const MESSAGE_TARGET_NOT_PUSH = 'message_target_not_push';
protected string $type = '';
protected array $errors = [];

View file

@ -556,6 +556,11 @@ class Deletes extends Action
$this->deleteByGroup('identities', [
Query::equal('userInternalId', [$userInternalId])
], $dbForProject);
// Delete targets
$this->deleteByGroup('targets', [
Query::equal('userInternalId', [$userInternalId])
], $dbForProject);
}
/**

View file

@ -17,7 +17,7 @@ use Utopia\Messaging\Adapters\SMS as SMSAdapter;
use Utopia\Messaging\Adapters\SMS\Mock;
use Utopia\Messaging\Adapters\SMS\Msg91;
use Utopia\Messaging\Adapters\SMS\Telesign;
use Utopia\Messaging\Adapters\SMS\TextMagic;
use Utopia\Messaging\Adapters\SMS\Textmagic;
use Utopia\Messaging\Adapters\SMS\Twilio;
use Utopia\Messaging\Adapters\SMS\Vonage;
use Utopia\Messaging\Adapters\Push as PushAdapter;
@ -67,7 +67,7 @@ class Messaging extends Action
}
if (!\is_null($payload['message']) && !\is_null($payload['recipients'])) {
if ($payload['providerType'] === 'SMS') {
if ($payload['providerType'] === MESSAGE_TYPE_SMS) {
$this->processInternalSMSMessage(new Document($payload['message']), $payload['recipients']);
}
} else {
@ -91,14 +91,16 @@ class Messaging extends Action
if (\count($topicsId) > 0) {
$topics = $dbForProject->find('topics', [Query::equal('$id', $topicsId)]);
foreach ($topics as $topic) {
$recipients = \array_merge($recipients, $topic->getAttribute('targets'));
$targets = \array_filter($topic->getAttribute('targets'), fn (Document $target) => $target->getAttribute('providerType') === $message->getAttribute('providerType'));
$recipients = \array_merge($recipients, $targets);
}
}
if (\count($usersId) > 0) {
$users = $dbForProject->find('users', [Query::equal('$id', $usersId)]);
foreach ($users as $user) {
$recipients = \array_merge($recipients, $user->getAttribute('targets'));
$targets = \array_filter($user->getAttribute('targets'), fn (Document $target) => $target->getAttribute('providerType') === $message->getAttribute('providerType'));
$recipients = \array_merge($recipients, $targets);
}
}
@ -107,7 +109,7 @@ class Messaging extends Action
$recipients = \array_merge($recipients, $targets);
}
$internalProvider = $dbForProject->findOne('providers', [
$primaryProvider = $dbForProject->findOne('providers', [
Query::equal('enabled', [true]),
Query::equal('type', [$recipients[0]->getAttribute('providerType')]),
]);
@ -124,36 +126,42 @@ class Messaging extends Action
foreach ($recipients as $recipient) {
$providerId = $recipient->getAttribute('providerId');
if (!$providerId) {
$providerId = $internalProvider->getId();
if (!$providerId && $primaryProvider instanceof Document && !$primaryProvider->isEmpty()) {
$providerId = $primaryProvider->getId();
}
if (!isset($identifiersByProviderId[$providerId])) {
$identifiersByProviderId[$providerId] = [];
if ($providerId) {
if (!isset($identifiersByProviderId[$providerId])) {
$identifiersByProviderId[$providerId] = [];
}
$identifiersByProviderId[$providerId][] = $recipient->getAttribute('identifier');
}
$identifiersByProviderId[$providerId][] = $recipient->getAttribute('identifier');
}
/**
* @var array[] $results
*/
$results = batch(\array_map(function ($providerId) use ($identifiersByProviderId, $providers, $internalProvider, $message, $dbForProject) {
return function () use ($providerId, $identifiersByProviderId, $providers, $internalProvider, $message, $dbForProject) {
$results = batch(\array_map(function ($providerId) use ($identifiersByProviderId, $providers, $primaryProvider, $message, $dbForProject) {
return function () use ($providerId, $identifiersByProviderId, $providers, $primaryProvider, $message, $dbForProject) {
$provider = new Document();
if ($internalProvider->getId() === $providerId) {
$provider = $internalProvider;
if ($primaryProvider->getId() === $providerId) {
$provider = $primaryProvider;
} else {
$provider = $dbForProject->getDocument('providers', $providerId);
$provider = $dbForProject->getDocument('providers', $providerId, [Query::equal('enabled', [true])]);
if ($provider->isEmpty()) {
$provider = $primaryProvider;
}
}
$providers[] = $provider;
$identifiers = $identifiersByProviderId[$providerId];
$adapter = match ($provider->getAttribute('type')) {
'sms' => $this->sms($provider),
'push' => $this->push($provider),
'email' => $this->email($provider),
MESSAGE_TYPE_SMS => $this->sms($provider),
MESSAGE_TYPE_PUSH => $this->push($provider),
MESSAGE_TYPE_EMAIL => $this->email($provider),
default => throw new Exception(Exception::PROVIDER_INCORRECT_TYPE)
};
@ -169,9 +177,9 @@ class Messaging extends Action
$messageData->setAttribute('to', $batch);
$data = match ($provider->getAttribute('type')) {
'sms' => $this->buildSMSMessage($messageData, $provider),
'push' => $this->buildPushMessage($messageData),
'email' => $this->buildEmailMessage($messageData, $provider),
MESSAGE_TYPE_SMS => $this->buildSMSMessage($messageData, $provider),
MESSAGE_TYPE_PUSH => $this->buildPushMessage($messageData),
MESSAGE_TYPE_EMAIL => $this->buildEmailMessage($messageData, $provider),
default => throw new Exception(Exception::PROVIDER_INCORRECT_TYPE)
};
@ -241,7 +249,7 @@ class Messaging extends Action
$provider = new Document([
'$id' => ID::unique(),
'provider' => $host,
'type' => 'sms',
'type' => MESSAGE_TYPE_SMS,
'name' => 'Internal SMS',
'enabled' => true,
'credentials' => match ($host) {
@ -303,7 +311,7 @@ class Messaging extends Action
return match ($provider->getAttribute('provider')) {
'mock' => new Mock('username', 'password'),
'twilio' => new Twilio($credentials['accountSid'], $credentials['authToken']),
'textmagic' => new TextMagic($credentials['username'], $credentials['apiKey']),
'textmagic' => new Textmagic($credentials['username'], $credentials['apiKey']),
'telesign' => new Telesign($credentials['username'], $credentials['password']),
'msg91' => new Msg91($credentials['senderId'], $credentials['authKey']),
'vonage' => new Vonage($credentials['apiKey'], $credentials['apiSecret']),

View file

@ -6,7 +6,9 @@ class Subscribers extends Base
{
public const ALLOWED_ATTRIBUTES = [
'targetId',
'topicId'
'topicId',
'userId',
'providerType'
];
/**

View file

@ -5,8 +5,9 @@ namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
use Utopia\Database\DateTime;
use Utopia\Database\Document as DatabaseDocument;
class Message extends Any
class Message extends Model
{
public function __construct()
{
@ -29,6 +30,12 @@ class Message extends Any
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('providerType', [
'type' => self::TYPE_STRING,
'description' => 'Message provider type.',
'default' => '',
'example' => MESSAGE_TYPE_EMAIL,
])
->addRule('topics', [
'type' => self::TYPE_STRING,
'description' => 'Topic IDs set as recipients.',
@ -50,7 +57,7 @@ class Message extends Any
'array' => true,
'example' => ['5e5ea5c16897e'],
])
->addRule('deliveryTime', [
->addRule('scheduledAt', [
'type' => self::TYPE_DATETIME,
'description' => 'The scheduled time for message.',
'required' => false,
@ -91,7 +98,7 @@ class Message extends Any
'type' => self::TYPE_STRING,
'description' => 'Status of delivery.',
'default' => 'processing',
'example' => 'Message status can be one of the following: processing, sent, failed.',
'example' => 'Message status can be one of the following: processing, sent, cancelled, failed.',
])
->addRule('description', [
'type' => self::TYPE_STRING,

View file

@ -50,7 +50,7 @@ class Provider extends Model
'type' => self::TYPE_STRING,
'description' => 'Type of provider.',
'default' => '',
'example' => 'sms',
'example' => MESSAGE_TYPE_SMS,
])
->addRule('credentials', [
'type' => self::TYPE_JSON,

View file

@ -34,9 +34,24 @@ class Subscriber extends Model
'default' => '',
'example' => '259125845563242502',
])
->addRule('target', [
'type' => Response::MODEL_TARGET,
'description' => 'Target.',
'default' => [],
'example' => [
'$id' => '259125845563242502',
'$createdAt' => self::TYPE_DATETIME_EXAMPLE,
'$updatedAt' => self::TYPE_DATETIME_EXAMPLE,
'providerType' => 'email',
'providerId' => '259125845563242502',
'name' => 'ageon-app-email',
'identifier' => 'random-mail@email.org',
'userId' => '5e5ea5c16897e',
],
])
->addRule('userId', [
'type' => self::TYPE_STRING,
'description' => 'User ID.',
'description' => 'Topic ID.',
'default' => '',
'example' => '5e5ea5c16897e',
])
@ -51,6 +66,12 @@ class Subscriber extends Model
'description' => 'Topic ID.',
'default' => '',
'example' => '259125845563242502',
])
->addRule('providerType', [
'type' => self::TYPE_STRING,
'description' => 'The target provider type. Can be one of the following: `email`, `sms` or `push`.',
'default' => '',
'example' => MESSAGE_TYPE_EMAIL,
]);
}

View file

@ -28,6 +28,12 @@ class Target extends Model
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('name', [
'type' => self::TYPE_STRING,
'description' => 'Target Name.',
'default' => '',
'example' => 'Aegon apple token',
])
->addRule('userId', [
'type' => self::TYPE_STRING,
'description' => 'User ID.',
@ -45,7 +51,7 @@ class Target extends Model
'type' => self::TYPE_STRING,
'description' => 'The target provider type. Can be one of the following: `email`, `sms` or `push`.',
'default' => '',
'example' => 'email',
'example' => MESSAGE_TYPE_EMAIL,
])
->addRule('identifier', [
'type' => self::TYPE_STRING,

View file

@ -1830,8 +1830,8 @@ trait Base
}
}';
case self::$CREATE_TEXTMAGIC_PROVIDER:
return 'mutation createTextMagicProvider($providerId: String!, $name: String!, $from: String!, $username: String!, $apiKey: String!) {
messagingCreateTextMagicProvider(providerId: $providerId, name: $name, from: $from, username: $username, apiKey: $apiKey) {
return 'mutation createTextmagicProvider($providerId: String!, $name: String!, $from: String!, $username: String!, $apiKey: String!) {
messagingCreateTextmagicProvider(providerId: $providerId, name: $name, from: $from, username: $username, apiKey: $apiKey) {
_id
name
provider
@ -1944,8 +1944,8 @@ trait Base
}
}';
case self::$UPDATE_TEXTMAGIC_PROVIDER:
return 'mutation updateTextMagicProvider($providerId: String!, $name: String!, $username: String!, $apiKey: String!) {
messagingUpdateTextMagicProvider(providerId: $providerId, name: $name, username: $username, apiKey: $apiKey) {
return 'mutation updateTextmagicProvider($providerId: String!, $name: String!, $username: String!, $apiKey: String!) {
messagingUpdateTextmagicProvider(providerId: $providerId, name: $name, username: $username, apiKey: $apiKey) {
_id
name
provider
@ -2044,9 +2044,16 @@ trait Base
return 'mutation createSubscriber($subscriberId: String!, $targetId: String!, $topicId: String!) {
messagingCreateSubscriber(subscriberId: $subscriberId, targetId: $targetId, topicId: $topicId) {
_id
userId
targetId
topicId
userName
target {
_id
userId
name
providerType
identifier
}
}
}';
case self::$LIST_SUBSCRIBERS:
@ -2055,9 +2062,16 @@ trait Base
total
subscribers {
_id
userId
targetId
topicId
userName
target {
_id
userId
name
providerType
identifier
}
}
}
}';
@ -2065,9 +2079,16 @@ trait Base
return 'query getSubscriber($topicId: String!, $subscriberId: String!) {
messagingGetSubscriber(topicId: $topicId, subscriberId: $subscriberId) {
_id
userId
targetId
topicId
userName
target {
_id
userId
name
providerType
identifier
}
}
}';
case self::$DELETE_SUBSCRIBER:
@ -2077,13 +2098,13 @@ trait Base
}
}';
case self::$CREATE_EMAIL:
return 'mutation createEmail($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $subject: String!, $content: String!, $status: String, $description: String, $html: Boolean, $deliveryTime: String) {
messagingCreateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, description: $description, html: $html, deliveryTime: $deliveryTime) {
return 'mutation createEmail($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $subject: String!, $content: String!, $status: String, $description: String, $html: Boolean, $scheduledAt: String) {
messagingCreateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, description: $description, html: $html, scheduledAt: $scheduledAt) {
_id
topics
users
targets
deliveryTime
scheduledAt
deliveredAt
deliveryErrors
deliveredTotal
@ -2092,13 +2113,13 @@ trait Base
}
}';
case self::$CREATE_SMS:
return 'mutation createSMS($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $content: String!, $status: String, $description: String, $deliveryTime: String) {
messagingCreateSMS(messageId: $messageId, topics: $topics, users: $users, targets: $targets, content: $content, status: $status, description: $description, deliveryTime: $deliveryTime) {
return 'mutation createSMS($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $content: String!, $status: String, $description: String, $scheduledAt: String) {
messagingCreateSMS(messageId: $messageId, topics: $topics, users: $users, targets: $targets, content: $content, status: $status, description: $description, scheduledAt: $scheduledAt) {
_id
topics
users
targets
deliveryTime
scheduledAt
deliveredAt
deliveryErrors
deliveredTotal
@ -2107,13 +2128,13 @@ trait Base
}
}';
case self::$CREATE_PUSH_NOTIFICATION:
return 'mutation createPushNotification($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $title: String!, $body: String!, $data: Json, $action: String, $icon: String, $sound: String, $color: String, $tag: String, $badge: String, $status: String, $description: String, $deliveryTime: String) {
messagingCreatePushNotification(messageId: $messageId, topics: $topics, users: $users, targets: $targets, title: $title, body: $body, data: $data, action: $action, icon: $icon, sound: $sound, color: $color, tag: $tag, badge: $badge, status: $status, description: $description, deliveryTime: $deliveryTime) {
return 'mutation createPushNotification($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $title: String!, $body: String!, $data: Json, $action: String, $icon: String, $sound: String, $color: String, $tag: String, $badge: String, $status: String, $description: String, $scheduledAt: String) {
messagingCreatePushNotification(messageId: $messageId, topics: $topics, users: $users, targets: $targets, title: $title, body: $body, data: $data, action: $action, icon: $icon, sound: $sound, color: $color, tag: $tag, badge: $badge, status: $status, description: $description, scheduledAt: $scheduledAt) {
_id
topics
users
targets
deliveryTime
scheduledAt
deliveredAt
deliveryErrors
deliveredTotal
@ -2127,10 +2148,11 @@ trait Base
total
messages {
_id
providerType
topics
users
targets
deliveryTime
scheduledAt
deliveredAt
deliveryErrors
deliveredTotal
@ -2143,10 +2165,11 @@ trait Base
return 'query getMessage($messageId: String!) {
messagingGetMessage(messageId: $messageId) {
_id
providerType
topics
users
targets
deliveryTime
scheduledAt
deliveredAt
deliveryErrors
deliveredTotal
@ -2155,13 +2178,13 @@ trait Base
}
}';
case self::$UPDATE_EMAIL:
return 'mutation updateEmail($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $subject: String, $content: String, $status: String, $description: String, $html: Boolean, $deliveryTime: String) {
messagingUpdateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, description: $description, html: $html, deliveryTime: $deliveryTime) {
return 'mutation updateEmail($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $subject: String, $content: String, $status: String, $description: String, $html: Boolean, $scheduledAt: String) {
messagingUpdateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, description: $description, html: $html, scheduledAt: $scheduledAt) {
_id
topics
users
targets
deliveryTime
scheduledAt
deliveredAt
deliveryErrors
deliveredTotal
@ -2170,13 +2193,13 @@ trait Base
}
}';
case self::$UPDATE_SMS:
return 'mutation updateSMS($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $content: String, $status: String, $description: String, $deliveryTime: String) {
messagingUpdateSMS(messageId: $messageId, topics: $topics, users: $users, targets: $targets, content: $content, status: $status, description: $description, deliveryTime: $deliveryTime) {
return 'mutation updateSMS($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $content: String, $status: String, $description: String, $scheduledAt: String) {
messagingUpdateSMS(messageId: $messageId, topics: $topics, users: $users, targets: $targets, content: $content, status: $status, description: $description, scheduledAt: $scheduledAt) {
_id
topics
users
targets
deliveryTime
scheduledAt
deliveredAt
deliveryErrors
deliveredTotal
@ -2185,13 +2208,13 @@ trait Base
}
}';
case self::$UPDATE_PUSH_NOTIFICATION:
return 'mutation updatePushNotification($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $title: String, $body: String, $data: Json, $action: String, $icon: String, $sound: String, $color: String, $tag: String, $badge: String, $status: String, $description: String, $deliveryTime: String) {
messagingUpdatePushNotification(messageId: $messageId, topics: $topics, users: $users, targets: $targets, title: $title, body: $body, data: $data, action: $action, icon: $icon, sound: $sound, color: $color, tag: $tag, badge: $badge, status: $status, description: $description, deliveryTime: $deliveryTime) {
return 'mutation updatePushNotification($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $title: String, $body: String, $data: Json, $action: String, $icon: String, $sound: String, $color: String, $tag: String, $badge: String, $status: String, $description: String, $scheduledAt: String) {
messagingUpdatePushNotification(messageId: $messageId, topics: $topics, users: $users, targets: $targets, title: $title, body: $body, data: $data, action: $action, icon: $icon, sound: $sound, color: $color, tag: $tag, badge: $badge, status: $status, description: $description, scheduledAt: $scheduledAt) {
_id
topics
users
targets
deliveryTime
scheduledAt
deliveredAt
deliveryErrors
deliveredTotal
@ -2451,7 +2474,7 @@ trait Base
protected string $stdout = '';
protected string $stderr = '';
protected function packageCode($folder)
protected function packageCode($folder): void
{
Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr);
}

View file

@ -47,7 +47,7 @@ class MessagingTest extends Scope
'password' => 'my-password',
'from' => '+123456789',
],
'TextMagic' => [
'Textmagic' => [
'providerId' => ID::unique(),
'name' => 'Textmagic1',
'username' => 'my-username',
@ -134,7 +134,7 @@ class MessagingTest extends Scope
'username' => 'my-username',
'password' => 'my-password',
],
'TextMagic' => [
'Textmagic' => [
'providerId' => $providers[4]['_id'],
'name' => 'Textmagic2',
'username' => 'my-username',
@ -398,7 +398,7 @@ class MessagingTest extends Scope
'providerType' => 'email',
'userId' => $userId,
'providerId' => $providerId,
'identifier' => 'token',
'identifier' => 'random-email@mail.org',
],
];
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
@ -409,7 +409,7 @@ class MessagingTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($userId, $response['body']['data']['usersCreateTarget']['userId']);
$this->assertEquals('token', $response['body']['data']['usersCreateTarget']['identifier']);
$this->assertEquals('random-email@mail.org', $response['body']['data']['usersCreateTarget']['identifier']);
$targetId = $response['body']['data']['usersCreateTarget']['_id'];
@ -430,7 +430,7 @@ class MessagingTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($response['body']['data']['messagingCreateSubscriber']['topicId'], $topicId);
$this->assertEquals($response['body']['data']['messagingCreateSubscriber']['targetId'], $targetId);
$this->assertEquals($response['body']['data']['messagingCreateSubscriber']['userId'], $userId);
$this->assertEquals($response['body']['data']['messagingCreateSubscriber']['target']['userId'], $userId);
return $response['body']['data']['messagingCreateSubscriber'];
}
@ -454,9 +454,9 @@ class MessagingTest extends Scope
]), $graphQLPayload);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($response['body']['data']['messagingListSubscribers']['subscribers'][0]['topicId'], $subscriber['topicId']);
$this->assertEquals($response['body']['data']['messagingListSubscribers']['subscribers'][0]['targetId'], $subscriber['targetId']);
$this->assertEquals($response['body']['data']['messagingListSubscribers']['subscribers'][0]['userId'], $subscriber['userId']);
$this->assertEquals($subscriber['topicId'], $response['body']['data']['messagingListSubscribers']['subscribers'][0]['topicId']);
$this->assertEquals($subscriber['targetId'], $response['body']['data']['messagingListSubscribers']['subscribers'][0]['targetId']);
$this->assertEquals($subscriber['target']['userId'], $response['body']['data']['messagingListSubscribers']['subscribers'][0]['target']['userId']);
$this->assertEquals(1, \count($response['body']['data']['messagingListSubscribers']['subscribers']));
}
@ -487,7 +487,7 @@ class MessagingTest extends Scope
$this->assertEquals($subscriberId, $response['body']['data']['messagingGetSubscriber']['_id']);
$this->assertEquals($topicId, $response['body']['data']['messagingGetSubscriber']['topicId']);
$this->assertEquals($subscriber['targetId'], $response['body']['data']['messagingGetSubscriber']['targetId']);
$this->assertEquals($subscriber['userId'], $response['body']['data']['messagingGetSubscriber']['userId']);
$this->assertEquals($subscriber['target']['userId'], $response['body']['data']['messagingGetSubscriber']['target']['userId']);
}
/**

View file

@ -80,7 +80,7 @@ class UsersTest extends Scope
'userId' => $user['_id'],
'providerType' => 'email',
'providerId' => $providerId,
'identifier' => 'identifier',
'identifier' => 'random-email@mail.org',
]
];
@ -90,7 +90,7 @@ class UsersTest extends Scope
], $this->getHeaders()), $graphQLPayload);
$this->assertEquals(200, $target['headers']['status-code']);
$this->assertEquals('identifier', $target['body']['data']['usersCreateTarget']['identifier']);
$this->assertEquals('random-email@mail.org', $target['body']['data']['usersCreateTarget']['identifier']);
return $target['body']['data']['usersCreateTarget'];
}
@ -247,7 +247,7 @@ class UsersTest extends Scope
$this->assertEquals(200, $targets['headers']['status-code']);
$this->assertIsArray($targets['body']['data']['usersListTargets']);
$this->assertCount(1, $targets['body']['data']['usersListTargets']['targets']);
$this->assertCount(2, $targets['body']['data']['usersListTargets']['targets']);
}
/**
@ -271,7 +271,7 @@ class UsersTest extends Scope
], $this->getHeaders()), $graphQLPayload);
$this->assertEquals(200, $target['headers']['status-code']);
$this->assertEquals('identifier', $target['body']['data']['usersGetTarget']['identifier']);
$this->assertEquals('random-email@mail.org', $target['body']['data']['usersGetTarget']['identifier']);
}
public function testUpdateUserStatus()
@ -470,7 +470,7 @@ class UsersTest extends Scope
'variables' => [
'userId' => $target['userId'],
'targetId' => $target['_id'],
'identifier' => 'newidentifier',
'identifier' => 'random-email1@mail.org',
],
];
@ -480,7 +480,7 @@ class UsersTest extends Scope
], $this->getHeaders()), $graphQLPayload);
$this->assertEquals(200, $target['headers']['status-code']);
$this->assertEquals('newidentifier', $target['body']['data']['usersUpdateTarget']['identifier']);
$this->assertEquals('random-email1@mail.org', $target['body']['data']['usersUpdateTarget']['identifier']);
}
public function testDeleteUserSessions()

View file

@ -319,7 +319,7 @@ trait MessagingBase
'targetId' => ID::unique(),
'providerType' => 'email',
'providerId' => $provider['body']['$id'],
'identifier' => 'my-token',
'identifier' => 'random-email@mail.org',
]);
$this->assertEquals(201, $target['headers']['status-code']);
@ -333,7 +333,8 @@ trait MessagingBase
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertEquals($target['body']['userId'], $response['body']['userId']);
$this->assertEquals($target['body']['userId'], $response['body']['target']['userId']);
$this->assertEquals($target['body']['providerType'], $response['body']['target']['providerType']);
$topic = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['$id'], [
'content-type' => 'application/json',
@ -350,7 +351,9 @@ trait MessagingBase
'topicId' => $topic['body']['$id'],
'targetId' => $target['body']['$id'],
'userId' => $target['body']['userId'],
'subscriberId' => $response['body']['$id']
'subscriberId' => $response['body']['$id'],
'identifier' => $target['body']['identifier'],
'providerType' => $target['body']['providerType'],
];
}
@ -368,7 +371,9 @@ trait MessagingBase
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($data['topicId'], $response['body']['topicId']);
$this->assertEquals($data['targetId'], $response['body']['targetId']);
$this->assertEquals($data['userId'], $response['body']['userId']);
$this->assertEquals($data['userId'], $response['body']['target']['userId']);
$this->assertEquals($data['providerType'], $response['body']['target']['providerType']);
$this->assertEquals($data['identifier'], $response['body']['target']['identifier']);
}
/**
@ -384,7 +389,9 @@ trait MessagingBase
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(1, $response['body']['total']);
$this->assertEquals($data['userId'], $response['body']['subscribers'][0]['userId']);
$this->assertEquals($data['userId'], $response['body']['subscribers'][0]['target']['userId']);
$this->assertEquals($data['providerType'], $response['body']['subscribers'][0]['target']['providerType']);
$this->assertEquals($data['identifier'], $response['body']['subscribers'][0]['target']['identifier']);
$this->assertEquals(\count($response['body']['subscribers']), $response['body']['total']);
return $data;

View file

@ -23,6 +23,7 @@ trait UsersBase
'password' => 'password',
'name' => 'Cristiano Ronaldo',
], false);
$this->assertEquals($user['headers']['status-code'], 201);
// Test empty prefs is object not array
$bodyString = $user['body'];
@ -1245,11 +1246,11 @@ trait UsersBase
'targetId' => ID::unique(),
'providerId' => $provider['body']['$id'],
'providerType' => 'email',
'identifier' => 'my-token',
'identifier' => 'random-email@mail.org',
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertEquals($provider['body']['$id'], $response['body']['providerId']);
$this->assertEquals('my-token', $response['body']['identifier']);
$this->assertEquals('random-email@mail.org', $response['body']['identifier']);
return $response['body'];
}
@ -1262,10 +1263,10 @@ trait UsersBase
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'identifier' => 'my-updated-token',
'identifier' => 'random-email1@mail.org',
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals('my-updated-token', $response['body']['identifier']);
$this->assertEquals('random-email1@mail.org', $response['body']['identifier']);
return $response['body'];
}
@ -1279,7 +1280,7 @@ trait UsersBase
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(1, \count($response['body']['targets']));
$this->assertEquals(2, \count($response['body']['targets']));
}
/**
@ -1313,7 +1314,7 @@ trait UsersBase
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(0, $response['body']['total']);
$this->assertEquals(1, $response['body']['total']);
}
/**