diff --git a/.env b/.env index 127efd2e4a..07d953af57 100644 --- a/.env +++ b/.env @@ -99,14 +99,6 @@ _APP_VCS_GITHUB_WEBHOOK_SECRET= _APP_MIGRATIONS_FIREBASE_CLIENT_ID= _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET= _APP_ASSISTANT_OPENAI_API_KEY= -_APP_MESSAGE_SMS_PROVIDER_MSG91_SENDER_ID= -_APP_MESSAGE_SMS_PROVIDER_MSG91_AUTH_KEY= -_APP_MESSAGE_SMS_PROVIDER_MSG91_FROM= -_APP_MESSAGE_SMS_PROVIDER_MSG91_TO= -_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_API_KEY= -_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_DOMAIN= -_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_FROM= -_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_RECEIVER_EMAIL= -_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_IS_EU_REGION= -_APP_MESSAGE_PUSH_PROVIDER_FCM_SERVERY_KEY= -_APP_MESSAGE_PUSH_PROVIDER_FCM_RECEIVER_TOKEN= +_APP_MESSAGE_SMS_TEST_DSN= +_APP_MESSAGE_EMAIL_TEST_DSN= +_APP_MESSAGE_PUSH_TEST_DSN= diff --git a/app/config/collections.php b/app/config/collections.php index 54b6b3bec3..33a60fce44 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1417,7 +1417,7 @@ $commonCollections = [ 'filters' => [], ], [ - '$id' => ID::custom('default'), + '$id' => ID::custom('internal'), 'type' => Database::VAR_BOOLEAN, 'signed' => true, 'size' => 0, @@ -1495,16 +1495,16 @@ $commonCollections = [ 'orders' => [Database::ORDER_ASC], ], [ - '$id' => ID::custom('_key_default'), + '$id' => ID::custom('_key_internal'), 'type' => Database::INDEX_KEY, - 'attributes' => ['default'], + 'attributes' => ['internal'], 'lengths' => [], 'orders' => [Database::ORDER_ASC], ], [ - '$id' => ID::custom('_key_default_type'), + '$id' => ID::custom('_key_internal_type'), 'type' => Database::INDEX_KEY, - 'attributes' => ['default','type'], + 'attributes' => ['internal','type'], 'lengths' => [], 'orders' => [Database::ORDER_ASC], ], @@ -1557,13 +1557,35 @@ $commonCollections = [ 'filters' => ['json'], ], [ - '$id' => ID::custom('to'), + '$id' => ID::custom('topics'), 'type' => Database::VAR_STRING, 'format' => '', - 'size' => 65535, + 'size' => 21845, 'signed' => true, - 'required' => true, - 'default' => null, + 'required' => false, + 'default' => [], + 'array' => true, + 'filters' => [], + ], + [ + '$id' => ID::custom('users'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 21845, + 'signed' => true, + 'required' => false, + 'default' => [], + 'array' => true, + 'filters' => [], + ], + [ + '$id' => ID::custom('targets'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 21845, + 'signed' => true, + 'required' => false, + 'default' => [], 'array' => true, 'filters' => [], ], @@ -1601,7 +1623,7 @@ $commonCollections = [ 'filters' => [], ], [ - '$id' => ID::custom('deliveredTo'), + '$id' => ID::custom('deliveredTotal'), 'type' => Database::VAR_INTEGER, 'format' => '', 'size' => 0, @@ -1706,7 +1728,7 @@ $commonCollections = [ [ '$id' => ID::custom('_key_search'), 'type' => Database::INDEX_FULLTEXT, - 'attributes' => ['name'], + 'attributes' => ['search'], 'lengths' => [], 'orders' => [Database::ORDER_ASC], ] diff --git a/app/config/errors.php b/app/config/errors.php index 50c59b23ec..931d24d65c 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -790,6 +790,11 @@ return [ 'description' => 'Message with the requested ID could not be found.', 'code' => 404, ], + Exception::MESSAGE_MISSING_TARGET => [ + 'name' => Exception::MESSAGE_MISSING_TARGET, + 'description' => 'Message with the requested ID is missing a target (Topics or Users or Targets).', + 'code' => 400, + ], Exception::MESSAGE_ALREADY_SENT => [ 'name' => Exception::MESSAGE_ALREADY_SENT, 'description' => 'Message with the requested ID has already been sent.', diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 4c7411d978..faa8623601 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1241,7 +1241,7 @@ App::post('/v1/account/sessions/phone') ->inject('locale') ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale) { $provider = Authorization::skip(fn () => $dbForProject->findOne('providers', [ - Query::equal('default', [true]), + Query::equal('internal', [true]), Query::equal('type', ['sms']) ])); if ($provider === false || $provider->isEmpty()) { @@ -1347,7 +1347,7 @@ App::post('/v1/account/sessions/phone') $messageDoc = $dbForProject->createDocument('messages', new Document([ '$id' => $token->getId(), - 'to' => [$target->getId()], + 'targets' => [$target->getId()], 'data' => [ 'content' => $message, ], @@ -2914,7 +2914,7 @@ App::post('/v1/account/verification/phone') ->inject('locale') ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale) { $provider = Authorization::skip(fn () => $dbForProject->findOne('providers', [ - Query::equal('default', [true]), + Query::equal('internal', [true]), Query::equal('type', ['sms']) ])); if ($provider === false || $provider->isEmpty()) { @@ -2980,7 +2980,7 @@ App::post('/v1/account/verification/phone') $messageDoc = $dbForProject->createDocument('messages', new Document([ '$id' => $verification->getId(), - 'to' => [$target->getId()], + 'targets' => [$target->getId()], 'data' => [ 'content' => $message, ], diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 1dbb371a7f..0207708b8b 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -69,14 +69,14 @@ App::post('/v1/messaging/providers/mailgun') ] ]); - // Check if a default provider exists, if not, set this one as default + // Check if a internal provider exists, if not, set this one as internal if ( empty($dbForProject->findOne('providers', [ - Query::equal('default', [true]), + Query::equal('internal', [true]), Query::equal('type', ['email']) ])) ) { - $provider->setAttribute('default', true); + $provider->setAttribute('internal', true); } try { @@ -127,14 +127,14 @@ App::post('/v1/messaging/providers/sendgrid') ] ]); - // Check if a default provider exists, if not, set this one as default + // Check if a internal provider exists, if not, set this one as internal if ( empty($dbForProject->findOne('providers', [ - Query::equal('default', [true]), + Query::equal('internal', [true]), Query::equal('type', ['sms']) ])) ) { - $provider->setAttribute('default', true); + $provider->setAttribute('internal', true); } try { @@ -187,14 +187,14 @@ App::post('/v1/messaging/providers/msg91') ] ]); - // Check if a default provider exists, if not, set this one as default + // Check if a internal provider exists, if not, set this one as internal if ( empty($dbForProject->findOne('providers', [ - Query::equal('default', [true]), + Query::equal('internal', [true]), Query::equal('type', ['sms']) ])) ) { - $provider->setAttribute('default', true); + $provider->setAttribute('internal', true); } try { @@ -247,14 +247,14 @@ App::post('/v1/messaging/providers/telesign') ] ]); - // Check if a default provider exists, if not, set this one as default + // Check if a internal provider exists, if not, set this one as internal if ( empty($dbForProject->findOne('providers', [ - Query::equal('default', [true]), + Query::equal('internal', [true]), Query::equal('type', ['sms']) ])) ) { - $provider->setAttribute('default', true); + $provider->setAttribute('internal', true); } try { @@ -307,14 +307,14 @@ App::post('/v1/messaging/providers/textmagic') ] ]); - // Check if a default provider exists, if not, set this one as default + // Check if a internal provider exists, if not, set this one as internal if ( empty($dbForProject->findOne('providers', [ - Query::equal('default', [true]), + Query::equal('internal', [true]), Query::equal('type', ['sms']) ])) ) { - $provider->setAttribute('default', true); + $provider->setAttribute('internal', true); } try { @@ -367,14 +367,14 @@ App::post('/v1/messaging/providers/twilio') ] ]); - // Check if a default provider exists, if not, set this one as default + // Check if a internal provider exists, if not, set this one as internal if ( empty($dbForProject->findOne('providers', [ - Query::equal('default', [true]), + Query::equal('internal', [true]), Query::equal('type', ['sms']) ])) ) { - $provider->setAttribute('default', true); + $provider->setAttribute('internal', true); } try { @@ -427,14 +427,14 @@ App::post('/v1/messaging/providers/vonage') ] ]); - // Check if a default provider exists, if not, set this one as default + // Check if a internal provider exists, if not, set this one as internal if ( empty($dbForProject->findOne('providers', [ - Query::equal('default', [true]), + Query::equal('internal', [true]), Query::equal('type', ['sms']) ])) ) { - $provider->setAttribute('default', true); + $provider->setAttribute('internal', true); } try { @@ -481,14 +481,14 @@ App::post('/v1/messaging/providers/fcm') ], ]); - // Check if a default provider exists, if not, set this one as default + // Check if a internal provider exists, if not, set this one as internal if ( empty($dbForProject->findOne('providers', [ - Query::equal('default', [true]), + Query::equal('internal', [true]), Query::equal('type', ['push']) ])) ) { - $provider->setAttribute('default', true); + $provider->setAttribute('internal', true); } try { @@ -543,14 +543,14 @@ App::post('/v1/messaging/providers/apns') ], ]); - // Check if a default provider exists, if not, set this one as default + // Check if a internal provider exists, if not, set this one as internal if ( empty($dbForProject->findOne('providers', [ - Query::equal('default', [true]), + Query::equal('internal', [true]), Query::equal('type', ['push']) ])) ) { - $provider->setAttribute('default', true); + $provider->setAttribute('internal', true); } try { @@ -647,13 +647,14 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) + ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('isEuRegion', null, new Boolean(), 'Set as eu region.', true) ->param('from', '', new Text(256), 'Sender email address.', true) ->param('apiKey', '', new Text(0), 'Mailgun API Key.', true) ->param('domain', '', new Text(0), 'Mailgun Domain.', true) ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $isEuRegion, string $from, string $apiKey, string $domain, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, ?bool $isEuRegion, string $from, string $apiKey, string $domain, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -680,6 +681,10 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') $provider->setAttribute('enabled', $enabled); } + if ($internal === true) { + $provider->setAttribute('internal', $internal); + } + $credentials = $provider->getAttribute('credentials'); if ($isEuRegion === true || $isEuRegion === false) { @@ -698,6 +703,15 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); + if ($internal === true) { + $internalProvider = $dbForProject->findOne('providers', [ + 'internal' => true, + 'type' => 'email', + ]); + $internalProvider->setAttribute('internal', false); + $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); + } + $response ->dynamic($provider, Response::MODEL_PROVIDER); }); @@ -718,11 +732,12 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) + ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('apiKey', '', new Text(0), 'Sendgrid API key.', true) ->param('from', '', new Text(256), 'Sender email address.', true) ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, string $apiKey, string $from, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $apiKey, string $from, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -749,6 +764,10 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId') $provider->setAttribute('enabled', $enabled); } + if ($internal === true) { + $provider->setAttribute('internal', $internal); + } + if (!empty($apiKey)) { $provider->setAttribute('credentials', [ 'apiKey' => $apiKey, @@ -757,6 +776,15 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); + if ($internal === true) { + $internalProvider = $dbForProject->findOne('providers', [ + 'internal' => true, + 'type' => 'email', + ]); + $internalProvider->setAttribute('internal', false); + $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); + } + $response ->dynamic($provider, Response::MODEL_PROVIDER); }); @@ -777,12 +805,13 @@ App::patch('/v1/messaging/providers/msg91/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) + ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('senderId', '', new Text(0), 'Msg91 Sender ID.', true) ->param('authKey', '', new Text(0), 'Msg91 Auth Key.', true) ->param('from', '', new Text(256), 'Sender number.', true) ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, string $senderId, string $authKey, string $from, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $senderId, string $authKey, string $from, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -809,6 +838,10 @@ App::patch('/v1/messaging/providers/msg91/:providerId') $provider->setAttribute('enabled', $enabled); } + if ($internal === true) { + $provider->setAttribute('internal', $internal); + } + $credentials = $provider->getAttribute('credentials'); if (!empty($senderId)) { @@ -823,6 +856,15 @@ App::patch('/v1/messaging/providers/msg91/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); + if ($internal === true) { + $internalProvider = $dbForProject->findOne('providers', [ + 'internal' => true, + 'type' => 'email', + ]); + $internalProvider->setAttribute('internal', false); + $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); + } + $response ->dynamic($provider, Response::MODEL_PROVIDER); }); @@ -843,12 +885,13 @@ App::patch('/v1/messaging/providers/telesign/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) + ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('username', '', new Text(0), 'Telesign username.', true) ->param('password', '', new Text(0), 'Telesign password.', true) ->param('from', '', new Text(256), 'Sender number.', true) ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, string $username, string $password, string $from, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $username, string $password, string $from, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -875,6 +918,10 @@ App::patch('/v1/messaging/providers/telesign/:providerId') $provider->setAttribute('enabled', $enabled); } + if ($internal === true) { + $provider->setAttribute('internal', $internal); + } + $credentials = $provider->getAttribute('credentials'); if (!empty($username)) { @@ -889,6 +936,15 @@ App::patch('/v1/messaging/providers/telesign/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); + if ($internal === true) { + $internalProvider = $dbForProject->findOne('providers', [ + 'internal' => true, + 'type' => 'email', + ]); + $internalProvider->setAttribute('internal', false); + $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); + } + $response ->dynamic($provider, Response::MODEL_PROVIDER); }); @@ -909,12 +965,13 @@ App::patch('/v1/messaging/providers/textmagic/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) + ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('username', '', new Text(0), 'Textmagic username.', true) ->param('apiKey', '', new Text(0), 'Textmagic apiKey.', true) ->param('from', '', new Text(256), 'Sender number.', true) ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, string $username, string $apiKey, string $from, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $username, string $apiKey, string $from, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -941,6 +998,10 @@ App::patch('/v1/messaging/providers/textmagic/:providerId') $provider->setAttribute('enabled', $enabled); } + if ($internal === true) { + $provider->setAttribute('internal', $internal); + } + $credentials = $provider->getAttribute('credentials'); if (!empty($username)) { @@ -955,6 +1016,15 @@ App::patch('/v1/messaging/providers/textmagic/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); + if ($internal === true) { + $internalProvider = $dbForProject->findOne('providers', [ + 'internal' => true, + 'type' => 'email', + ]); + $internalProvider->setAttribute('internal', false); + $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); + } + $response ->dynamic($provider, Response::MODEL_PROVIDER); }); @@ -975,12 +1045,13 @@ App::patch('/v1/messaging/providers/twilio/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) + ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('accountSid', null, new Text(0), 'Twilio account secret ID.', true) ->param('authToken', null, new Text(0), 'Twilio authentication token.', true) ->param('from', '', new Text(256), 'Sender number.', true) ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, string $accountSid, string $authToken, string $from, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $accountSid, string $authToken, string $from, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -1007,6 +1078,10 @@ App::patch('/v1/messaging/providers/twilio/:providerId') $provider->setAttribute('enabled', $enabled); } + if ($internal === true) { + $provider->setAttribute('internal', $internal); + } + $credentials = $provider->getAttribute('credentials'); if (!empty($accountSid)) { @@ -1021,6 +1096,15 @@ App::patch('/v1/messaging/providers/twilio/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); + if ($internal === true) { + $internalProvider = $dbForProject->findOne('providers', [ + 'internal' => true, + 'type' => 'email', + ]); + $internalProvider->setAttribute('internal', false); + $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); + } + $response ->dynamic($provider, Response::MODEL_PROVIDER); }); @@ -1041,12 +1125,13 @@ App::patch('/v1/messaging/providers/vonage/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) + ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('apiKey', '', new Text(0), 'Vonage API key.', true) ->param('apiSecret', '', new Text(0), 'Vonage API secret.', true) ->param('from', '', new Text(256), 'Sender number.', true) ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, string $apiKey, string $apiSecret, string $from, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $apiKey, string $apiSecret, string $from, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -1073,6 +1158,10 @@ App::patch('/v1/messaging/providers/vonage/:providerId') $provider->setAttribute('enabled', $enabled); } + if ($internal === true) { + $provider->setAttribute('internal', $internal); + } + $credentials = $provider->getAttribute('credentials'); if (!empty($apiKey)) { @@ -1087,6 +1176,15 @@ App::patch('/v1/messaging/providers/vonage/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); + if ($internal === true) { + $internalProvider = $dbForProject->findOne('providers', [ + 'internal' => true, + 'type' => 'email', + ]); + $internalProvider->setAttribute('internal', false); + $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); + } + $response ->dynamic($provider, Response::MODEL_PROVIDER); }); @@ -1107,10 +1205,11 @@ App::patch('/v1/messaging/providers/fcm/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) + ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('serverKey', '', new Text(0), 'FCM Server Key.', true) ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, string $serverKey, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $serverKey, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -1131,12 +1230,25 @@ App::patch('/v1/messaging/providers/fcm/:providerId') $provider->setAttribute('enabled', $enabled); } + if ($internal === true) { + $provider->setAttribute('internal', $internal); + } + if (!empty($serverKey)) { $provider->setAttribute('credentials', ['serverKey' => $serverKey]); } $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); + if ($internal === true) { + $internalProvider = $dbForProject->findOne('providers', [ + 'internal' => true, + 'type' => 'email', + ]); + $internalProvider->setAttribute('internal', false); + $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); + } + $response ->dynamic($provider, Response::MODEL_PROVIDER); }); @@ -1158,6 +1270,7 @@ App::patch('/v1/messaging/providers/apns/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) + ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('authKey', '', new Text(0), 'APNS authentication key.', true) ->param('authKeyId', '', new Text(0), 'APNS authentication key ID.', true) ->param('teamId', '', new Text(0), 'APNS team ID.', true) @@ -1165,7 +1278,7 @@ App::patch('/v1/messaging/providers/apns/:providerId') ->param('endpoint', '', new Text(0), 'APNS endpoint.', true) ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, string $authKey, string $authKeyId, string $teamId, string $bundleId, string $endpoint, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $authKey, string $authKeyId, string $teamId, string $bundleId, string $endpoint, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -1186,6 +1299,10 @@ App::patch('/v1/messaging/providers/apns/:providerId') $provider->setAttribute('enabled', $enabled); } + if ($internal === true) { + $provider->setAttribute('internal', $internal); + } + $credentials = $provider->getAttribute('credentials'); if (!empty($authKey)) { @@ -1212,6 +1329,15 @@ App::patch('/v1/messaging/providers/apns/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); + if ($internal === true) { + $internalProvider = $dbForProject->findOne('providers', [ + 'internal' => true, + 'type' => 'email', + ]); + $internalProvider->setAttribute('internal', false); + $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); + } + $response ->dynamic($provider, Response::MODEL_PROVIDER); }); @@ -1274,6 +1400,9 @@ App::post('/v1/messaging/topics') if ($description) { $topic->setAttribute('description', $description); + $topic->setAttribute('search', $topic->getId() . ' ' . $name . ' ' . $description); + } else { + $topic->setAttribute('search', $topic->getId() . ' ' . $name); } try { @@ -1390,6 +1519,16 @@ App::patch('/v1/messaging/topics/:topicId') $topic->setAttribute('description', $description); } + if (!empty($name) || !empty($description)) { + if (!empty($name) && !empty($description)) { + $topic->setAttribute('search', $topic->getId() . ' ' . $name . ' ' . $description); + } elseif (!empty($name)) { + $topic->setAttribute('search', $topic->getId() . ' ' . $name . ' ' . $topic->getAttribute('description')); + } else { + $topic->setAttribute('search', $topic->getId() . ' ' . $topic->getAttribute('name') . ' ' . $description); + } + } + $topic = $dbForProject->updateDocument('topics', $topicId, $topic); $response @@ -1620,9 +1759,11 @@ App::post('/v1/messaging/messages/email') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_MESSAGE) ->param('messageId', '', new CustomId(), 'Message 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('to', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Topic IDs or List of User IDs or List of Target IDs.') ->param('subject', '', new Text(998), 'Email Subject.') ->param('content', '', new Text(64230), 'Email Content.') + ->param('topics', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Topic IDs.', true) + ->param('users', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of User IDs.', true) + ->param('targets', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Targets IDs.', true) ->param('description', '', new Text(256), 'Description for message.', true) ->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true) ->param('html', false, new Boolean(), 'Is content of type HTML', true) @@ -1631,12 +1772,18 @@ App::post('/v1/messaging/messages/email') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, array $to, string $subject, string $content, string $description, string $status, bool $html, ?string $deliveryTime, 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 $deliveryTime, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) { $messageId = $messageId == 'unique()' ? ID::unique() : $messageId; + if (\count($topics) === 0 && \count($users) === 0 && \count($targets) === 0) { + throw new Exception(Exception::MESSAGE_MISSING_TARGET); + } + $message = $dbForProject->createDocument('messages', new Document([ '$id' => $messageId, - 'to' => $to, + 'topics' => $topics, + 'users' => $users, + 'targets' => $targets, 'description' => $description, 'data' => [ 'subject' => $subject, @@ -1673,8 +1820,10 @@ App::post('/v1/messaging/messages/sms') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_MESSAGE) ->param('messageId', '', new CustomId(), 'Message 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('to', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Topic IDs or List of User IDs or List of Target IDs.') ->param('content', '', new Text(64230), 'SMS Content.') + ->param('topics', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Topic IDs.', true) + ->param('users', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of User IDs.', true) + ->param('targets', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Targets IDs.', true) ->param('description', '', new Text(256), 'Description for Message.', true) ->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true) ->param('deliveryTime', null, new DatetimeValidator(requireDateInFuture: true), 'Delivery time for message in ISO 8601 format. DateTime value must be in future.', true) @@ -1682,12 +1831,18 @@ App::post('/v1/messaging/messages/sms') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, array $to, string $content, string $description, string $status, ?string $deliveryTime, 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 $deliveryTime, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) { $messageId = $messageId == 'unique()' ? ID::unique() : $messageId; + if (\count($topics) === 0 && \count($users) === 0 && \count($targets) === 0) { + throw new Exception(Exception::MESSAGE_MISSING_TARGET); + } + $message = $dbForProject->createDocument('messages', new Document([ '$id' => $messageId, - 'to' => $to, + 'topics' => $topics, + 'users' => $users, + 'targets' => $targets, 'description' => $description, 'data' => [ 'content' => $content, @@ -1722,9 +1877,11 @@ App::post('/v1/messaging/messages/push') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_MESSAGE) ->param('messageId', '', new CustomId(), 'Message 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('to', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Topic IDs or List of User IDs or List of Target IDs.') ->param('title', '', new Text(256), 'Title for push notification.') ->param('body', '', new Text(64230), 'Body for push notification.') + ->param('topics', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Topic IDs.', true) + ->param('users', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of User IDs.', true) + ->param('targets', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Targets IDs.', true) ->param('description', '', new Text(256), 'Description for Message.', true) ->param('data', null, new JSON(), 'Additional Data for push notification.', true) ->param('action', '', new Text(256), 'Action for push notification.', true) @@ -1739,9 +1896,13 @@ App::post('/v1/messaging/messages/push') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, array $to, string $title, string $body, string $description, ?array $data, string $action, string $icon, string $sound, string $color, string $tag, string $badge, string $status, ?string $deliveryTime, 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 $deliveryTime, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) { $messageId = $messageId == 'unique()' ? ID::unique() : $messageId; + if (\count($topics) === 0 && \count($users) === 0 && \count($targets) === 0) { + throw new Exception(Exception::MESSAGE_MISSING_TARGET); + } + $pushData = [ 'title' => $title, 'body' => $body, @@ -1777,7 +1938,9 @@ App::post('/v1/messaging/messages/push') $message = $dbForProject->createDocument('messages', new Document([ '$id' => $messageId, - 'to' => $to, + 'topics' => $topics, + 'users' => $users, + 'targets' => $targets, 'description' => $description, 'deliveryTime' => $deliveryTime, 'data' => $pushData, @@ -1878,7 +2041,9 @@ App::patch('/v1/messaging/messages/email/:messageId') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_MESSAGE) ->param('messageId', '', new UID(), 'Message ID.') - ->param('to', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Topic IDs or List of User IDs or List of Target IDs.', true) + ->param('topics', null, new ArrayList(new Text(Database::LENGTH_KEY)), 'List of Topic IDs.', true) + ->param('users', null, new ArrayList(new Text(Database::LENGTH_KEY)), 'List of User IDs.', true) + ->param('targets', null, new ArrayList(new Text(Database::LENGTH_KEY)), 'List of Targets IDs.', true) ->param('subject', '', new Text(998), 'Email Subject.', true) ->param('description', '', new Text(256), 'Description for Message.', true) ->param('content', '', new Text(64230), 'Email Content.', true) @@ -1889,7 +2054,7 @@ App::patch('/v1/messaging/messages/email/:messageId') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, array $to, string $subject, string $description, string $content, string $status, bool $html, ?string $deliveryTime, 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 $deliveryTime, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) { $message = $dbForProject->getDocument('messages', $messageId); if ($message->isEmpty()) { @@ -1904,8 +2069,16 @@ App::patch('/v1/messaging/messages/email/:messageId') throw new Exception(Exception::MESSAGE_ALREADY_SCHEDULED); } - if (\count($to) > 0) { - $message->setAttribute('to', $to); + if (!\is_null($topics)) { + $message->setAttribute('topics', $topics); + } + + if (!\is_null($users)) { + $message->setAttribute('users', $users); + } + + if (!\is_null($targets)) { + $message->setAttribute('targets', $targets); } $data = $message->getAttribute('data'); @@ -1965,7 +2138,9 @@ App::patch('/v1/messaging/messages/sms/:messageId') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_MESSAGE) ->param('messageId', '', new UID(), 'Message ID.') - ->param('to', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Topic IDs or List of User IDs or List of Target IDs.', true) + ->param('topics', null, new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Topic IDs.', true) + ->param('users', null, new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of User IDs.', true) + ->param('targets', null, new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Targets IDs.', true) ->param('description', '', new Text(256), 'Description for Message.', true) ->param('content', '', new Text(64230), 'Email Content.', true) ->param('status', '', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true) @@ -1974,7 +2149,7 @@ App::patch('/v1/messaging/messages/sms/:messageId') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, array $to, string $description, string $content, string $status, ?string $deliveryTime, 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 $deliveryTime, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) { $message = $dbForProject->getDocument('messages', $messageId); if ($message->isEmpty()) { @@ -1989,8 +2164,16 @@ App::patch('/v1/messaging/messages/sms/:messageId') throw new Exception(Exception::MESSAGE_ALREADY_SCHEDULED); } - if (\count($to) > 0) { - $message->setAttribute('to', $to); + if (!\is_null($topics)) { + $message->setAttribute('topics', $topics); + } + + if (!\is_null($users)) { + $message->setAttribute('users', $users); + } + + if (!\is_null($targets)) { + $message->setAttribute('targets', $targets); } $data = $message->getAttribute('data'); @@ -2042,7 +2225,9 @@ App::patch('/v1/messaging/messages/push/:messageId') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_MESSAGE) ->param('messageId', '', new UID(), 'Message ID.') - ->param('to', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Topic IDs or List of User IDs or List of Target IDs.', true) + ->param('topics', null, new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Topic IDs.', true) + ->param('users', null, new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of User IDs.', true) + ->param('targets', null, new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Targets IDs.', true) ->param('description', '', new Text(256), 'Description for Message.', true) ->param('title', '', new Text(256), 'Title for push notification.', true) ->param('body', '', new Text(64230), 'Body for push notification.', true) @@ -2058,7 +2243,7 @@ App::patch('/v1/messaging/messages/push/:messageId') ->inject('project') ->inject('queueForMessaging') ->inject('response') - ->action(function (string $messageId, array $to, string $description, string $title, string $body, ?array $data, string $action, string $icon, string $sound, string $color, string $tag, string $badge, string $status, ?string $deliveryTime, 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 $deliveryTime, Database $dbForProject, Document $project, Messaging $queueForMessaging, Response $response) { $message = $dbForProject->getDocument('messages', $messageId); if ($message->isEmpty()) { @@ -2073,8 +2258,16 @@ App::patch('/v1/messaging/messages/push/:messageId') throw new Exception(Exception::MESSAGE_ALREADY_SCHEDULED); } - if (\count($to) > 0) { - $message->setAttribute('to', $to); + if (!\is_null($topics)) { + $message->setAttribute('topics', $topics); + } + + if (!\is_null($users)) { + $message->setAttribute('users', $users); + } + + if (!\is_null($targets)) { + $message->setAttribute('targets', $targets); } $pushData = $message->getAttribute('data'); diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index da2261fd50..a0975634d5 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -633,7 +633,7 @@ App::post('/v1/teams/:teamId/memberships') ; } elseif (!empty($phone)) { $provider = Authorization::skip(fn () => $dbForProject->findOne('providers', [ - Query::equal('default', [true]), + Query::equal('internal', [true]), Query::equal('type', ['sms']) ])); @@ -662,7 +662,7 @@ App::post('/v1/teams/:teamId/memberships') $messageDoc = $dbForProject->createDocument('messages', new Document([ // Here membership ID is used as message ID so that it can be used in test cases to verify the message '$id' => $membership->getId(), - 'to' => [$target->getId()], + 'targets' => [$target->getId()], 'data' => [ 'content' => $message, ], diff --git a/docker-compose.yml b/docker-compose.yml index c0013f7385..8255508356 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -188,17 +188,9 @@ services: - _APP_MIGRATIONS_FIREBASE_CLIENT_ID - _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET - _APP_ASSISTANT_OPENAI_API_KEY - - _APP_MESSAGE_SMS_PROVIDER_MSG91_SENDER_ID - - _APP_MESSAGE_SMS_PROVIDER_MSG91_AUTH_KEY - - _APP_MESSAGE_SMS_PROVIDER_MSG91_FROM - - _APP_MESSAGE_SMS_PROVIDER_MSG91_TO - - _APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_API_KEY - - _APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_DOMAIN - - _APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_FROM - - _APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_RECEIVER_EMAIL - - _APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_IS_EU_REGION - - _APP_MESSAGE_PUSH_PROVIDER_FCM_SERVERY_KEY - - _APP_MESSAGE_PUSH_PROVIDER_FCM_RECEIVER_TOKEN + - _APP_MESSAGE_SMS_TEST_DSN + - _APP_MESSAGE_EMAIL_TEST_DSN + - _APP_MESSAGE_PUSH_TEST_DSN appwrite-realtime: entrypoint: realtime <<: *x-logging @@ -578,6 +570,7 @@ services: environment: - _APP_ENV - _APP_WORKER_PER_CORE + - _APP_OPENSSL_KEY_V1 - _APP_REDIS_HOST - _APP_REDIS_PORT - _APP_REDIS_USER diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 8d6dc6d29a..cef64ffce5 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -241,6 +241,7 @@ class Exception extends \Exception /** Message */ public const MESSAGE_NOT_FOUND = 'message_not_found'; + public const MESSAGE_MISSING_TARGET = 'message_missing_target'; public const MESSAGE_ALREADY_SENT = 'message_already_sent'; public const MESSAGE_ALREADY_SCHEDULED = 'message_already_scheduled'; diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 5ca5ebfc65..63e0ff651a 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -10,6 +10,7 @@ use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Query; +use Utopia\Database\Validator\Authorization; use Utopia\Messaging\Adapters\SMS as SMSAdapter; use Utopia\Messaging\Adapters\SMS\Mock; use Utopia\Messaging\Adapters\SMS\Msg91; @@ -70,25 +71,33 @@ class Messaging extends Action private function processMessage(Database $dbForProject, Document $message): void { - $recipientsId = $message->getAttribute('to', []); + $topicsId = $message->getAttribute('topics', []); + $targetsId = $message->getAttribute('targets', []); + $usersId = $message->getAttribute('users', []); /** * @var Document[] $recipients */ $recipients = []; - $topics = $dbForProject->find('topics', [Query::equal('$id', $recipientsId)]); - foreach ($topics as $topic) { - $recipients = \array_merge($recipients, $topic->getAttribute('targets')); + if (\count($topicsId) > 0) { + $topics = $dbForProject->find('topics', [Query::equal('$id', $topicsId)]); + foreach ($topics as $topic) { + $recipients = \array_merge($recipients, $topic->getAttribute('targets')); + } } - $users = $dbForProject->find('users', [Query::equal('$id', $recipientsId)]); - foreach ($users as $user) { - $recipients = \array_merge($recipients, $user->getAttribute('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 = $dbForProject->find('targets', [Query::equal('$id', $recipientsId)]); - $recipients = \array_merge($recipients, $targets); + if (\count($targetsId) > 0) { + $targets = $dbForProject->find('targets', [Query::equal('$id', $targetsId)]); + $recipients = \array_merge($recipients, $targets); + } $providers = []; foreach ($recipients as $recipient) { @@ -118,7 +127,7 @@ class Messaging extends Action $results = batch(\array_map(function ($batch) use ($message, $provider, $adapter, $batchIndex) { return function () use ($batch, $message, $provider, $adapter, $batchIndex) { - $deliveredTo = 0; + $deliveredTotal = 0; $deliveryErrors = []; $messageData = clone $message; $messageData->setAttribute('to', $batch); @@ -130,13 +139,13 @@ class Messaging extends Action }; try { $adapter->send($data); - $deliveredTo += \count($batch); + $deliveredTotal += \count($batch); } catch (\Exception $e) { $deliveryErrors[] = 'Failed sending to targets ' . $batchIndex + 1 . '-' . \count($batch) . ' with error: ' . $e->getMessage(); } finally { $batchIndex++; return [ - 'deliveredTo' => $deliveredTo, + 'deliveredTotal' => $deliveredTotal, 'deliveryErrors' => $deliveryErrors, ]; } @@ -149,10 +158,10 @@ class Messaging extends Action $results = array_merge(...$results); - $deliveredTo = 0; + $deliveredTotal = 0; $deliveryErrors = []; foreach ($results as $result) { - $deliveredTo += $result['deliveredTo']; + $deliveredTotal += $result['deliveredTotal']; $deliveryErrors = \array_merge($deliveryErrors, $result['deliveryErrors']); } $message->setAttribute('deliveryErrors', $deliveryErrors); @@ -162,8 +171,8 @@ class Messaging extends Action } else { $message->setAttribute('status', 'sent'); } - $message->setAttribute('to', $recipientsId); - $message->setAttribute('deliveredTo', $deliveredTo); + $message->removeAttribute('to'); + $message->setAttribute('deliveredTotal', $deliveredTotal); $message->setAttribute('deliveredAt', DateTime::now()); $dbForProject->updateDocument('messages', $message->getId(), $message); diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Messages.php b/src/Appwrite/Utopia/Database/Validator/Queries/Messages.php index 4bff13ae19..dd043474a8 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Messages.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Messages.php @@ -5,7 +5,9 @@ namespace Appwrite\Utopia\Database\Validator\Queries; class Messages extends Base { public const ALLOWED_ATTRIBUTES = [ - 'to', + 'topics', + 'users', + 'targets', 'providerId', 'deliveredAt', 'deliveredTo', diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Providers.php b/src/Appwrite/Utopia/Database/Validator/Queries/Providers.php index 1fd6c9e9f8..7760acbdad 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Providers.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Providers.php @@ -8,7 +8,7 @@ class Providers extends Base 'name', 'provider', 'type', - 'default', + 'internal', 'enabled', ]; diff --git a/src/Appwrite/Utopia/Response/Model/Message.php b/src/Appwrite/Utopia/Response/Model/Message.php index 609aa4022e..27c70d7073 100644 --- a/src/Appwrite/Utopia/Response/Model/Message.php +++ b/src/Appwrite/Utopia/Response/Model/Message.php @@ -17,12 +17,38 @@ class Message extends Any 'default' => '', 'example' => '5e5ea5c16897e', ]) - ->addRule('to', [ + ->addRule('$createdAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'Message creation time in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) + ->addRule('$updatedAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'Message update date in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) + ->addRule('topics', [ 'type' => self::TYPE_STRING, - 'description' => 'Message recipients.', + 'description' => 'Topic IDs set as recipients.', 'default' => '', 'array' => true, - 'example' => ['user-1'], + 'example' => ['5e5ea5c16897e'], + ]) + ->addRule('users', [ + 'type' => self::TYPE_STRING, + 'description' => 'User IDs set as recipients.', + 'default' => '', + 'array' => true, + 'example' => ['5e5ea5c16897e'], + ]) + ->addRule('targets', [ + 'type' => self::TYPE_STRING, + 'description' => 'Target IDs set as recipients.', + 'default' => '', + 'array' => true, + 'example' => ['5e5ea5c16897e'], ]) ->addRule('deliveryTime', [ 'type' => self::TYPE_DATETIME, @@ -46,7 +72,7 @@ class Message extends Any 'array' => true, 'example' => ['Failed to send message to target 5e5ea5c16897e: Credentials not valid.'], ]) - ->addRule('deliveredTo', [ + ->addRule('deliveredTotal', [ 'type' => self::TYPE_INTEGER, 'description' => 'Number of recipients the message was delivered to.', 'default' => 0, diff --git a/src/Appwrite/Utopia/Response/Model/Provider.php b/src/Appwrite/Utopia/Response/Model/Provider.php index 552e9783e8..a121753275 100644 --- a/src/Appwrite/Utopia/Response/Model/Provider.php +++ b/src/Appwrite/Utopia/Response/Model/Provider.php @@ -16,6 +16,18 @@ class Provider extends Model 'default' => '', 'example' => '5e5ea5c16897e', ]) + ->addRule('$createdAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'Provider creation time in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) + ->addRule('$updatedAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'Provider update date in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) ->addRule('name', [ 'type' => self::TYPE_STRING, 'description' => 'The name for the provider instance.', @@ -28,7 +40,7 @@ class Provider extends Model 'default' => '', 'example' => 'mailgun', ]) - ->addRule('default', [ + ->addRule('internal', [ 'type' => self::TYPE_BOOLEAN, 'description' => 'Is this a pre-configured provider instance?', 'default' => false, diff --git a/src/Appwrite/Utopia/Response/Model/Subscriber.php b/src/Appwrite/Utopia/Response/Model/Subscriber.php index 2f34619cb4..65bbd38f0e 100644 --- a/src/Appwrite/Utopia/Response/Model/Subscriber.php +++ b/src/Appwrite/Utopia/Response/Model/Subscriber.php @@ -16,6 +16,18 @@ class Subscriber extends Model 'default' => '', 'example' => '259125845563242502', ]) + ->addRule('$createdAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'Subscriber creation time in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) + ->addRule('$updatedAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'Subscriber update date in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) ->addRule('targetId', [ 'type' => self::TYPE_STRING, 'description' => 'Target ID.', diff --git a/src/Appwrite/Utopia/Response/Model/Target.php b/src/Appwrite/Utopia/Response/Model/Target.php index 5750f57eba..c6c5929ee3 100644 --- a/src/Appwrite/Utopia/Response/Model/Target.php +++ b/src/Appwrite/Utopia/Response/Model/Target.php @@ -16,6 +16,18 @@ class Target extends Model 'default' => '', 'example' => '259125845563242502', ]) + ->addRule('$createdAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'Target creation time in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) + ->addRule('$updatedAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'Target update date in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) ->addRule('userId', [ 'type' => self::TYPE_STRING, 'description' => 'User ID.', diff --git a/src/Appwrite/Utopia/Response/Model/Topic.php b/src/Appwrite/Utopia/Response/Model/Topic.php index d46d26597d..096ddb347f 100644 --- a/src/Appwrite/Utopia/Response/Model/Topic.php +++ b/src/Appwrite/Utopia/Response/Model/Topic.php @@ -16,6 +16,18 @@ class Topic extends Model 'default' => '', 'example' => '259125845563242502', ]) + ->addRule('$createdAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'Topic creation time in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) + ->addRule('$updatedAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'Topic update date in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) ->addRule('name', [ 'type' => self::TYPE_STRING, 'description' => 'The name of the topic.', diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 7f4cb84b05..d56f93a42c 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -11,6 +11,7 @@ use Utopia\App; use Utopia\Database\DateTime; use Utopia\Database\Helpers\ID; use Utopia\Database\Validator\Datetime as DatetimeValidator; +use Utopia\DSN\DSN; use function sleep; @@ -743,10 +744,15 @@ class AccountCustomClientTest extends Scope public function testCreatePhone(): array { - $to = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_TO'); - $from = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_FROM'); - $authKey = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_AUTH_KEY'); - $senderId = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_SENDER_ID'); + if (empty(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN'))) { + $this->markTestSkipped('SMS DSN not provided'); + } + + $smsDSN = new DSN(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN')); + $to = $smsDSN->getParam('to'); + $from = $smsDSN->getParam('from'); + $authKey = $smsDSN->getPassword(); + $senderId = $smsDSN->getUser(); if (empty($to) || empty($from) || empty($authKey) || empty($senderId)) { $this->markTestSkipped('SMS provider not configured'); @@ -762,7 +768,6 @@ class AccountCustomClientTest extends Scope 'name' => 'Sms provider', 'senderId' => $senderId, 'authKey' => $authKey, - 'default' => true, 'from' => $from, ]); $this->assertEquals(201, $response['headers']['status-code']); @@ -810,7 +815,7 @@ class AccountCustomClientTest extends Scope ]); $this->assertEquals(200, $message['headers']['status-code']); - $this->assertEquals(1, $message['body']['deliveredTo']); + $this->assertEquals(1, $message['body']['deliveredTotal']); $this->assertEquals(0, \count($message['body']['deliveryErrors'])); @@ -1013,6 +1018,8 @@ class AccountCustomClientTest extends Scope public function testPhoneVerification(array $data): array { $session = $data['session'] ?? ''; + $smsDSN = new DSN(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN')); + $from = $smsDSN->getParam('from'); /** * Test for SUCCESS @@ -1023,7 +1030,7 @@ class AccountCustomClientTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, - ]), ['from' => App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_FROM')]); + ]), ['from' => $from]); $this->assertEquals(201, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); @@ -1040,7 +1047,7 @@ class AccountCustomClientTest extends Scope ]); $this->assertEquals(200, $message['headers']['status-code']); - $this->assertEquals(1, $message['body']['deliveredTo']); + $this->assertEquals(1, $message['body']['deliveredTotal']); $this->assertEquals(0, \count($message['body']['deliveryErrors'])); return \array_merge($data, [ diff --git a/tests/e2e/Services/GraphQL/AccountTest.php b/tests/e2e/Services/GraphQL/AccountTest.php index 801be808c9..9d1da09feb 100644 --- a/tests/e2e/Services/GraphQL/AccountTest.php +++ b/tests/e2e/Services/GraphQL/AccountTest.php @@ -8,6 +8,7 @@ use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; use Utopia\App; use Utopia\Database\Helpers\ID; +use Utopia\DSN\DSN; class AccountTest extends Scope { @@ -123,10 +124,15 @@ class AccountTest extends Scope */ public function testCreatePhoneVerification(): array { - $to = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_TO'); - $from = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_FROM'); - $authKey = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_AUTH_KEY'); - $senderId = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_SENDER_ID'); + if (empty(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN'))) { + $this->markTestSkipped('SMS DSN not provided'); + } + + $smsDSN = new DSN(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN')); + $to = $smsDSN->getParam('to'); + $from = $smsDSN->getParam('from'); + $authKey = $smsDSN->getPassword(); + $senderId = $smsDSN->getUser(); if (empty($to) || empty($from) || empty($authKey) || empty($senderId)) { $this->markTestSkipped('SMS provider not configured'); @@ -142,7 +148,6 @@ class AccountTest extends Scope 'from' => $from, 'senderId' => $senderId, 'authKey' => $authKey, - 'default' => true, ], ]; diff --git a/tests/e2e/Services/GraphQL/Base.php b/tests/e2e/Services/GraphQL/Base.php index bf6ae84f7f..45fc148497 100644 --- a/tests/e2e/Services/GraphQL/Base.php +++ b/tests/e2e/Services/GraphQL/Base.php @@ -1790,7 +1790,7 @@ trait Base name provider type - default + internal enabled } }'; @@ -1801,7 +1801,7 @@ trait Base name provider type - default + internal enabled } }'; @@ -1812,7 +1812,7 @@ trait Base name provider type - default + internal enabled } }'; @@ -1823,7 +1823,7 @@ trait Base name provider type - default + internal enabled } }'; @@ -1834,7 +1834,7 @@ trait Base name provider type - default + internal enabled } }'; @@ -1845,7 +1845,7 @@ trait Base name provider type - default + internal enabled } }'; @@ -1856,7 +1856,7 @@ trait Base name provider type - default + internal enabled } }'; @@ -1867,7 +1867,7 @@ trait Base name provider type - default + internal enabled } }'; @@ -1878,7 +1878,7 @@ trait Base name provider type - default + internal enabled } }'; @@ -1891,7 +1891,7 @@ trait Base name provider type - default + internal enabled } } @@ -1903,7 +1903,7 @@ trait Base name provider type - default + internal enabled } }'; @@ -1914,7 +1914,7 @@ trait Base name provider type - default + internal enabled } }'; @@ -1925,7 +1925,7 @@ trait Base name provider type - default + internal enabled } }'; @@ -1936,7 +1936,7 @@ trait Base name provider type - default + internal enabled } }'; @@ -1947,7 +1947,7 @@ trait Base name provider type - default + internal enabled } }'; @@ -1958,7 +1958,7 @@ trait Base name provider type - default + internal enabled } }'; @@ -1969,7 +1969,7 @@ trait Base name provider type - default + internal enabled } }'; @@ -1980,7 +1980,7 @@ trait Base name provider type - default + internal enabled } }'; @@ -1991,7 +1991,7 @@ trait Base name provider type - default + internal enabled } }'; @@ -2002,7 +2002,7 @@ trait Base name provider type - default + internal enabled } }'; @@ -2087,40 +2087,46 @@ trait Base } }'; case self::$CREATE_EMAIL: - return 'mutation createEmail($messageId: String!, $to: [String!]!, $subject: String!, $content: String!, $status: String, $description: String, $html: Boolean, $deliveryTime: String) { - messagingCreateEmail(messageId: $messageId, to: $to, 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, $deliveryTime: String) { + messagingCreateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, description: $description, html: $html, deliveryTime: $deliveryTime) { _id - to + topics + users + targets deliveryTime deliveredAt deliveryErrors - deliveredTo + deliveredTotal status description } }'; case self::$CREATE_SMS: - return 'mutation createSMS($messageId: String!, $to: [String!]!, $content: String!, $status: String, $description: String, $deliveryTime: String) { - messagingCreateSMS(messageId: $messageId, to: $to, 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, $deliveryTime: String) { + messagingCreateSMS(messageId: $messageId, topics: $topics, users: $users, targets: $targets, content: $content, status: $status, description: $description, deliveryTime: $deliveryTime) { _id - to + topics + users + targets deliveryTime deliveredAt deliveryErrors - deliveredTo + deliveredTotal status description } }'; case self::$CREATE_PUSH_NOTIFICATION: - return 'mutation createPushNotification($messageId: String!, $to: [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, to: $to, 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, $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) { _id - to + topics + users + targets deliveryTime deliveredAt deliveryErrors - deliveredTo + deliveredTotal status description } @@ -2131,11 +2137,13 @@ trait Base total messages { _id - to + topics + users + targets deliveryTime deliveredAt deliveryErrors - deliveredTo + deliveredTotal status description } @@ -2145,50 +2153,58 @@ trait Base return 'query getMessage($messageId: String!) { messagingGetMessage(messageId: $messageId) { _id - to + topics + users + targets deliveryTime deliveredAt deliveryErrors - deliveredTo + deliveredTotal status description } }'; case self::$UPDATE_EMAIL: - return 'mutation updateEmail($messageId: String!, $to: [String!], $subject: String, $content: String, $status: String, $description: String, $html: Boolean, $deliveryTime: String) { - messagingUpdateEmail(messageId: $messageId, to: $to, 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, $deliveryTime: String) { + messagingUpdateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, description: $description, html: $html, deliveryTime: $deliveryTime) { _id - to + topics + users + targets deliveryTime deliveredAt deliveryErrors - deliveredTo + deliveredTotal status description } }'; case self::$UPDATE_SMS: - return 'mutation updateSMS($messageId: String!, $to: [String!], $content: String, $status: String, $description: String, $deliveryTime: String) { - messagingUpdateSMS(messageId: $messageId, to: $to, 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, $deliveryTime: String) { + messagingUpdateSMS(messageId: $messageId, topics: $topics, users: $users, targets: $targets, content: $content, status: $status, description: $description, deliveryTime: $deliveryTime) { _id - to + topics + users + targets deliveryTime deliveredAt deliveryErrors - deliveredTo + deliveredTotal status description } }'; case self::$UPDATE_PUSH_NOTIFICATION: - return 'mutation updatePushNotification($messageId: String!, $to: [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, to: $to, 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, $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) { _id - to + topics + users + targets deliveryTime deliveredAt deliveryErrors - deliveredTo + deliveredTotal status description } diff --git a/tests/e2e/Services/GraphQL/MessagingTest.php b/tests/e2e/Services/GraphQL/MessagingTest.php index 82f697f7ec..c0322372ba 100644 --- a/tests/e2e/Services/GraphQL/MessagingTest.php +++ b/tests/e2e/Services/GraphQL/MessagingTest.php @@ -8,6 +8,7 @@ use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideServer; use Utopia\App; use Utopia\Database\Helpers\ID; +use Utopia\DSN\DSN; class MessagingTest extends Scope { @@ -526,11 +527,17 @@ class MessagingTest extends Scope public function testSendEmail() { - $to = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_RECEIVER_EMAIL'); - $from = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_FROM'); - $apiKey = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_API_KEY'); - $domain = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_DOMAIN'); - $isEuRegion = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_IS_EU_REGION'); + if (empty(App::getEnv('_APP_MESSAGE_EMAIL_TEST_DSN'))) { + $this->markTestSkipped('Email DSN not provided'); + } + + $emailDSN = new DSN(App::getEnv('_APP_MESSAGE_EMAIL_TEST_DSN')); + $to = $emailDSN->getParam('to'); + $from = $emailDSN->getParam('from'); + $isEuRegion = $emailDSN->getParam('isEuRegion'); + $apiKey = $emailDSN->getPassword(); + $domain = $emailDSN->getUser(); + if (empty($to) || empty($from) || empty($apiKey) || empty($domain) || empty($isEuRegion)) { $this->markTestSkipped('Email provider not configured'); } @@ -631,7 +638,7 @@ class MessagingTest extends Scope 'query' => $query, 'variables' => [ 'messageId' => ID::unique(), - 'to' => [$topic['body']['data']['messagingCreateTopic']['_id']], + 'topics' => [$topic['body']['data']['messagingCreateTopic']['_id']], 'subject' => 'Khali beats Undertaker', 'content' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', ], @@ -660,7 +667,7 @@ class MessagingTest extends Scope ]), $graphQLPayload); $this->assertEquals(200, $message['headers']['status-code']); - $this->assertEquals(1, $message['body']['data']['messagingGetMessage']['deliveredTo']); + $this->assertEquals(1, $message['body']['data']['messagingGetMessage']['deliveredTotal']); $this->assertEquals(0, \count($message['body']['data']['messagingGetMessage']['deliveryErrors'])); return $message['body']['data']['messagingGetMessage']; @@ -671,11 +678,17 @@ class MessagingTest extends Scope */ public function testUpdateEmail(array $email) { - $to = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_RECEIVER_EMAIL'); - $from = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_FROM'); - $apiKey = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_API_KEY'); - $domain = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_DOMAIN'); - $isEuRegion = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_IS_EU_REGION'); + if (empty(App::getEnv('_APP_MESSAGE_EMAIL_TEST_DSN'))) { + $this->markTestSkipped('Email DSN not provided'); + } + + $emailDSN = new DSN(App::getEnv('_APP_MESSAGE_EMAIL_TEST_DSN')); + $to = $emailDSN->getParam('to'); + $from = $emailDSN->getParam('from'); + $isEuRegion = $emailDSN->getParam('isEuRegion'); + $apiKey = $emailDSN->getPassword(); + $domain = $emailDSN->getUser(); + if (empty($to) || empty($from) || empty($apiKey) || empty($domain) || empty($isEuRegion)) { $this->markTestSkipped('Email provider not configured'); } @@ -777,7 +790,7 @@ class MessagingTest extends Scope 'variables' => [ 'messageId' => ID::unique(), 'status' => 'draft', - 'to' => [$topic['body']['data']['messagingCreateTopic']['_id']], + 'topics' => [$topic['body']['data']['messagingCreateTopic']['_id']], 'subject' => 'Khali beats Undertaker', 'content' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', ], @@ -822,16 +835,22 @@ class MessagingTest extends Scope ]), $graphQLPayload); $this->assertEquals(200, $message['headers']['status-code']); - $this->assertEquals(1, $message['body']['data']['messagingGetMessage']['deliveredTo']); + $this->assertEquals(1, $message['body']['data']['messagingGetMessage']['deliveredTotal']); $this->assertEquals(0, \count($message['body']['data']['messagingGetMessage']['deliveryErrors'])); } public function testSendSMS() { - $to = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_TO'); - $from = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_FROM'); - $senderId = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_SENDER_ID'); - $authKey = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_AUTH_KEY'); + if (empty(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN'))) { + $this->markTestSkipped('SMS DSN not provided'); + } + + $smsDSN = new DSN(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN')); + $to = $smsDSN->getParam('to'); + $from = $smsDSN->getParam('from'); + $authKey = $smsDSN->getPassword(); + $senderId = $smsDSN->getUser(); + if (empty($to) || empty($from) || empty($senderId) || empty($authKey)) { $this->markTestSkipped('SMS provider not configured'); } @@ -931,7 +950,7 @@ class MessagingTest extends Scope 'query' => $query, 'variables' => [ 'messageId' => ID::unique(), - 'to' => [$topic['body']['data']['messagingCreateTopic']['_id']], + 'topics' => [$topic['body']['data']['messagingCreateTopic']['_id']], 'content' => '454665', ], ]; @@ -959,7 +978,7 @@ class MessagingTest extends Scope ]), $graphQLPayload); $this->assertEquals(200, $message['headers']['status-code']); - $this->assertEquals(1, $message['body']['data']['messagingGetMessage']['deliveredTo']); + $this->assertEquals(1, $message['body']['data']['messagingGetMessage']['deliveredTotal']); $this->assertEquals(0, \count($message['body']['data']['messagingGetMessage']['deliveryErrors'])); return $message['body']['data']['messagingGetMessage']; } @@ -969,10 +988,16 @@ class MessagingTest extends Scope */ public function testUpdateSMS(array $sms) { - $to = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_TO'); - $from = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_FROM'); - $senderId = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_SENDER_ID'); - $authKey = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_AUTH_KEY'); + if (empty(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN'))) { + $this->markTestSkipped('SMS DSN not provided'); + } + + $smsDSN = new DSN(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN')); + $to = $smsDSN->getParam('to'); + $from = $smsDSN->getParam('from'); + $authKey = $smsDSN->getPassword(); + $senderId = $smsDSN->getUser(); + if (empty($to) || empty($from) || empty($senderId) || empty($authKey)) { $this->markTestSkipped('SMS provider not configured'); } @@ -1073,7 +1098,7 @@ class MessagingTest extends Scope 'variables' => [ 'messageId' => ID::unique(), 'status' => 'draft', - 'to' => [$topic['body']['data']['messagingCreateTopic']['_id']], + 'topics' => [$topic['body']['data']['messagingCreateTopic']['_id']], 'content' => '345463', ], ]; @@ -1117,14 +1142,20 @@ class MessagingTest extends Scope ]), $graphQLPayload); $this->assertEquals(200, $message['headers']['status-code']); - $this->assertEquals(1, $message['body']['data']['messagingGetMessage']['deliveredTo']); + $this->assertEquals(1, $message['body']['data']['messagingGetMessage']['deliveredTotal']); $this->assertEquals(0, \count($message['body']['data']['messagingGetMessage']['deliveryErrors'])); } public function testSendPushNotification() { - $to = App::getEnv('_APP_MESSAGE_PUSH_PROVIDER_FCM_RECEIVER_TOKEN'); - $serverKey = App::getEnv('_APP_MESSAGE_PUSH_PROVIDER_FCM_SERVERY_KEY'); + if (empty(App::getEnv('_APP_MESSAGE_PUSH_TEST_DSN'))) { + $this->markTestSkipped('Push DSN empty'); + } + + $pushDSN = new DSN(App::getEnv('_APP_MESSAGE_PUSH_TEST_DSN')); + $to = $pushDSN->getParam('to'); + $serverKey = $pushDSN->getPassword(); + if (empty($to) || empty($serverKey)) { $this->markTestSkipped('Push provider not configured'); } @@ -1222,7 +1253,7 @@ class MessagingTest extends Scope 'query' => $query, 'variables' => [ 'messageId' => ID::unique(), - 'to' => [$topic['body']['data']['messagingCreateTopic']['_id']], + 'topics' => [$topic['body']['data']['messagingCreateTopic']['_id']], 'title' => 'Push Notification Title', 'body' => 'Push Notifiaction Body', ], @@ -1251,7 +1282,7 @@ class MessagingTest extends Scope ]), $graphQLPayload); $this->assertEquals(200, $message['headers']['status-code']); - $this->assertEquals(1, $message['body']['data']['messagingGetMessage']['deliveredTo']); + $this->assertEquals(1, $message['body']['data']['messagingGetMessage']['deliveredTotal']); $this->assertEquals(0, \count($message['body']['data']['messagingGetMessage']['deliveryErrors'])); return $message['body']['data']['messagingGetMessage']; @@ -1262,8 +1293,14 @@ class MessagingTest extends Scope */ public function testUpdatePushNotification(array $push) { - $to = App::getEnv('_APP_MESSAGE_PUSH_PROVIDER_FCM_RECEIVER_TOKEN'); - $serverKey = App::getEnv('_APP_MESSAGE_PUSH_PROVIDER_FCM_SERVERY_KEY'); + if (empty(App::getEnv('_APP_MESSAGE_PUSH_TEST_DSN'))) { + $this->markTestSkipped('Push DSN empty'); + } + + $pushDSN = new DSN(App::getEnv('_APP_MESSAGE_PUSH_TEST_DSN')); + $to = $pushDSN->getParam('to'); + $serverKey = $pushDSN->getPassword(); + if (empty($to) || empty($serverKey)) { $this->markTestSkipped('Push provider not configured'); } @@ -1361,7 +1398,7 @@ class MessagingTest extends Scope 'variables' => [ 'messageId' => ID::unique(), 'status' => 'draft', - 'to' => [$topic['body']['data']['messagingCreateTopic']['_id']], + 'topics' => [$topic['body']['data']['messagingCreateTopic']['_id']], 'title' => 'Push Notification Title', 'body' => 'Push Notifiaction Body', ], @@ -1406,7 +1443,7 @@ class MessagingTest extends Scope ]), $graphQLPayload); $this->assertEquals(200, $message['headers']['status-code']); - $this->assertEquals(1, $message['body']['data']['messagingGetMessage']['deliveredTo']); + $this->assertEquals(1, $message['body']['data']['messagingGetMessage']['deliveredTotal']); $this->assertEquals(0, \count($message['body']['data']['messagingGetMessage']['deliveryErrors'])); } } diff --git a/tests/e2e/Services/Messaging/MessagingBase.php b/tests/e2e/Services/Messaging/MessagingBase.php index 01138c6412..221617a1d1 100644 --- a/tests/e2e/Services/Messaging/MessagingBase.php +++ b/tests/e2e/Services/Messaging/MessagingBase.php @@ -5,6 +5,7 @@ namespace Tests\E2E\Services\Messaging; use Tests\E2E\Client; use Utopia\App; use Utopia\Database\Helpers\ID; +use Utopia\DSN\DSN; trait MessagingBase { @@ -223,7 +224,7 @@ trait MessagingBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ - 'topicId' => 'unique()', + 'topicId' => ID::unique(), 'name' => 'my-app', 'description' => 'web app' ]); @@ -258,6 +259,8 @@ trait MessagingBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'search' => 'updated-description', ]); $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals(1, \count($response['body']['topics'])); @@ -405,11 +408,17 @@ trait MessagingBase public function testSendEmail() { - $to = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_RECEIVER_EMAIL'); - $from = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_FROM'); - $apiKey = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_API_KEY'); - $domain = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_DOMAIN'); - $isEuRegion = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_IS_EU_REGION'); + if (empty(App::getEnv('_APP_MESSAGE_EMAIL_TEST_DSN'))) { + $this->markTestSkipped('Email DSN not provided'); + } + + $emailDSN = new DSN(App::getEnv('_APP_MESSAGE_EMAIL_TEST_DSN')); + $to = $emailDSN->getParam('to'); + $from = $emailDSN->getParam('from'); + $isEuRegion = $emailDSN->getParam('isEuRegion'); + $apiKey = $emailDSN->getPassword(); + $domain = $emailDSN->getUser(); + if (empty($to) || empty($from) || empty($apiKey) || empty($domain) || empty($isEuRegion)) { $this->markTestSkipped('Email provider not configured'); } @@ -486,7 +495,7 @@ trait MessagingBase 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ 'messageId' => ID::unique(), - 'to' => [$topic['body']['$id']], + 'topics' => [$topic['body']['$id']], 'subject' => 'Khali beats Undertaker', 'content' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', ]); @@ -503,7 +512,7 @@ trait MessagingBase ]); $this->assertEquals(200, $message['headers']['status-code']); - $this->assertEquals(1, $message['body']['deliveredTo']); + $this->assertEquals(1, $message['body']['deliveredTotal']); $this->assertEquals(0, \count($message['body']['deliveryErrors'])); return $message; @@ -514,11 +523,17 @@ trait MessagingBase */ public function testUpdateEmail(array $email) { - $to = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_RECEIVER_EMAIL'); - $from = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_FROM'); - $apiKey = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_API_KEY'); - $domain = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_DOMAIN'); - $isEuRegion = App::getEnv('_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_IS_EU_REGION'); + if (empty(App::getEnv('_APP_MESSAGE_EMAIL_TEST_DSN'))) { + $this->markTestSkipped('Email DSN not provided'); + } + + $emailDSN = new DSN(App::getEnv('_APP_MESSAGE_EMAIL_TEST_DSN')); + $to = $emailDSN->getParam('to'); + $from = $emailDSN->getParam('from'); + $isEuRegion = $emailDSN->getParam('isEuRegion'); + $apiKey = $emailDSN->getPassword(); + $domain = $emailDSN->getUser(); + if (empty($to) || empty($from) || empty($apiKey) || empty($domain) || empty($isEuRegion)) { $this->markTestSkipped('Email provider not configured'); } @@ -605,7 +620,7 @@ trait MessagingBase ], [ 'messageId' => ID::unique(), 'status' => 'draft', - 'to' => [$topic['body']['$id']], + 'topics' => [$topic['body']['$id']], 'subject' => 'Khali beats Undertaker', 'content' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', ]); @@ -632,16 +647,22 @@ trait MessagingBase ]); $this->assertEquals(200, $message['headers']['status-code']); - $this->assertEquals(1, $message['body']['deliveredTo']); + $this->assertEquals(1, $message['body']['deliveredTotal']); $this->assertEquals(0, \count($message['body']['deliveryErrors'])); } public function testSendSMS() { - $to = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_TO'); - $from = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_FROM'); - $senderId = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_SENDER_ID'); - $authKey = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_AUTH_KEY'); + if (empty(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN'))) { + $this->markTestSkipped('SMS DSN not provided'); + } + + $smsDSN = new DSN(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN')); + $to = $smsDSN->getParam('to'); + $from = $smsDSN->getParam('from'); + $authKey = $smsDSN->getPassword(); + $senderId = $smsDSN->getUser(); + if (empty($to) || empty($from) || empty($senderId) || empty($authKey)) { $this->markTestSkipped('SMS provider not configured'); } @@ -717,7 +738,7 @@ trait MessagingBase 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ 'messageId' => ID::unique(), - 'to' => [$topic['body']['$id']], + 'topics' => [$topic['body']['$id']], 'content' => '064763', ]); @@ -733,7 +754,7 @@ trait MessagingBase ]); $this->assertEquals(200, $message['headers']['status-code']); - $this->assertEquals(1, $message['body']['deliveredTo']); + $this->assertEquals(1, $message['body']['deliveredTotal']); $this->assertEquals(0, \count($message['body']['deliveryErrors'])); return $message; @@ -744,10 +765,16 @@ trait MessagingBase */ public function testUpdateSMS(array $sms) { - $to = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_TO'); - $from = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_FROM'); - $senderId = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_SENDER_ID'); - $authKey = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_AUTH_KEY'); + if (empty(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN'))) { + $this->markTestSkipped('SMS DSN not provided'); + } + + $smsDSN = new DSN(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN')); + $to = $smsDSN->getParam('to'); + $from = $smsDSN->getParam('from'); + $authKey = $smsDSN->getPassword(); + $senderId = $smsDSN->getUser(); + if (empty($to) || empty($from) || empty($senderId) || empty($authKey)) { $this->markTestSkipped('SMS provider not configured'); } @@ -833,7 +860,7 @@ trait MessagingBase ], [ 'messageId' => ID::unique(), 'status' => 'draft', - 'to' => [$topic['body']['$id']], + 'topics' => [$topic['body']['$id']], 'content' => '047487', ]); @@ -859,14 +886,20 @@ trait MessagingBase ]); $this->assertEquals(200, $message['headers']['status-code']); - $this->assertEquals(1, $message['body']['deliveredTo']); + $this->assertEquals(1, $message['body']['deliveredTotal']); $this->assertEquals(0, \count($message['body']['deliveryErrors'])); } public function testSendPushNotification() { - $to = App::getEnv('_APP_MESSAGE_PUSH_PROVIDER_FCM_RECEIVER_TOKEN'); - $serverKey = App::getEnv('_APP_MESSAGE_PUSH_PROVIDER_FCM_SERVERY_KEY'); + if (empty(App::getEnv('_APP_MESSAGE_PUSH_TEST_DSN'))) { + $this->markTestSkipped('Push DSN empty'); + } + + $pushDSN = new DSN(App::getEnv('_APP_MESSAGE_PUSH_TEST_DSN')); + $to = $pushDSN->getParam('to'); + $serverKey = $pushDSN->getPassword(); + if (empty($to) || empty($serverKey)) { $this->markTestSkipped('Push provider not configured'); } @@ -940,7 +973,7 @@ trait MessagingBase 'x-appwrite-key' => $this->getProject()['apiKey'], ], [ 'messageId' => ID::unique(), - 'to' => [$topic['body']['$id']], + 'topics' => [$topic['body']['$id']], 'title' => 'Test-Notification', 'body' => 'Test-Notification-Body', ]); @@ -957,7 +990,7 @@ trait MessagingBase ]); $this->assertEquals(200, $message['headers']['status-code']); - $this->assertEquals(1, $message['body']['deliveredTo']); + $this->assertEquals(1, $message['body']['deliveredTotal']); $this->assertEquals(0, \count($message['body']['deliveryErrors'])); return $message; @@ -968,8 +1001,14 @@ trait MessagingBase */ public function testUpdatePushNotification(array $push) { - $to = App::getEnv('_APP_MESSAGE_PUSH_PROVIDER_FCM_RECEIVER_TOKEN'); - $serverKey = App::getEnv('_APP_MESSAGE_PUSH_PROVIDER_FCM_SERVERY_KEY'); + if (empty(App::getEnv('_APP_MESSAGE_PUSH_TEST_DSN'))) { + $this->markTestSkipped('Push DSN empty'); + } + + $pushDSN = new DSN(App::getEnv('_APP_MESSAGE_PUSH_TEST_DSN')); + $to = $pushDSN->getParam('to'); + $serverKey = $pushDSN->getPassword(); + if (empty($to) || empty($serverKey)) { $this->markTestSkipped('Push provider not configured'); } @@ -1053,7 +1092,7 @@ trait MessagingBase ], [ 'messageId' => ID::unique(), 'status' => 'draft', - 'to' => [$topic['body']['$id']], + 'topics' => [$topic['body']['$id']], 'title' => 'Test-Notification', 'body' => 'Test-Notification-Body', ]); @@ -1080,7 +1119,7 @@ trait MessagingBase ]); $this->assertEquals(200, $message['headers']['status-code']); - $this->assertEquals(1, $message['body']['deliveredTo']); + $this->assertEquals(1, $message['body']['deliveredTotal']); $this->assertEquals(0, \count($message['body']['deliveryErrors'])); } }