diff --git a/app/config/errors.php b/app/config/errors.php index 99cde0baee..5942c6027c 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -696,7 +696,11 @@ return [ Exception::PROVIDER_NOT_FOUND => [ 'name' => Exception::PROVIDER_NOT_FOUND, 'description' => 'Provider with the request ID could not be found.', + 'code' => 404, + ], + Exception::PROVIDER_INCORRECT_TYPE => [ + 'name' => Exception::PROVIDER_INCORRECT_TYPE, + 'description' => 'Provider with the request ID is of incorrect type: ', 'code' => 400, - ] ]; diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index e6b0b2addc..46acd2efdb 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -3051,8 +3051,7 @@ App::post('/v1/account/targets') ])); $dbForProject->deleteCachedDocument('users', $user->getId()); $events - ->setParam('userId', $userId) - ->setParam('targetId', $targetId); + ->setParam('userId', $userId); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($target, Response::MODEL_TARGET); @@ -3097,8 +3096,7 @@ App::patch('/v1/account/targets/:targetId/identifier') $dbForProject->deleteCachedDocument('users', $user->getId()); $events - ->setParam('userId', $userId) - ->setParam('targetId', $targetId); + ->setParam('userId', $userId); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -3145,7 +3143,6 @@ App::delete('/v1/account/targets/:targetId') $events ->setParam('userId', $userId) - ->setParam('targetId', $targetId) ->setPayload($response->output($clone, Response::MODEL_USER)); $response->noContent(); diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 8d1c13492e..14065f9097 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -2,18 +2,87 @@ use Appwrite\Event\Event; use Appwrite\Extend\Exception; +use Appwrite\Utopia\Database\Validator\Queries\Providers; use Appwrite\Utopia\Response; use Utopia\App; use Utopia\Database\Database; use Utopia\Database\Document; +use Utopia\Database\Query; +use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Datetime; +use Utopia\Database\Validator\UID; use Utopia\Validator\ArrayList; use Utopia\Validator\Text; +App::get('/v1/messaging/providers') + ->desc('List Providers') + ->groups(['api', 'messaging']) + ->label('scope', 'providers.read') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'listProviders') + ->label('sdk.description', '/docs/references/messaging/list-providers.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER_LIST) + ->param('queries', [], new Providers(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Providers::ALLOWED_ATTRIBUTES), true) + ->inject('dbForProject') + ->inject('response') + ->action(function (array $queries, Database $dbForProject, Response $response) { + $queries = Query::parseQueries($queries); + + // Get cursor document if there was a cursor query + $cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]); + $cursor = reset($cursor); + + if ($cursor) { + $providerId = $cursor->getValue(); + $cursorDocument = Authorization::skip(fn () => $dbForProject->find('providers', [ + Query::equal('$id', [$providerId]), + Query::limit(1), + ])); + + if (empty($cursorDocument) || $cursorDocument[0]->isEmpty()) { + throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Provider '{$providerId}' for the 'cursor' value not found."); + } + + $cursor->setValue($cursorDocument[0]); + } + + $filterQueries = Query::groupByType($queries)['filters']; + $response->dynamic(new Document([ + 'total' => $dbForProject->count('providers', $filterQueries, APP_LIMIT_COUNT), + 'indexes' => $dbForProject->find('providers', $queries), + ]), Response::MODEL_PROVIDER_LIST); + }); + +App::get('/v1/messaging/providers/:id') + ->desc('Get Provider') + ->groups(['api', 'messaging']) + ->label('scope', 'providers.read') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'getProvider') + ->label('sdk.description', '/docs/references/messaging/get-provider.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('id', null, new UID(), 'Provider ID.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $id, Response $response, Database $dbForProject) { + $provider = $dbForProject->getDocument('providers', $id); + + if ($provider->isEmpty()) { + throw new Exception(Exception::PROVIDER_NOT_FOUND); + } + + $response->dynamic($provider, Response::MODEL_PROVIDER); + }); + /** * Email Providers */ - App::post('/v1/messaging/providers/mailgun') ->desc('Create Mailgun Provider') ->groups(['api', 'messaging']) @@ -21,23 +90,24 @@ App::post('/v1/messaging/providers/mailgun') ->label('scope', 'providers.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'createProviderMailgun') ->label('sdk.description', '/docs/references/messaging/create-provider-mailgun.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_PROVIDER) ->param('name', '', new Text(128), 'Provider name.') - ->param('apiKey', null, new Text(0), 'Mailgun API Key.', true) - ->param('domain', null, new Text(0), 'Mailgun Domain.', true) + ->param('apiKey', '', new Text(0), 'Mailgun API Key.') + ->param('domain', '', new Text(0), 'Mailgun Domain.') ->inject('dbForProject') ->inject('response') ->action(function (string $name, string $apiKey, string $domain, Database $dbForProject, Response $response) { $provider = $dbForProject->createDocument('providers', new Document([ 'name' => $name, - 'provider' => 'Mailgun', + 'provider' => 'mailgun', 'type' => 'email', 'credentials' => [ 'apiKey' => $apiKey, - 'domain' => $domain + 'domain' => $domain, ], ])); $response @@ -45,6 +115,782 @@ App::post('/v1/messaging/providers/mailgun') ->dynamic($provider, Response::MODEL_PROVIDER); }); +App::patch('/v1/messaging/providers/:id/mailgun') + ->desc('Update Mailgun Provider') + ->groups(['api', 'messaging']) + ->label('event', 'messages.providers.update') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'updateProviderMailgun') + ->label('sdk.description', '/docs/references/messaging/update-provider-mailgun.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('id', '', new UID(), 'Provider ID.') + ->param('name', '', new Text(128), 'Provider name.', 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 $id, string $name, string $apiKey, string $domain, Database $dbForProject, Response $response) { + $provider = $dbForProject->getDocument('providers', $id); + + if ($provider->isEmpty()) { + throw new Exception(Exception::PROVIDER_NOT_FOUND); + } + $providerAttr = $provider->getAttribute('provider'); + + if ($providerAttr !== 'mailgun') { + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE . $providerAttr); + } + + if ($name) { + $provider->setAttribute('name', $name); + } + + if ($apiKey || $domain) { + // Check if all five variables are present + if ($apiKey && $domain) { + $provider->setAttribute('credentials', [ + 'apiKey' => $apiKey, + 'domain' => $domain + ]); + } else { + // Not all credential params are present + throw new Exception(Exception::DOCUMENT_MISSING_DATA); + } + } + + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); + $dbForProject->deleteCachedDocument('providers', $provider->getId()); + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::post('/v1/messaging/providers/sendgrid') + ->desc('Create Sendgrid Provider') + ->groups(['api', 'messaging']) + ->label('event', 'messages.providers.create') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'createProviderSendgrid') + ->label('sdk.description', '/docs/references/messaging/create-provider-sendgrid.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('name', '', new Text(128), 'Provider name.') + ->param('apiKey', '', new Text(0), 'Sendgrid API key.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $name, string $apiKey, Database $dbForProject, Response $response) { + $provider = $dbForProject->createDocument('providers', new Document([ + 'name' => $name, + 'provider' => 'sendgrid', + 'type' => 'email', + 'credentials' => [ + 'apiKey' => $apiKey, + ], + ])); + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::patch('/v1/messaging/providers/:id/sendgrid') + ->desc('Update Sendgrid Provider') + ->groups(['api', 'messaging']) + ->label('event', 'messages.providers.update') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'updateProviderSendgrid') + ->label('sdk.description', '/docs/references/messaging/update-provider-sendgrid.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('id', '', new UID(), 'Provider ID.') + ->param('name', '', new Text(128), 'Provider name.', true) + ->param('apiKey', '', new Text(0), 'Sendgrid API key.', true) + ->inject('dbForProject') + ->inject('response') + ->action(function (string $id, string $name, string $apiKey, Database $dbForProject, Response $response) { + $provider = $dbForProject->getDocument('providers', $id); + + if ($provider->isEmpty()) { + throw new Exception(Exception::PROVIDER_NOT_FOUND); + } + $providerAttr = $provider->getAttribute('provider'); + + if ($providerAttr !== 'sendgrid') { + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE . $providerAttr); + } + + if ($name) { + $provider->setAttribute('name', $name); + } + + if ($apiKey) { + $provider->setAttribute('credentials', [ + 'apiKey' => $apiKey + ]); + } + + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); + $dbForProject->deleteCachedDocument('providers', $provider->getId()); + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +/** + * SMS Providers + */ +App::post('/v1/messaging/providers/msg91') + ->desc('Create Msg91 Provider') + ->groups(['api', 'messaging']) + ->label('event', 'messages.providers.create') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'createProviderMsg91') + ->label('sdk.description', '/docs/references/messaging/create-provider-msg91.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('name', '', new Text(128), 'Provider name.') + ->param('senderId', '', new Text(0), 'Msg91 Sender ID.') + ->param('authKey', '', new Text(0), 'Msg91 Auth Key.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $name, string $senderId, string $authKey, Database $dbForProject, Response $response) { + $provider = $dbForProject->createDocument('providers', new Document([ + 'name' => $name, + 'provider' => 'msg91', + 'type' => 'sms', + 'credentials' => [ + 'senderId' => $senderId, + 'authKey' => $authKey, + ], + ])); + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::patch('/v1/messaging/providers/:id/msg91') + ->desc('Update Msg91 Provider') + ->groups(['api', 'messaging']) + ->label('event', 'messages.providers.update') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'updateProviderMsg91') + ->label('sdk.description', '/docs/references/messaging/update-provider-msg91.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('id', '', new UID(), 'Provider ID.') + ->param('name', '', new Text(128), 'Provider name.', true) + ->param('senderId', '', new Text(0), 'Msg91 Sender ID.', true) + ->param('authKey', '', new Text(0), 'Msg91 Auth Key.', true) + ->inject('dbForProject') + ->inject('response') + ->action(function (string $id, string $name, string $senderId, string $authKey, Database $dbForProject, Response $response) { + $provider = $dbForProject->getDocument('providers', $id); + + if ($provider->isEmpty()) { + throw new Exception(Exception::PROVIDER_NOT_FOUND); + } + $providerAttr = $provider->getAttribute('provider'); + + if ($providerAttr !== 'msg91') { + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE . $providerAttr); + } + + if ($name) { + $provider->setAttribute('name', $name); + } + + if ($senderId || $authKey) { + // Check if all five variables are present + if ($senderId && $authKey) { + $provider->setAttribute('credentials', [ + 'senderId' => $senderId, + 'authKey' => $authKey + ]); + } else { + // Not all credential params are present + throw new Exception(Exception::DOCUMENT_MISSING_DATA); + } + } + + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); + $dbForProject->deleteCachedDocument('providers', $provider->getId()); + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::post('/v1/messaging/providers/telesign') + ->desc('Create Telesign Provider') + ->groups(['api', 'messaging']) + ->label('event', 'messages.providers.create') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'createProviderTelesign') + ->label('sdk.description', '/docs/references/messaging/create-provider-telesign.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('name', '', new Text(128), 'Provider name.') + ->param('username', '', new Text(0), 'Telesign username.') + ->param('password', '', new Text(0), 'Telesign password.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $name, string $username, string $password, Database $dbForProject, Response $response) { + $provider = $dbForProject->createDocument('providers', new Document([ + 'name' => $name, + 'provider' => 'telesign', + 'type' => 'sms', + 'credentials' => [ + 'username' => $username, + 'password' => $password, + ], + ])); + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::patch('/v1/messaging/providers/:id/telesign') + ->desc('Update Telesign Provider') + ->groups(['api', 'messaging']) + ->label('event', 'messages.providers.update') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'updateProviderTelesign') + ->label('sdk.description', '/docs/references/messaging/update-provider-telesign.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('id', '', new UID(), 'Provider ID.') + ->param('name', '', new Text(128), 'Provider name.', true) + ->param('username', '', new Text(0), 'Telesign username.', true) + ->param('password', '', new Text(0), 'Telesign password.', true) + ->inject('dbForProject') + ->inject('response') + ->action(function (string $id, string $name, string $username, string $password, Database $dbForProject, Response $response) { + $provider = $dbForProject->getDocument('providers', $id); + + if ($provider->isEmpty()) { + throw new Exception(Exception::PROVIDER_NOT_FOUND); + } + $providerAttr = $provider->getAttribute('provider'); + + if ($providerAttr !== 'telesign') { + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE . $providerAttr); + } + + if ($name) { + $provider->setAttribute('name', $name); + } + + if ($username || $password) { + // Check if all five variables are present + if ($username && $password) { + $provider->setAttribute('credentials', [ + 'username' => $username, + 'password' => $password + ]); + } else { + // Not all credential params are present + throw new Exception(Exception::DOCUMENT_MISSING_DATA); + } + } + + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); + $dbForProject->deleteCachedDocument('providers', $provider->getId()); + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::post('/v1/messaging/providers/textmagic') + ->desc('Create Textmagic Provider') + ->groups(['api', 'messaging']) + ->label('event', 'messages.providers.create') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'createProviderTextmagic') + ->label('sdk.description', '/docs/references/messaging/create-provider-textmagic.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('name', '', new Text(128), 'Provider name.') + ->param('username', '', new Text(0), 'Textmagic username.') + ->param('apiKey', '', new Text(0), 'Textmagic apiKey.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $name, string $username, string $apiKey, Database $dbForProject, Response $response) { + $provider = $dbForProject->createDocument('providers', new Document([ + 'name' => $name, + 'provider' => 'text-magic', + 'type' => 'sms', + 'credentials' => [ + 'username' => $username, + 'apiKey' => $apiKey, + ], + ])); + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::patch('/v1/messaging/providers/:id/textmagic') + ->desc('Update Textmagic Provider') + ->groups(['api', 'messaging']) + ->label('event', 'messages.providers.update') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'updateProviderTextmagic') + ->label('sdk.description', '/docs/references/messaging/update-provider-textmagic.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('id', '', new UID(), 'Provider ID.') + ->param('name', '', new Text(128), 'Provider name.', true) + ->param('username', '', new Text(0), 'Textmagic username.', true) + ->param('apiKey', '', new Text(0), 'Textmagic apiKey.', true) + ->inject('dbForProject') + ->inject('response') + ->action(function (string $id, string $name, string $username, string $apiKey, Database $dbForProject, Response $response) { + $provider = $dbForProject->getDocument('providers', $id); + + if ($provider->isEmpty()) { + throw new Exception(Exception::PROVIDER_NOT_FOUND); + } + $providerAttr = $provider->getAttribute('provider'); + + if ($providerAttr !== 'text-magic') { + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE . $providerAttr); + } + + if ($name) { + $provider->setAttribute('name', $name); + } + + if ($username || $apiKey) { + // Check if all five variables are present + if ($username && $apiKey) { + $provider->setAttribute('credentials', [ + 'username' => $username, + 'apiKey' => $apiKey + ]); + } else { + // Not all credential params are present + throw new Exception(Exception::DOCUMENT_MISSING_DATA); + } + } + + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); + $dbForProject->deleteCachedDocument('providers', $provider->getId()); + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::post('/v1/messaging/providers/twilio') + ->desc('Create Twilio Provider') + ->groups(['api', 'messaging']) + ->label('event', 'messages.providers.create') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'createProviderTwilio') + ->label('sdk.description', '/docs/references/messaging/create-provider-twilio.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('name', '', new Text(128), 'Provider name.') + ->param('accountSid', '', new Text(0), 'Twilio account secret ID.') + ->param('authToken', '', new Text(0), 'Twilio authentication token.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $name, string $accountSid, string $authToken, Database $dbForProject, Response $response) { + $provider = $dbForProject->createDocument('providers', new Document([ + 'name' => $name, + 'provider' => 'twilio', + 'type' => 'sms', + 'credentials' => [ + 'accountSid' => $accountSid, + 'authToken' => $authToken, + ], + ])); + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::patch('/v1/messaging/providers/:id/twilio') + ->desc('Update Twilio Provider') + ->groups(['api', 'messaging']) + ->label('event', 'messages.providers.update') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'updateProviderTwilio') + ->label('sdk.description', '/docs/references/messaging/update-provider-twilio.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('id', '', new UID(), 'Provider ID.') + ->param('name', '', new Text(128), 'Provider name.', true) + ->param('accountSid', null, new Text(0), 'Twilio account secret ID.', true) + ->param('authToken', null, new Text(0), 'Twilio authentication token.', true) + ->inject('dbForProject') + ->inject('response') + ->action(function (string $id, string $name, string $accountSid, string $authToken, Database $dbForProject, Response $response) { + $provider = $dbForProject->getDocument('providers', $id); + + if ($provider->isEmpty()) { + throw new Exception(Exception::PROVIDER_NOT_FOUND); + } + $providerAttr = $provider->getAttribute('provider'); + + if ($providerAttr !== 'twilio') { + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE . $providerAttr); + } + + if ($name) { + $provider->setAttribute('name', $name); + } + + if ($accountSid || $authToken) { + // Check if all five variables are present + if ($accountSid && $authToken) { + $provider->setAttribute('credentials', [ + 'accountSid' => $accountSid, + 'authToken' => $authToken, + ]); + } else { + // Not all credential params are present + throw new Exception(Exception::DOCUMENT_MISSING_DATA); + } + } + + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); + $dbForProject->deleteCachedDocument('providers', $provider->getId()); + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::post('/v1/messaging/providers/vonage') + ->desc('Create Vonage Provider') + ->groups(['api', 'messaging']) + ->label('event', 'messages.providers.create') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'createProviderVonage') + ->label('sdk.description', '/docs/references/messaging/create-provider-vonage.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('name', '', new Text(128), 'Provider name.') + ->param('apiKey', '', new Text(0), 'Vonage API key.') + ->param('apiSecret', '', new Text(0), 'Vonage API secret.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $name, string $apiKey, string $apiSecret, Database $dbForProject, Response $response) { + $provider = $dbForProject->createDocument('providers', new Document([ + 'name' => $name, + 'provider' => 'vonage', + 'type' => 'sms', + 'credentials' => [ + 'apiKey' => $apiKey, + 'apiSecret' => $apiSecret, + ], + ])); + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::patch('/v1/messaging/providers/:id/vonage') + ->desc('Update Vonage Provider') + ->groups(['api', 'messaging']) + ->label('event', 'messages.providers.update') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'updateProviderVonage') + ->label('sdk.description', '/docs/references/messaging/update-provider-vonage.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('id', '', new UID(), 'Provider ID.') + ->param('name', '', new Text(128), 'Provider name.', true) + ->param('apiKey', '', new Text(0), 'Vonage API key.', true) + ->param('apiSecret', '', new Text(0), 'Vonage API secret.', true) + ->inject('dbForProject') + ->inject('response') + ->action(function (string $id, string $name, string $apiKey, string $apiSecret, Database $dbForProject, Response $response) { + $provider = $dbForProject->getDocument('providers', $id); + + if ($provider->isEmpty()) { + throw new Exception(Exception::PROVIDER_NOT_FOUND); + } + $providerAttr = $provider->getAttribute('provider'); + + if ($providerAttr !== 'vonage') { + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE . $providerAttr); + } + + if ($name) { + $provider->setAttribute('name', $name); + } + + if ($apiKey || $apiSecret) { + // Check if all five variables are present + if ($apiKey && $apiSecret) { + $provider->setAttribute('credentials', [ + 'apiKey' => $apiKey, + 'apiSecret' => $apiSecret, + ]); + } else { + // Not all credential params are present + throw new Exception(Exception::DOCUMENT_MISSING_DATA); + } + } + + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); + $dbForProject->deleteCachedDocument('providers', $provider->getId()); + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +/** + * Push Providers + */ +App::post('/v1/messaging/providers/fcm') + ->desc('Create FCM Provider') + ->groups(['api', 'messaging']) + ->label('event', 'messages.providers.create') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'createProviderFCM') + ->label('sdk.description', '/docs/references/messaging/create-provider-fcm.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('name', '', new Text(128), 'Provider name.') + ->param('serverKey', '', new Text(0), 'FCM Server Key.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $name, string $serverKey, Database $dbForProject, Response $response) { + $provider = $dbForProject->createDocument('providers', new Document([ + 'name' => $name, + 'provider' => 'fcm', + 'type' => 'push', + 'credentials' => [ + 'serverKey' => $serverKey, + ], + ])); + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::patch('/v1/messaging/providers/:id/fcm') + ->desc('Update FCM Provider') + ->groups(['api', 'messaging']) + ->label('event', 'messages.providers.update') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'updateProviderFCM') + ->label('sdk.description', '/docs/references/messaging/update-provider-fcm.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('id', '', new UID(), 'Provider ID.') + ->param('name', '', new Text(128), 'Provider name.', true) + ->param('serverKey', '', new Text(0), 'FCM Server Key.', true) + ->inject('dbForProject') + ->inject('response') + ->action(function (string $id, string $name, string $serverKey, Database $dbForProject, Response $response) { + $provider = $dbForProject->getDocument('providers', $id); + + if ($provider->isEmpty()) { + throw new Exception(Exception::PROVIDER_NOT_FOUND); + } + $providerAttr = $provider->getAttribute('provider'); + + if ($providerAttr !== 'fcm') { + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE . $providerAttr); + } + + if ($name) { + $provider->setAttribute('name', $name); + } + + if ($serverKey) { + $provider->setAttribute('credentials', ['serverKey' => $serverKey]); + } + + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); + $dbForProject->deleteCachedDocument('providers', $provider->getId()); + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::post('/v1/messaging/providers/apns') + ->desc('Create APNS Provider') + ->groups(['api', 'messaging']) + ->label('event', 'messages.providers.create') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'createProviderAPNS') + ->label('sdk.description', '/docs/references/messaging/create-provider-apns.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('name', '', new Text(128), 'Provider name.') + ->param('authKey', '', new Text(0), 'APNS authentication key.') + ->param('authKeyId', '', new Text(0), 'APNS authentication key ID.') + ->param('teamId', '', new Text(0), 'APNS team ID.') + ->param('bundleId', '', new Text(0), 'APNS bundle ID.') + ->param('endpoint', '', new Text(0), 'APNS endpoint.') + ->inject('dbForProject') + ->inject('response') + ->action(function (string $name, string $authKey, string $authKeyId, string $teamId, string $bundleId, string $endpoint, Database $dbForProject, Response $response) { + $provider = $dbForProject->createDocument('providers', new Document([ + 'name' => $name, + 'provider' => 'apns', + 'type' => 'push', + 'credentials' => [ + 'authKey' => $authKey, + 'authKeyId' => $authKeyId, + 'teamId' => $teamId, + 'bundleId' => $bundleId, + 'endpoint' => $endpoint, + ], + ])); + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::patch('/v1/messaging/providers/:id/apns') + ->desc('Update APNS Provider') + ->groups(['api', 'messaging']) + ->label('event', 'messages.providers.update') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'updateProviderAPNS') + ->label('sdk.description', '/docs/references/messaging/update-provider-apns.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROVIDER) + ->param('id', '', new UID(), 'Provider ID.') + ->param('name', '', new Text(128), 'Provider name.', 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) + ->param('bundleId', '', new Text(0), 'APNS bundle ID.', true) + ->param('endpoint', '', new Text(0), 'APNS endpoint.', true) + ->inject('dbForProject') + ->inject('response') + ->action(function (string $id, string $name, string $authKey, string $authKeyId, string $teamId, string $bundleId, string $endpoint, Database $dbForProject, Response $response) { + $provider = $dbForProject->getDocument('providers', $id); + + if ($provider->isEmpty()) { + throw new Exception(Exception::PROVIDER_NOT_FOUND); + } + $providerAttr = $provider->getAttribute('provider'); + + if ($providerAttr !== 'apns') { + throw new Exception(Exception::PROVIDER_INCORRECT_TYPE . $providerAttr); + } + + if ($name) { + $provider->setAttribute('name', $name); + } + + if ($authKey || $authKeyId || $teamId || $bundleId || $endpoint) { + // Check if all five variables are present + if ($authKey && $authKeyId && $teamId && $bundleId && $endpoint) { + $provider->setAttribute('credentials', [ + 'authKey' => $authKey, + 'authKeyId' => $authKeyId, + 'teamId' => $teamId, + 'bundleId' => $bundleId, + 'endpoint' => $endpoint, + ]); + } else { + // Not all credential params are present + throw new Exception(Exception::DOCUMENT_MISSING_DATA); + } + } + + $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); + $dbForProject->deleteCachedDocument('providers', $provider->getId()); + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($provider, Response::MODEL_PROVIDER); + }); + +App::delete('/v1/messaging/providers/:id') + ->desc('Delete Provider') + ->groups(['api', 'messaging']) + ->label('event', 'messages.providers.[id].delete') + ->label('scope', 'providers.write') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'deleteProvider') + ->label('sdk.description', '/docs/references/messaging/delete-provider.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_NONE) + ->param('id', '', new UID(), 'Provider ID.') + ->inject('response') + ->inject('dbForProject') + ->inject('events') + ->action(function (string $id, Response $response, Database $dbForProject) { + $provider = $dbForProject->getDocument('providers', $id); + + if ($provider->isEmpty()) { + throw new Exception(Exception::PROVIDER_NOT_FOUND); + } + + $dbForProject->deleteCachedDocument('providers', $provider->getId()); + $dbForProject->deleteDocument('providers', $provider->getId()); + + $response->noContent(); + }); + App::post('/v1/messaging/messages/email') ->desc('Send an email.') ->groups(['api', 'messaging']) @@ -52,6 +898,7 @@ App::post('/v1/messaging/messages/email') ->label('scope', 'messages.write') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'messaging') + ->label('sdk.method', 'sendEmail') ->label('sdk.description', '/docs/references/messaging/send-email.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) @@ -70,7 +917,7 @@ App::post('/v1/messaging/messages/email') $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { - throw new Exception(Exception::PROVIDER_NOT_FOUND); + throw new Exception(Exception::PROVIDER_NOT_FOUND); } $message = $dbForProject->createDocument('messages', new Document([ @@ -85,7 +932,7 @@ App::post('/v1/messaging/messages/email') 'deliveryError' => null, 'deliveredTo' => null, 'delivered' => false, - 'search' => null + 'search' => null, ])); $eventsInstance->setParam('messageId', $message->getId()); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index e6d5e8e1b9..cf94135351 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -425,8 +425,7 @@ App::post('/v1/users/:userId/targets') ])); $dbForProject->deleteCachedDocument('users', $user->getId()); $events - ->setParam('userId', $userId) - ->setParam('targetId', $targetId); + ->setParam('userId', $userId); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($target, Response::MODEL_TARGET); @@ -1251,8 +1250,7 @@ App::patch('/v1/users/:userId/targets/:targetId/identifier') $dbForProject->deleteCachedDocument('users', $user->getId()); $events - ->setParam('userId', $userId) - ->setParam('targetId', $targetId); + ->setParam('userId', $userId); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -1426,7 +1424,6 @@ App::delete('/v1/users/:userId/targets/:targetId') $events ->setParam('userId', $userId) - ->setParam('targetId', $targetId) ->setPayload($response->output($clone, Response::MODEL_USER)); $response->noContent(); diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index a3e6817645..d5803f5fbf 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -214,6 +214,7 @@ class Exception extends \Exception /** Provider */ public const PROVIDER_NOT_FOUND = 'provider_not_found'; + public const PROVIDER_INCORRECT_TYPE = 'provider_incorrect_type'; protected $type = ''; protected $errors = []; diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Providers.php b/src/Appwrite/Utopia/Database/Validator/Queries/Providers.php new file mode 100644 index 0000000000..83dbf665f1 --- /dev/null +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Providers.php @@ -0,0 +1,21 @@ +